home *** CD-ROM | disk | FTP | other *** search
/ Piper's Pit BBS/FTP: ibm 0000 - 0009 / ibm0000-0009 / ibm0003.tar / ibm0003 / TPOWER52.ZIP / TPSRC1.ARC / TPDOS.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1989-07-10  |  40.5 KB  |  1,366 lines

  1. {$S-,R-,V-,I-,B-,F+}
  2.  
  3. {$IFNDEF Ver40}
  4.   {$I OPLUS.INC}
  5. {$ENDIF}
  6.  
  7. {*********************************************************}
  8. {*                    TPDOS.PAS 5.07                     *}
  9. {*        Copyright (c) TurboPower Software 1987.        *}
  10. {* Portions copyright (c) Sunny Hill Software 1985, 1986 *}
  11. {*     and used under license to TurboPower Software     *}
  12. {*                 All rights reserved.                  *}
  13. {*********************************************************}
  14.  
  15. unit TpDos;
  16.   {-Miscellaneous DOS/BIOS call routines}
  17.  
  18. interface
  19.  
  20. uses
  21.   Dos, TpString;
  22.  
  23. type
  24.   ActionCodeType = (
  25.     ExecSaveScreen, ExecShowMemory, ExecPauseAfterRun, ExecRestoreScreen);
  26.   DiskClass = (
  27.     Floppy360, Floppy720, Floppy12, Floppy144, OtherFloppy, Bernoulli,
  28.     HardDisk, RamDisk, SubstDrive, UnknownDisk, InvalidDrive);
  29.  
  30. var
  31.   IoResultPtr : Pointer;     {if not nil, must point to a routine like
  32.                               Int24Result that returns an I/O error code}
  33. const
  34.   StackSafetyMargin : Word = 1000;
  35.   MinSpaceForDos : Word = 20000; {Minimum bytes for DOS shell to run}
  36.  
  37.   StdInHandle = 0;           {handle numbers for OpenStdDev}
  38.   StdOutHandle = 1;
  39.   StdErrHandle = 2;
  40.   StdPrnHandle = 4;
  41.  
  42. function DOSVersion : Word;
  43.   {-Returns the DOS version number. High byte has major version number,
  44.     low byte has minor version number. Eg., DOS 3.1 => $0301.}
  45.  
  46. function NumberOfDrives : Byte;
  47.   {-Returns the number of logical drives}
  48.  
  49. procedure SelectDrive(Drive : Char);
  50.   {-Selects the specified drive as default if possible}
  51.  
  52. function DefaultDrive : Char;
  53.   {-Returns the default drive as an uppercase letter}
  54.  
  55. function GetDiskInfo(Drive : Byte; var ClustersAvailable, TotalClusters,
  56.                      BytesPerSector, SectorsPerCluster : Word) : Boolean;
  57.   {-Return technical info about the specified drive}
  58.  
  59. function GetDiskClass(Drive : Char; var SubstDriveChar : Char) : DiskClass;
  60.   {-Return the disk class for the drive with the specified letter}
  61.  
  62. function ReadDiskSectors(Drive : Word; FirstSect : Longint;
  63.                          NumSects : Word; var Buf) : Boolean;
  64.   {-Read absolute disk sectors.}
  65.  
  66. function WriteDiskSectors(Drive : Word; FirstSect : Longint;
  67.                           NumSects : Word; var Buf) : Boolean;
  68.   {-Write absolute disk sectors.}
  69.  
  70. function GetFileMode(FName : string; var Attr : Word) : Byte;
  71.   {-Returns a file's attribute in Attr and the DOS error code as the function
  72.     result.}
  73.  
  74. function FileHandlesLeft : Byte;
  75.   {-Return the number of available file handles}
  76.  
  77. function FileHandlesOpen(CountDevices : Boolean) : Byte;
  78.   {-Return the number of open files owned by a program}
  79.  
  80. procedure SetDta(DTAptr : Pointer);
  81.   {-Set the DOS DTA to point to DTAptr}
  82.  
  83. procedure GetDta(var DTAptr : Pointer);
  84.   {-Return the DOS DTA pointer}
  85.  
  86. function VerifyOn : Boolean;
  87.   {-Returns True if disk write verification is on}
  88.  
  89. procedure SetVerify(On : Boolean);
  90.   {-Turn disk write verification on/off}
  91.  
  92. function ParsePath(var InputPath, SearchPath, LeadInPath : string) : Boolean;
  93.   {-Takes a user entered path, trims blanks, and returns a valid global
  94.     search path and a valid lead-in path.}
  95.  
  96. function PrintInstalled : Boolean;
  97.   {-Returns True if PRINT.COM is installed}
  98.  
  99. function SubmitPrintFile(FileName : string) : Byte;
  100.   {-This procedure submits a file to the PC DOS 3.0 or greater concurrent
  101.    print utility.}
  102.  
  103. procedure CancelPrintFile(FileMask : string);
  104.   {-Cancels the files matched by the file mask passed in FileMask.}
  105.  
  106. procedure CancelAllPrintFiles;
  107.   {-Cancels all files in the print queue}
  108.  
  109. function GetPrintStatus(var QPtr : Pointer) : Byte;
  110.  {-Halts printing, returns current error status, puts pointer to the filename
  111.    queue in the QPtr variable. Filenames in the queue are 64-byte ASCIIZ
  112.    strings. The end of the queue is marked by a name starting with a null.}
  113.  
  114. procedure EndPrintStatus;
  115.   {-Releases the spooler from the GetPrintStatus procedure.}
  116.  
  117. function GetEnvironmentString(SearchString : string) : string;
  118.   {-Return a string from the environment}
  119.  
  120. function SetBlock(var Paragraphs : Word) : Boolean;
  121.   {-Change size of DOS memory block allocated to this program}
  122.  
  123. function ExecDos(Command : string; UseSecond : Boolean; UserRoutine : Pointer) : Integer;
  124.  {-Execute any DOS command. Call with Command = '' for a new shell.
  125.    If UseSecond is false, Command must be the full pathname of a program to
  126.    be executed. UserRoutine is the address of a routine to display status,
  127.    save/restore the screen, etc., or a nil pointer. It must be of the form:
  128.  
  129.    procedure UserRoutine(ActionCode : ActionCodeType; Param : Word);
  130.  
  131.    and it must have the FAR attribute. ExecDos return codes are as follows:
  132.          0 : Success
  133.         -1 : Insufficient memory to store free list
  134.         -2 : DOS setblock error before EXEC call
  135.         -3 : DOS setblock error after EXEC call  -- critical error!
  136.         -4 : Insufficient memory to run DOS command
  137.       else   a DOS error code
  138.   }
  139.  
  140. function TextSeek(var F : Text; Target : LongInt) : Boolean;
  141.  {-Do a Seek for a text file opened for input. Returns False in case of I/O
  142.    error.}
  143.  
  144. function TextFileSize(var F : Text) : LongInt;
  145.   {-Return the size of text file F. Returns -1 in case of I/O error.}
  146.  
  147. function TextPos(var F : Text) : LongInt;
  148.  {-Return the current position of the logical file pointer (that is,
  149.    the position of the physical file pointer, adjusted to account for
  150.    buffering). Returns -1 in case of I/O error.}
  151.  
  152. function TextFlush(var F : Text) : Boolean;
  153.   {-Flush the buffer(s) for a text file. Returns False in case of I/O error.}
  154.  
  155. function OpenStdDev(var F : Text; StdHandle : Word) : Boolean;
  156.   {-Assign the text file to a standard DOS device: 0, 1, 2, or 4}
  157.  
  158. function HandleIsConsole(Handle : Word) : Boolean;
  159.   {-Return true if handle is the console device}
  160.  
  161. procedure SetRawMode(var F : Text; On : Boolean);
  162.   {-Set "raw" mode on or off for the specified text file (must be a device)}
  163.  
  164. function ExistFile(FName : string) : Boolean;
  165.   {-Return true if file is found}
  166.  
  167. function ExistOnPath(FName : string; var FullName : string) : Boolean;
  168.  {-Return true if fname is found in
  169.    a) current directory
  170.    b) program's directory (DOS 3.X only)
  171.    c) any DOS path directory
  172.   and return full path name to file}
  173.  
  174. function TimeMs : LongInt;
  175.   {-Return time of day in milliseconds since midnight}
  176.  
  177.   {============================================================================}
  178.  
  179. implementation
  180.  
  181. type
  182.   SegOfs = record
  183.              O, S : Word;
  184.            end;
  185.   LongRec = record
  186.               LowWord, HighWord : Word; {structure of a LongInt}
  187.             end;
  188.  
  189.   {text buffer}
  190.   TextBuffer = array[0..65520] of Byte;
  191.  
  192.   {structure of a Turbo File Interface Block}
  193.   FIB =
  194.     record
  195.       Handle : Word;
  196.       Mode : Word;
  197.       BufSize : Word;
  198.       Private : Word;
  199.       BufPos : Word;
  200.       BufEnd : Word;
  201.       BufPtr : ^TextBuffer;
  202.       OpenProc : Pointer;
  203.       InOutProc : Pointer;
  204.       FlushProc : Pointer;
  205.       CloseProc : Pointer;
  206.       UserData : array[1..16] of Byte;
  207.       Name : array[0..79] of Char;
  208.       Buffer : array[0..127] of Char;
  209.     end;
  210. const
  211.   FMClosed = $D7B0;
  212.   FMInput = $D7B1;
  213.   FMOutput = $D7B2;
  214.   FMInOut = $D7B3;
  215. var
  216.   Regs : Registers;
  217.  
  218.   function DOSVersion : Word;
  219.   {-Returns the DOS version number. High byte has major version number,
  220.     low byte has minor version number. Eg., DOS 3.1 => $030A ($A = 10).}
  221.   begin
  222.     with Regs do begin
  223.       AH := $30;             {Get MS-DOS version number}
  224.       MsDos(Regs);
  225.       DOSVersion := Swap(AX); {put major version in high byte, minor in low}
  226.     end;
  227.   end;
  228.  
  229.   {$L TPDISK.OBJ}
  230.  
  231.   function NumberOfDrives : Byte; external;
  232.   procedure SelectDrive(Drive : Char); external;
  233.   function DefaultDrive : Char; external;
  234.   function GetDiskInfo(Drive : Byte; var ClustersAvailable, TotalClusters,
  235.                        BytesPerSector, SectorsPerCluster : Word) : Boolean;
  236.     external;
  237.   function ReadDiskSectors(Drive : Word; FirstSect : Longint;
  238.                            NumSects : Word; var Buf) : Boolean;
  239.     external;
  240.   function WriteDiskSectors(Drive : Word; FirstSect : Longint;
  241.                             NumSects : Word; var Buf) : Boolean;
  242.     external;
  243.  
  244.   function GetDiskClass(Drive : Char; var SubstDriveChar : Char) : DiskClass;
  245.     {-Return the disk class for the drive with the specified letter}
  246.     {-This routine uses an undocumented DOS function ($32). Information about
  247.       this function was obtained from Terry Dettmann's DOS Programmer's
  248.       Reference (Que, 1988).}
  249.   type
  250.     ParamBlock =
  251.       record
  252.         DriveNumber, DeviceDriverUnit : Byte;
  253.         BytesPerSector : Word;
  254.         SectorsPerCluster, ShiftFactor : Byte;
  255.         ReservedBootSectors : Word;
  256.         FatCopies : Byte;
  257.         RootDirEntries, FirstDataSector, HighestCluster : Word;
  258.         SectorsPerFat : Byte;
  259.         RootDirStartingSector : Word;
  260.         DeviceDriverAddress : Pointer;
  261.         Media2and3 : Byte; {media descriptor here in DOS 2.x and 3.x}
  262.         Media4 : Byte;     {media descriptor here in DOS 4.x}
  263.         NextDeviceParamBlock : Pointer;
  264.       end;
  265.     ParamBlockPtr = ^ParamBlock;
  266.   var
  267.     DriveNum : Byte;
  268.     MediaDescriptor : Byte;
  269.     Regs : Registers;
  270.   begin
  271.     {assume failure}
  272.     GetDiskClass := InvalidDrive;
  273.  
  274.     {assume that this is not a SUBSTituted drive}
  275.     SubstDriveChar := Drive;
  276.  
  277.     {convert drive letter to drive number}
  278.     Drive := Upcase(Drive);
  279.     case Drive of
  280.       'A'..'Z' : DriveNum := Ord(Drive)-$40;
  281.       else Exit;
  282.     end;
  283.  
  284.     with Regs do begin
  285.       {get pointer to media descriptor byte}
  286.       AH := $1C;
  287.       DL := DriveNum;
  288.       MsDos(Regs);
  289.       MediaDescriptor := Mem[DS:BX];
  290.  
  291.       {get pointer to drive parameter block}
  292.       AH := $32;
  293.       DL := DriveNum;
  294.       MsDos(Regs);
  295.  
  296.       {drive invalid if AL = $FF}
  297.       if (AL = $FF) then
  298.         Exit;
  299.  
  300.       with ParamBlockPtr(Ptr(DS,BX))^ do begin
  301.         {check for SUBSTituted drive}
  302.         if (DriveNumber <> Pred(DriveNum)) then begin
  303.           GetDiskClass := SubstDrive;
  304.           SubstDriveChar := Char(Ord('A')+DriveNumber);
  305.         end
  306.         else if (FatCopies = 1) then
  307.           {RAM disks have one copy of File Allocation Table}
  308.           GetDiskClass := RamDisk
  309.         else if (MediaDescriptor = $F8) then
  310.           {MediaDescriptor of $F8 indicates hard disk}
  311.           GetDiskClass := HardDisk
  312.         else if (MediaDescriptor = $FD) and (SectorsPerFat <> 2) then
  313.           {Bernoulli drives have more than 2 sectors per FAT}
  314.           GetDiskClass := Bernoulli
  315.         else if (MediaDescriptor >= $F9) then
  316.           {media descriptors >= $F9 are for floppy disks}
  317.           case HighestCluster of
  318.              355 : GetDiskClass := Floppy360;
  319.              714,
  320.             1423 : GetDiskClass := Floppy720;
  321.             2372 : GetDiskClass := Floppy12;
  322.             else   GetDiskClass := OtherFloppy;
  323.           end
  324.         else if (MediaDescriptor = $F0) and (HighestCluster = 2848) then
  325.           {it's a 1.44 meg floppy}
  326.           GetDiskClass := Floppy144
  327.         else
  328.           {unable to classify disk/drive}
  329.           GetDiskClass := UnknownDisk;
  330.       end;
  331.     end;
  332.   end;
  333.  
  334.   function GetFileMode(FName : string; var Attr : Word) : Byte;
  335.     {-Returns a file's attribute in Attr and the DOS error code as the function
  336.       result.}
  337.   var
  338.     F : file;
  339.   begin
  340.     Assign(F, FName);
  341.     {call routine in Turbo's DOS unit to get the attribute}
  342.     GetFAttr(F, Attr);
  343.     GetFileMode := DosError;
  344.   end;
  345.  
  346.   procedure SetDta(DTAptr : Pointer);
  347.     {-Set the DOS DTA to point to DTA}
  348.   var
  349.     Regs : Registers;
  350.   begin
  351.     with Regs do begin
  352.       AH := $1A;
  353.       DS := Seg(DTAptr^);
  354.       DX := Ofs(DTAptr^);
  355.       MsDos(Regs);
  356.     end;
  357.   end;
  358.  
  359.   procedure GetDta(var DTAptr : Pointer);
  360.     {-Return the DOS DTA pointer}
  361.   var
  362.     Regs : Registers;
  363.   begin
  364.     with Regs do begin
  365.       AH := $2F;
  366.       MsDos(Regs);
  367.       DTAptr := Ptr(ES, BX);
  368.     end;
  369.   end;
  370.  
  371.   function VerifyOn : Boolean;
  372.     {-Returns True if disk write verification is on}
  373.   begin
  374.     Regs.AH := $54;          {Get verify state function}
  375.     MsDos(Regs);
  376.     VerifyOn := Boolean(Regs.AL);
  377.   end;
  378.  
  379.   procedure SetVerify(On : Boolean);
  380.     {-Turn disk write verification on/off}
  381.   begin
  382.     Regs.DL := 0;            {only MicroSoft knows for sure}
  383.     Regs.AL := Byte(On);     {0 = off, 1 = on}
  384.     Regs.AH := $2E;          {Set verify state function}
  385.     MsDos(Regs);
  386.   end;
  387.  
  388.   function ParsePath(var InputPath, SearchPath, LeadInPath : string) : Boolean;
  389.     {-Takes a user entered path, trims blanks, and returns a valid global
  390.       search path and a valid lead-in path.}
  391.   var
  392.     S : string[255];
  393.     SLen : Byte absolute S;
  394.     Attr : Word;
  395.  
  396.     function IsPath(S : string) : Boolean;
  397.       {-Return True if S is empty or ends with ':' or '\'}
  398.     var
  399.       SLen : Byte absolute S;
  400.     begin
  401.       {check last character in S}
  402.       case S[SLen] of
  403.         ':', '\' : IsPath := True;
  404.         else IsPath := (SLen = 0); {True if string is empty}
  405.       end;
  406.     end;
  407.  
  408.   begin
  409.     {Assume success}
  410.     ParsePath := True;
  411.  
  412.     {Get working copy of InputPath; convert to uppercase and trim blanks}
  413.     S := StUpCase(Trim(InputPath));
  414.  
  415.     {if S is just a path name, add "*.*" to search path}
  416.     if IsPath(S) then begin
  417.       LeadInPath := S;
  418.       SearchPath := S+'*.*';
  419.     end
  420.     else
  421.       if SLen >= 77 then
  422.         ParsePath := False
  423.       else
  424.         {test validity of pathname by calling routine to get file attribute}
  425.         case GetFileMode(S, Attr) of
  426.  
  427.           0 : if (Attr and Directory {= $10} ) <> 0 then begin
  428.                 {Input path is valid directory name}
  429.                 SearchPath := S+'\*.*';
  430.                 LeadInPath := S+'\';
  431.               end
  432.               else begin
  433.                 {Input path is the name of a file}
  434.                 SearchPath := S;
  435.  
  436.                 {trim end of string until only a path is left}
  437.                 while not IsPath(S) do
  438.                   Dec(SLen);
  439.                 LeadInPath := S
  440.               end;
  441.  
  442.           3 : begin
  443.                 {path not found}
  444.                 SearchPath := S;
  445.  
  446.                 {trim end of string until only a path is left}
  447.                 while not IsPath(S) do
  448.                   Dec(SLen);
  449.  
  450.                 if (S[SLen] <> ':') or (SLen = 2) then
  451.                   LeadInPath := S
  452.                 else
  453.                   ParsePath := False;
  454.               end;
  455.         else
  456.           ParsePath := False;
  457.         end;
  458.   end;
  459.  
  460.   function PrintInstalled : Boolean;
  461.     {-Returns True if PRINT.COM is installed}
  462.   begin
  463.     {INT $2F functions available only in DOS 3}
  464.     if DOSVersion >= $300 then
  465.       with Regs do begin
  466.         AX := $0100;         {get PRINT installed status}
  467.         Intr($2F, Regs);     {print spool control interrupt}
  468.         PrintInstalled := (AL = $FF); {DOS returns $FF in AL if PRINT installed}
  469.       end
  470.     else
  471.       PrintInstalled := False;
  472.   end;
  473.  
  474.   function SubmitPrintFile(FileName : string) : Byte;
  475.     {-This procedure submits a file to the PC DOS 3.0 or greater concurrent
  476.       print utility.}
  477.   type
  478.     AsciiZ = array[1..65] of Char;
  479.     SubmitPacket = record
  480.                      Level : Byte;
  481.                      FilenamePtr : ^AsciiZ;
  482.                    end;
  483.   var
  484.     SubPack : SubmitPacket;
  485.     S : string;
  486.     SLen : Byte absolute S;
  487.   begin
  488.     S := Trim(FileName);
  489.     if SLen <> 0 then
  490.       with SubPack, Regs do begin
  491.         Level := 0;          {set level code}
  492.         if SLen > 64 then
  493.           SLen := 64;        {truncate filenames longer than 64 characters}
  494.         S[Succ(SLen)] := #0; {add null to end of string}
  495.         FilenamePtr := @S[1]; {point to first character in S}
  496.         DS := Seg(SubPack);  {DS:DX points to the packet}
  497.         DX := Ofs(SubPack);
  498.         AX := $0101;         {submit file to be printed}
  499.         Intr($2F, Regs);     {print spool control interrupt}
  500.         if Odd(Flags) then   {check carry flag}
  501.           SubmitPrintFile := AL {carry set, return code in AL}
  502.         else
  503.           SubmitPrintFile := 0;
  504.       end
  505.     else
  506.       SubmitPrintFile := 2;  {return the code for a file not found error}
  507.   end;
  508.  
  509.   procedure CancelPrintFile(FileMask : string);
  510.     {-Cancels the files matched by the file mask passed in FileMask.}
  511.   var
  512.     Len : Byte absolute FileMask;
  513.   begin
  514.     if Len > 64 then
  515.       Len := 64;             {truncate filenames longer than 64 characters}
  516.     with Regs do begin
  517.       FileMask[Succ(Len)] := #0; {make FileMask an ASCIIZ string}
  518.       DS := Seg(FileMask);   {DS:DX points to the ASCIIZ string}
  519.       DX := Ofs(FileMask[1]);
  520.       AX := $0102;           {cancel print file}
  521.       Intr($2F, Regs);       {print spool control interrupt}
  522.     end;
  523.   end;
  524.  
  525.   procedure CancelAllPrintFiles;
  526.     {-Cancels all files in the print queue}
  527.   begin
  528.     Regs.AX := $0103;        {cancel all files function}
  529.     Intr($2F, Regs);         {print spool control interrupt}
  530.   end;
  531.  
  532.   function GetPrintStatus(var QPtr : Pointer) : Byte;
  533.     {-Halts printing, returns current error status, puts pointer to the filename
  534.       queue in the QPtr variable. Filenames in the queue are 64-byte ASCIIZ
  535.       strings. The end of the queue is marked by a name starting with a null.}
  536.   begin
  537.     with Regs do begin
  538.       AX := $0104;           {access print queue function}
  539.       Intr($2F, Regs);       {print spool control interrupt}
  540.       {check carry flag}
  541.       if Odd(Flags) then begin
  542.         {carry set, return code in AL}
  543.         QPtr := nil;
  544.         GetPrintStatus := AL;
  545.       end
  546.       else begin
  547.         {DS:SI points to the queue}
  548.         QPtr := Ptr(DS, SI);
  549.         GetPrintStatus := 0;
  550.       end;
  551.     end;
  552.   end;
  553.  
  554.   procedure EndPrintStatus;
  555.     {-Releases the spooler from the GetPrintStatus procedure.}
  556.   begin
  557.     Regs.AX := $0105;        {unfreeze queue function}
  558.     Intr($2F, Regs);         {print spool control interrupt}
  559.   end;
  560.  
  561.   function GetEnvironmentString(SearchString : string) : string;
  562.     {-Return a string from the environment}
  563.   type
  564.     Env = array[0..32767] of Char;
  565.   var
  566.     EPtr : ^Env;
  567.     EStr : string;
  568.     EStrLen : Byte absolute EStr;
  569.     Done : Boolean;
  570.     SearchLen : Byte absolute SearchString;
  571.     I : Word;
  572.   begin
  573.     GetEnvironmentString := '';
  574.     if SearchString = '' then
  575.       Exit;
  576.  
  577.     {force upper case}
  578.     for I := 1 to SearchLen do
  579.       SearchString[I] := Upcase(SearchString[I]);
  580.  
  581.     EPtr := Ptr(MemW[PrefixSeg:$2C], 0);
  582.     I := 0;
  583.     if SearchString[SearchLen] <> '=' then
  584.       SearchString := SearchString+'=';
  585.     Done := False;
  586.     EStrLen := 0;
  587.     repeat
  588.       if EPtr^[I] = #0 then begin
  589.         if EPtr^[Succ(I)] = #0 then begin
  590.           Done := True;
  591.           if SearchString = '==' then begin
  592.             EStrLen := 0;
  593.             Inc(I, 4);
  594.             while EPtr^[I] <> #0 do begin
  595.               Inc(EStrLen);
  596.               EStr[EStrLen] := EPtr^[I];
  597.               Inc(I);
  598.             end;
  599.             GetEnvironmentString := EStr;
  600.           end;
  601.         end;
  602.         if Copy(EStr, 1, SearchLen) = SearchString then begin
  603.           GetEnvironmentString := Copy(EStr, Succ(SearchLen), 255);
  604.           Done := True;
  605.         end;
  606.         EStrLen := 0;
  607.       end
  608.       else begin
  609.         Inc(EStrLen);
  610.         EStr[EStrLen] := EPtr^[I];
  611.       end;
  612.       Inc(I);
  613.     until Done;
  614.   end;
  615.  
  616.   function EndOfHeap : Pointer;
  617.     {-Returns a pointer to the end of the free list}
  618.   var
  619.     FreeSegOfs : SegOfs absolute FreePtr;
  620.   begin
  621.     if FreeSegOfs.O = 0 then
  622.       {the free list is empty, add $1000 to the segment}
  623.       EndOfHeap := Ptr(FreeSegOfs.S+$1000, 0)
  624.     else
  625.       EndOfHeap := Ptr(FreeSegOfs.S+(FreeSegOfs.O shr 4), 0);
  626.   end;
  627.  
  628.   function PtrDiff(H, L : Pointer) : LongInt;
  629.     {-Return the number of bytes between H^ and L^. H is the higher address}
  630.   var
  631.     High : SegOfs absolute H;
  632.     Low : SegOfs absolute L;
  633.   begin
  634.     PtrDiff := (LongInt(High.S) shl 4+High.O)-(LongInt(Low.S) shl 4+Low.O);
  635.   end;
  636.  
  637.   function SetBlock(var Paragraphs : Word) : Boolean;
  638.     {-Change size of DOS memory block allocated to this program}
  639.   begin
  640.     with Regs do begin
  641.       AH := $4A;
  642.       ES := PrefixSeg;
  643.       BX := Paragraphs;
  644.       MsDos(Regs);
  645.       Paragraphs := BX;
  646.       SetBlock := not Odd(Flags);
  647.     end;
  648.   end;
  649.  
  650. {$IFNDEF Ver40}
  651.   function UsingEmulator : Boolean;
  652.     {-Return True if floating point emulator in use}
  653.   type
  654.     Array3 = array[1..3] of Char;
  655.   const
  656.     EmuSignature : Array3 = 'emu';
  657.   var
  658.     A3P : ^Array3;
  659.   begin
  660.     A3P := Ptr(SSeg, $E0);
  661.     {using emulator if Test8087 is 0 and emulator's signature is found in SS}
  662.     UsingEmulator := (Test8087 = 0) and (A3P^ = EmuSignature);
  663.   end;
  664. {$ENDIF}
  665.  
  666.   function ExecDos(Command : string; UseSecond : Boolean; UserRoutine : Pointer) : Integer;
  667.     {-Execute any DOS command. Call with Command = '' for a new shell. If
  668.       UseSecond is false, Command must be the full pathname of a program to be
  669.       executed. UserRoutine is the address of a routine to display status,
  670.       save/restore the screen, etc., or a nil pointer.}
  671.  
  672.     procedure CallUserRoutine(ActionCode : ActionCodeType; Param : Word);
  673.       {-Call UserRoutine with an action code}
  674.     inline(
  675.       $FF/$5E/<UserRoutine); {call far dword ptr [bp+<UserRoutine]}
  676.  
  677.   label
  678.     ExitPoint;
  679.   var
  680.     PathName,
  681.     CommandTail : string[127];
  682.     OurInt23,
  683.     OurInt24,
  684.     OldEndOfHeap,
  685.     NewEndOfHeap,
  686.     TopOfHeap : Pointer;
  687.     BlankPos,
  688.     Allocated,
  689.     SizeOfFreeList,
  690.     ParasToKeep,
  691.     ParasWeHave,
  692.     ParasForDos : Word;
  693.     {$IFDEF Ver40}
  694.     UsingEmulator : Boolean;
  695.     {$ENDIF}
  696.   begin
  697.     {Calculate number of bytes to save}
  698.     TopOfHeap := Ptr(SegOfs(FreePtr).S+$1000, 0);
  699.     SizeOfFreeList := PtrDiff(TopOfHeap, EndOfHeap);
  700.  
  701.     {If enough space available, use stack to store the free list}
  702.     {$IFDEF Ver40}
  703.     UsingEmulator := False;
  704.     {$ENDIF}
  705.     if (not UsingEmulator) and
  706.        (LongInt(SizeOfFreeList)+StackSafetyMargin < LongInt(SPtr)) then begin
  707.       NewEndOfHeap := Ptr(SSeg, 0);
  708.       Allocated := 0;
  709.     end
  710.     else begin
  711.       {Check for sufficient memory}
  712.       if MaxAvail < LongInt(SizeOfFreeList) then begin
  713.         {Insufficient memory to store free list}
  714.         ExecDos := -1;
  715.         Exit;
  716.       end;
  717.  
  718.       {Allocate memory for a copy of free list}
  719.       Allocated := SizeOfFreeList;
  720.       if Allocated > 0 then
  721.         GetMem(NewEndOfHeap, Allocated);
  722.  
  723.       {Recalculate the size of the free list}
  724.       SizeOfFreeList := Word(PtrDiff(TopOfHeap, EndOfHeap));
  725.     end;
  726.  
  727.     {Save the current pointer to the end of the free list}
  728.     OldEndOfHeap := EndOfHeap;
  729.  
  730.     {Current DOS memory allocation read from memory control block}
  731.     ParasWeHave := MemW[Pred(PrefixSeg):3];
  732.  
  733.     {Calculate amount of memory to give up}
  734.     ParasForDos := Pred(PtrDiff(TopOfHeap, HeapPtr) shr 4);
  735.  
  736.     {Calculate amount of memory to keep while in shell}
  737.     ParasToKeep := ParasWeHave-ParasForDos;
  738.  
  739.     {See if enough memory to run DOS}
  740.     if (ParasForDos > 0) and (ParasForDos < (MinSpaceForDos shr 4)) then begin
  741.       ExecDos := -4;
  742.       goto ExitPoint;
  743.     end;
  744.  
  745.     {Deallocate memory for DOS}
  746.     if not SetBlock(ParasToKeep) then begin
  747.       ExecDos := -2;
  748.       goto ExitPoint;
  749.     end;
  750.  
  751.     {get parameters for Execute}
  752.     if Command = '' then
  753.       UseSecond := True;
  754.     if not UseSecond {command processor} then begin
  755.       {Command is assumed to be a full pathname for a program}
  756.       BlankPos := Pos(' ', Command);
  757.       if BlankPos = 0 then begin
  758.         PathName := Command;
  759.         CommandTail := '';
  760.       end
  761.       else begin
  762.         CommandTail := Copy(Command, BlankPos, Length(Command));
  763.         PathName := Copy(Command, 1, Pred(BlankPos));
  764.       end;
  765.     end
  766.     else begin
  767.       {Pathname is the full pathname for COMMAND.COM}
  768.       PathName := GetEnvironmentString('COMSPEC');
  769.  
  770.       {if Command is empty, we're doing a shell}
  771.       if Command = '' then
  772.         CommandTail := ''
  773.       else
  774.         {we're asking COMMAND.COM to execute the command}
  775.         CommandTail := '/C '+Command;
  776.     end;
  777.  
  778.     {Let user routine store and clear the physical screen}
  779.     if UserRoutine <> nil then
  780.       CallUserRoutine(ExecSaveScreen, 0);
  781.  
  782.     {let user routine show status info if entering DOS shell}
  783.     if (Command = '') and (UserRoutine <> nil) then
  784.       {Pass user routine the approximate memory available in KB}
  785.       CallUserRoutine(ExecShowMemory, (ParasForDos-240) shr 6);
  786.  
  787.     {Copy the free list to a safe location}
  788.     Move(OldEndOfHeap^, NewEndOfHeap^, SizeOfFreeList);
  789.  
  790.     {$IFDEF Ver40}
  791.       {save our INT 23 and 24 vectors and put old ones back}
  792.       GetIntVec($23, OurInt23);
  793.       GetIntVec($24, OurInt24);
  794.       SetIntVec($23, SaveInt23);
  795.       SetIntVec($24, SaveInt24);
  796.       {$ELSE}
  797.       SwapVectors;
  798.     {$ENDIF}
  799.  
  800.     {Call Turbo's EXEC function}
  801.     Exec(PathName, CommandTail);
  802.  
  803.     {$IFDEF Ver40}
  804.       {restore our INT 23 and 24 vectors}
  805.       SetIntVec($23, OurInt23);
  806.       SetIntVec($24, OurInt24);
  807.     {$ELSE}
  808.       SwapVectors;
  809.     {$ENDIF}
  810.  
  811.     {Reallocate memory from DOS}
  812.     if not SetBlock(ParasWeHave) then begin
  813.       ExecDos := -3;
  814.       goto ExitPoint;
  815.     end;
  816.  
  817.     {Put free list back where it was}
  818.     Move(NewEndOfHeap^, OldEndOfHeap^, SizeOfFreeList);
  819.  
  820.     {if not in shell , let user routine allow time to see result}
  821.     if ((Command <> '') or (DosError <> 0)) and (UserRoutine <> nil) then
  822.       CallUserRoutine(ExecPauseAfterRun, 0);
  823.  
  824.     {give user routine a chance to restore the screen}
  825.     if UserRoutine <> nil then
  826.       CallUserRoutine(ExecRestoreScreen, 0);
  827.  
  828.     {If we get to here, our function result is in DosError}
  829.     ExecDos := DosError;
  830.  
  831. ExitPoint:
  832.     {Deallocate any dynamic memory used}
  833.     if Allocated <> 0 then
  834.       FreeMem(NewEndOfHeap, Allocated);
  835.   end;
  836.  
  837.   function UserDefinedIoResult : Word;
  838.     {-Calls user-defined I/O checking routine}
  839.   inline(
  840.     $FF/$1E/>IoResultPtr);   {CALL DWORD PTR [IoResultPtr]}
  841.  
  842.   function IoResult : Word;
  843.     {-Returns I/O result if IoResultPtr is nil, else the code returned by
  844.       the user-specified I/O error checking routine.}
  845.   begin
  846.     if IoResultPtr = nil then
  847.       IoResult := System.IoResult
  848.     else
  849.       IoResult := UserDefinedIoResult;
  850.   end;
  851.  
  852.   function DosBlockWrite(H : Word; var Src; N : Word) : Word;
  853.   {-Calls DOS's BlockWrite routine. Returns 0 if successful, else the DOS
  854.     error code.}
  855.   begin
  856.     with Regs do begin
  857.       AH := $40;             {write to file}
  858.       BX := H;               {file handle}
  859.       CX := N;               {Number of bytes to write}
  860.       DS := Seg(Src);        {DS:DX points to buffer}
  861.       DX := Ofs(Src);
  862.       MsDos(Regs);           {returns bytes written in AX}
  863.  
  864.       {check carry flag, also the number of bytes written}
  865.       if Odd(Flags) or (AX <> N) then
  866.         DosBlockWrite := AX
  867.       else
  868.         DosBlockWrite := 0;
  869.     end;
  870.   end;
  871.  
  872.   function TextSeek(var F : Text; Target : LongInt) : Boolean;
  873.     {-Do a Seek for a text file opened for input. Returns False in case of I/O
  874.       error.}
  875.   var
  876.     T : LongRec absolute Target;
  877.     Pos : LongInt;
  878.   begin
  879.     with Regs, FIB(F) do begin
  880.       {assume failure}
  881.       TextSeek := False;
  882.  
  883.       {check for file opened for input}
  884.       if Mode <> FMInput then
  885.         Exit;
  886.  
  887.       {get current position of the file pointer}
  888.       AX := $4201;           {move file pointer function}
  889.       BX := Handle;          {file handle}
  890.       CX := 0;               {if CX and DX are both 0, call returns the..}
  891.       DX := 0;               {current file pointer in DX:AX}
  892.       MsDos(Regs);
  893.  
  894.       {check for I/O error}
  895.       if Odd(Flags) then
  896.         Exit;
  897.  
  898.       {calculate current position for the start of the buffer}
  899.       LongRec(Pos).HighWord := DX;
  900.       LongRec(Pos).LowWord := AX;
  901.       Dec(Pos, BufEnd);
  902.  
  903.       {see if the Target is within the buffer}
  904.       Pos := Target-Pos;
  905.       if (Pos >= 0) and (Pos < BufEnd) then
  906.         {it is--just move the buffer pointer}
  907.         BufPos := Pos
  908.       else begin
  909.         {have DOS seek to the Target-ed offset}
  910.         AX := $4200;         {move file pointer function}
  911.         BX := Handle;        {file handle}
  912.         CX := T.HighWord;    {CX has high word of Target offset}
  913.         DX := T.LowWord;     {DX has low word}
  914.         MsDos(Regs);
  915.  
  916.         {check for I/O error}
  917.         if Odd(Flags) then
  918.           Exit;
  919.  
  920.         {tell Turbo its buffer is empty}
  921.         BufEnd := 0;
  922.         BufPos := 0;
  923.       end;
  924.     end;
  925.  
  926.     {if we get to here we succeeded}
  927.     TextSeek := True;
  928.   end;
  929.  
  930.   function TextFileSize(var F : Text) : LongInt;
  931.     {-Return the size of text file F. Returns -1 in case of I/O error.}
  932.   var
  933.     OldHi, OldLow : Integer;
  934.   begin
  935.     with Regs, FIB(F) do begin
  936.       {check for open file}
  937.       if Mode = FMClosed then begin
  938.         TextFileSize := -1;
  939.         Exit;
  940.       end;
  941.  
  942.       {get current position of the file pointer}
  943.       AX := $4201;           {move file pointer function}
  944.       BX := Handle;          {file handle}
  945.       CX := 0;               {if CX and DX are both 0, call returns the..}
  946.       DX := 0;               {current file pointer in DX:AX}
  947.       MsDos(Regs);
  948.  
  949.       {check for I/O error}
  950.       if Odd(Flags) then begin
  951.         TextFileSize := -1;
  952.         Exit;
  953.       end;
  954.  
  955.       {save current position of the file pointer}
  956.       OldHi := DX;
  957.       OldLow := AX;
  958.  
  959.       {have DOS move to end-of-file}
  960.       AX := $4202;           {move file pointer function}
  961.       BX := Handle;          {file handle}
  962.       CX := 0;               {if CX and DX are both 0, call returns the...}
  963.       DX := 0;               {current file pointer in DX:AX}
  964.       MsDos(Regs);           {call DOS}
  965.  
  966.       {check for I/O error}
  967.       if Odd(Flags) then begin
  968.         TextFileSize := -1;
  969.         Exit;
  970.       end;
  971.  
  972.       {calculate the size}
  973.       TextFileSize := LongInt(DX) shl 16+AX;
  974.  
  975.       {reset the old position of the file pointer}
  976.       AX := $4200;           {move file pointer function}
  977.       BX := Handle;          {file handle}
  978.       CX := OldHi;           {high word of old position}
  979.       DX := OldLow;          {low word of old position}
  980.       MsDos(Regs);           {call DOS}
  981.  
  982.       {check for I/O error}
  983.       if Odd(Flags) then
  984.         TextFileSize := -1;
  985.     end;
  986.   end;
  987.  
  988.   function TextPos(var F : Text) : LongInt;
  989.     {-Return the current position of the logical file pointer (that is,
  990.       the position of the physical file pointer, adjusted to account for
  991.       buffering). Returns -1 in case of I/O error.}
  992.   var
  993.     Position : LongInt;
  994.   begin
  995.     with Regs, FIB(F) do begin
  996.       {check for open file}
  997.       if Mode = FMClosed then begin
  998.         TextPos := -1;
  999.         Exit;
  1000.       end;
  1001.  
  1002.       {get current position of the physical file pointer}
  1003.       AX := $4201;           {move file pointer function}
  1004.       BX := Handle;          {file handle}
  1005.       CX := 0;               {if CX and DX are both 0, call returns the...}
  1006.       DX := 0;               {current file pointer in DX:AX}
  1007.       MsDos(Regs);           {call DOS}
  1008.  
  1009.       {check for I/O error}
  1010.       if Odd(Flags) then begin
  1011.         TextPos := -1;
  1012.         Exit;
  1013.       end;
  1014.  
  1015.       {calculate the position of the logical file pointer}
  1016.       LongRec(Position).HighWord := DX;
  1017.       LongRec(Position).LowWord := AX;
  1018.       if Mode = FMOutput then
  1019.         {writing}
  1020.         Inc(Position, BufPos)
  1021.       else
  1022.         {reading}
  1023.         if BufEnd <> 0 then
  1024.           Dec(Position, BufEnd-BufPos);
  1025.  
  1026.       {return the calculated position}
  1027.       TextPos := Position;
  1028.     end;
  1029.   end;
  1030.  
  1031.   function TextFlush(var F : Text) : Boolean;
  1032.     {-Flush the buffer(s) for a text file. Returns False in case of I/O error.}
  1033.   var
  1034.     Position : LongInt;
  1035.     P : LongRec absolute Position;
  1036.     Code : Word;
  1037.   begin
  1038.     with Regs, FIB(F) do begin
  1039.       {assume failure}
  1040.       TextFlush := False;
  1041.  
  1042.       {check for open file}
  1043.       if Mode = FMClosed then
  1044.         Exit;
  1045.  
  1046.       {see if file is opened for reading or writing}
  1047.       if Mode = FMInput then begin
  1048.         {get current position of the logical file pointer}
  1049.         Position := TextPos(F);
  1050.  
  1051.         {exit in case of I/O error}
  1052.         if Position = -1 then
  1053.           Exit;
  1054.  
  1055.         {set the new position of the physical file pointer}
  1056.         AX := $4200;         {move file pointer function}
  1057.         BX := Handle;        {file handle}
  1058.         CX := P.HighWord;    {CX has high word of offset}
  1059.         DX := P.LowWord;     {DX has low word}
  1060.         MsDos(Regs);         {call DOS}
  1061.  
  1062.         {check for I/O error}
  1063.         if Odd(Flags) then
  1064.           Exit;
  1065.       end
  1066.       else begin
  1067.         {write the current contents of the buffer, if any}
  1068.         if BufPos <> 0 then begin
  1069.           Code := DosBlockWrite(Handle, BufPtr^, BufPos);
  1070.           if Code <> 0 then
  1071.             Exit;
  1072.         end;
  1073.  
  1074.         {dupe the file handle}
  1075.         AH := $45;
  1076.         BX := Handle;
  1077.         MsDos(Regs);
  1078.         if Odd(Flags) then
  1079.           Exit;
  1080.  
  1081.         {close the duped file}
  1082.         BX := AX;
  1083.         AH := $3E;
  1084.         MsDos(Regs);
  1085.         if Odd(Flags) then
  1086.           Exit;
  1087.       end;
  1088.  
  1089.       {tell Turbo its buffer is empty}
  1090.       BufEnd := 0;
  1091.       BufPos := 0;
  1092.     end;
  1093.  
  1094.     {if we get to here we succeeded}
  1095.     TextFlush := True;
  1096.   end;
  1097.  
  1098.   function OpenStdDev(var F : Text; StdHandle : Word) : Boolean;
  1099.     {-Assign the text file to the specified standard DOS device}
  1100.   begin
  1101.     OpenStdDev := False;
  1102.     case StdHandle of
  1103.       StdInHandle,
  1104.       StdOutHandle,
  1105.       StdErrHandle,
  1106.       StdPrnHandle :
  1107.         begin
  1108.           {Initialize the file variable}
  1109.           Assign(F, '');
  1110.           Rewrite(F);
  1111.           if IoResult = 0 then begin
  1112.             FIB(F).Handle := StdHandle;
  1113.             if StdHandle = StdErrHandle then
  1114.               FIB(F).BufSize := 1;
  1115.             OpenStdDev := True;
  1116.           end;
  1117.         end;
  1118.     end;
  1119.   end;
  1120.  
  1121.   function HandleIsConsole(Handle : Word) : Boolean;
  1122.     {-Return true if handle is the console device (input or output)}
  1123.   begin
  1124.     with Regs do begin
  1125.       AX := $4400;
  1126.       BX := Handle;
  1127.       MsDos(Regs);
  1128.       if (DX and $80) = 0 then
  1129.         HandleIsConsole := False
  1130.       else
  1131.         HandleIsConsole := (DX and $02 <> 0) or (DX and $01 <> 0);
  1132.     end;
  1133.   end;
  1134.  
  1135.   procedure SetRawMode(var F : Text; On : Boolean);
  1136.     {-Set "raw" mode on or off for the specified text file (must be a device)}
  1137.   var
  1138.     FH : Word absolute F; {F's file handle}
  1139.     FMod : Word;
  1140.   begin
  1141.     {check for open file}
  1142.     FMod := FIB(F).Mode;
  1143.     if (FMod < FMInput) or (FMod > fmInOut) then begin
  1144.       {Turbo's file not found error code}
  1145.       DosError := 103;
  1146.       Exit;
  1147.     end;
  1148.  
  1149.     DosError := 0;
  1150.     with Regs do begin
  1151.       AX := $4400;           {Get device information}
  1152.       BX := FH;
  1153.       MsDos(Regs);           {returns device info in DX}
  1154.  
  1155.       if not Odd(Flags) then begin
  1156.         {check bit 7 for device flag}
  1157.         if DL and $80 = 0 then
  1158.           Exit;
  1159.  
  1160.         {clear unwanted bits}
  1161.         DX := DX and $00AF;
  1162.  
  1163.         {select raw/cooked mode}
  1164.         if On then
  1165.           {set bit 5 of DX}
  1166.           DL := DL or $20
  1167.         else
  1168.           {clear bit 5 of DX}
  1169.           DL := DL and $DF;
  1170.  
  1171.         AX := $4401;           {Set device information}
  1172.         BX := FH;              {BX has file handle}
  1173.         MsDos(Regs);
  1174.       end;
  1175.  
  1176.       if Odd(Flags) then
  1177.         DosError := AX
  1178.       else
  1179.         DosError := 0;
  1180.     end;
  1181.   end;
  1182.  
  1183.   function FileHandlesOpen(CountDevices : Boolean) : Byte;
  1184.     {-Return the number of open files owned by a program}
  1185.   type
  1186.     HandleTable = array[0..19] of Byte;
  1187.   var
  1188.     HandlesPtr : ^HandleTable;
  1189.     I, N : Byte;
  1190.   begin
  1191.     {file handles table starts at PrefixSeg:$18}
  1192.     HandlesPtr := Ptr(PrefixSeg, $18);
  1193.     N := 0;
  1194.     for I := 0 to 19 do
  1195.       if HandlesPtr^[I] <> $FF then
  1196.         case I of
  1197.           0..4 : Inc(N, Ord(CountDevices));
  1198.         else Inc(N);
  1199.         end;
  1200.     FileHandlesOpen := N;
  1201.   end;
  1202.  
  1203.   function FileHandlesLeft : Byte;
  1204.     {-Return the number of available file handles}
  1205.   const
  1206.     MaxFiles = 20;
  1207.   var
  1208.     Files : array[1..MaxFiles] of file;
  1209.     I, N : Byte;
  1210.     OK : Boolean;
  1211.   begin
  1212.     N := 0;
  1213.     repeat
  1214.       {try opening the N+1'th file}
  1215.       Assign(Files[N+1], 'NUL');
  1216.       Reset(Files[N+1]);
  1217.       OK := IoResult = 0;
  1218.       Inc(N, Ord(OK));
  1219.     until (N = MaxFiles) or not OK;
  1220.  
  1221.     for I := 1 to N do begin
  1222.       {close each of the files that we opened}
  1223.       Close(Files[I]);
  1224.       OK := (IoResult = 0);
  1225.     end;
  1226.  
  1227.     FileHandlesLeft := N;
  1228.   end;
  1229.  
  1230.   function ExistFile(FName : string) : Boolean;
  1231.     {-Return true if file is found}
  1232.   var
  1233.     Regs : Registers;
  1234.     FLen : Byte absolute FName;
  1235.   begin
  1236.     {check for empty string}
  1237.     if Length(FName) = 0 then
  1238.       ExistFile := False
  1239.     else with Regs do begin
  1240.       Inc(FLen);
  1241.       FName[FLen] := #0;
  1242.       AX := $4300;           {get file attribute}
  1243.       DS := Seg(FName);
  1244.       DX := Ofs(FName[1]);
  1245.       MsDos(Regs);
  1246.       ExistFile := (not Odd(Flags)) and (IoResult = 0);
  1247.     end;
  1248.   end;
  1249.  
  1250.   function ExistOnPath(FName : string; var FullName : string) : Boolean;
  1251.    {-Return true if fname is found in
  1252.       a) current directory (returns just name, no path)
  1253.       b) program's directory (DOS 3.X only)
  1254.       c) any DOS path directory
  1255.     and return path name to file}
  1256.   type
  1257.     Environment = array[0..32766] of Char;
  1258.   const
  1259.     Null : Char = #0;
  1260.     DoubleNull : string[2] = #0#0;
  1261.     PathStr : string[5] = 'PATH=';
  1262.   var
  1263.     E : ^Environment;
  1264.     Elast : Word;
  1265.     Epos : Word;
  1266.     Fpos : Word;
  1267.     Found : Boolean;
  1268.   begin
  1269.     {Assume success}
  1270.     ExistOnPath := True;
  1271.  
  1272.     {Check current directory -- If you need the complete path name,
  1273.      call TPString.FullPathname after calling ExistOnPath}
  1274.     FullName := FName;
  1275.     if ExistFile(FullName) then
  1276.       Exit;
  1277.  
  1278.     {Get a pointer to the DOS environment}
  1279.     E := Ptr(MemW[PrefixSeg:$2C], 0);
  1280.  
  1281.     {Find the end of the environment}
  1282.     Elast := Search(E^[0], 32767, DoubleNull[1], 2);
  1283.     if Elast = $FFFF then begin
  1284.       {Something is wrong}
  1285.       ExistOnPath := False;
  1286.       Exit;
  1287.     end;
  1288.  
  1289.     {If DOS 3 or higher, check the directory where the program was found}
  1290.     if DOSVersion >= $300 then begin
  1291.       {Skip over the doublenull and a word count}
  1292.       Epos := Elast+4;
  1293.       {Find the next null}
  1294.       Fpos := Search(E^[Epos], 100, Null, 1);
  1295.       if Fpos <> $FFFF then begin
  1296.         {Move from the environment into the return string}
  1297.         FullName[0] := Chr(Fpos);
  1298.         Move(E^[Epos], FullName[1], Fpos);
  1299.         FullName := AddBackSlash(JustPathname(FullName))+FName;
  1300.         if ExistFile(FullName) then
  1301.           Exit;
  1302.       end;
  1303.     end;
  1304.  
  1305.     {Check the path}
  1306.     Found := False;
  1307.     Epos := 0;
  1308.     repeat
  1309.       Fpos := Search(E^[Epos], Elast-Epos, PathStr[1], Length(PathStr));
  1310.       if Fpos <> $FFFF then begin
  1311.         {PATH= was found}
  1312.         Inc(Epos, Fpos);
  1313.         Found := (Epos = 0) or (E^[Pred(Epos)] = Null);
  1314.         if not(Found) then
  1315.           {Something like DPATH= was found}
  1316.           Inc(Epos);
  1317.       end;
  1318.     until (Fpos = $FFFF) or Found;
  1319.  
  1320.     if Found then begin
  1321.       {True PATH= was found, skip over the PATH= part}
  1322.       Inc(Epos, Length(PathStr));
  1323.  
  1324.       {Scan each item in the path}
  1325.       repeat
  1326.  
  1327.         {Find the termination of the current path entry}
  1328.         Fpos := Epos;
  1329.         while (E^[Fpos] <> ';') and (E^[Fpos] <> Null) do
  1330.           Inc(Fpos);
  1331.  
  1332.         if Fpos > Epos then begin
  1333.           {A path entry found}
  1334.           FullName[0] := Char(Fpos-Epos);
  1335.           Move(E^[Epos], FullName[1], Fpos-Epos);
  1336.           FullName := AddBackSlash(FullName)+FName;
  1337.           if ExistFile(FullName) then
  1338.             Exit;
  1339.         end;
  1340.  
  1341.         {Prepare to look at next item}
  1342.         Epos := Succ(Fpos);
  1343.  
  1344.       until E^[Fpos] = Null;
  1345.     end;
  1346.  
  1347.     {Not found, even on the path}
  1348.     ExistOnPath := False;
  1349.     FullName := FName;
  1350.   end;
  1351.  
  1352.   function TimeMs : LongInt;
  1353.     {-Return time of day in milliseconds since midnight}
  1354.   begin
  1355.     with Regs do begin
  1356.       AH := $2C;
  1357.       MsDos(Regs);
  1358.       TimeMs := 1000*(LongInt(DH)+60*(LongInt(CL)+60*LongInt(CH)))+10*LongInt(DL);
  1359.     end;
  1360.   end;
  1361.  
  1362. begin
  1363.   {No user-defined ioresult routine yet}
  1364.   IoResultPtr := nil;
  1365. end.
  1366.