{**********************************************************************
 ***                                                                ***
 ***                      DOS FUNCTIONS                             ***
 ***                                                                ***
 **********************************************************************}
{the various xxxfunc units, ie dosfunc and winfunc, are meant to provide
similar functions for the different running environments, in a similar
way that tuiapp provides a text alternative to winapp}
{$I compflgs}  {for single user flag}
{$O-}  {This unit does things to the overlay buffer and
						should therefore not be overlaid!}
unit DosUtils;

INTERFACE

uses
	objects, {for fnamestr}
	windos, dattime;

const
	{run types}
	rnNone = $00;
	rnCheck = $01;

	{extra sharing file-open modes - see p626 of the Dos Programmers Ref}
	fmShareReadWrite 	= $42; {0011 1101 0100 0010 - full access permitted, read/write}
	fmShareReadOnly 	= $20; {..        0010 0000 - write access denied, read only - for help/etc resource files}

	{$IFDEF SingleUser}
		fmDefault = 2;
	{$ELSE}
		fmDefault = fmShareReadWrite;
	{$ENDIF}

	{... and associated stxxx constants for initialising streams}
	stOpenShare 		= $3D00 + fmShareReadWRite;
	stCreateShare 	= $3C00 + fmShareReadWrite;
	stShareReadOnly = $3D00 + fmShareReadOnly;

{Running DOS Programs/Shells}
function Run(PRog, Parameters : string; rnType : word; Message : string) : word;  {Call a program with parameters}
function DOSCommand(s : string; rnType : word) : word;            {Call COMMAND.COM}
procedure DosShell;

{DOS functions}
function 	GetDiskInfo(Drive: Char; var SerialNo : longint; var VolumeLabel, FileSystem : PChar): Boolean;
function 	IsShareLoaded : boolean;
procedure SetNumFileHandles(Num : word);

procedure Gasp; {give the o/s a chance to operate - eg OS/2...?}

{File Maintenance}
procedure DeleteFile(FileName : FNameStr);
function FileExists(const FileName : FNameStr) : boolean;
procedure RenameFile(const FromName, ToName : FNameStr);
function CopyFile(const FromName, ToName : FNameStr) : integer;
procedure CopyStream(const FromName, ToName : FNameStr);

{File functions}
function 	GetFileSize(FileName : FNameStr) : longint;
procedure GetFileDate(FileName : string; var Date : TDate; var Time : TTime);


{Path funcs}
procedure SetCurrentDirectory(DirName : string);
function 	GetCurrentDirectory : string;
procedure CheckPath(var S : string);
function	ValidPath(var S : string) : boolean;

{path/filename processing}
function  ChkEndSlash(S : string) : string;
function GetFileName(S : string) : string; {returns name from drive:path\name}
function GetJustFileName(S : string) : string; {returns name w/o ext from drive:path\name}
function GetPath(S : string) : string; {returns path from drive:path\name}
function GetExt(S : string) : string; {returns path from drive:path\name}
function EmptyFileName(const FIleName : FNameStr) : boolean;
procedure SplitProgParams(const S : string; var Prog, Params : string);

var
	LastDOSFuncResult : word;
	DosVer : single;

IMPLEMENTATION

uses
			{$IFDEF Windows}
				winmsgs, winprocs, wintypes,
			{$ELSE}
				dos, crt, memory, drivers, tuimsgs, app,
				{$IFDEF DPMI}	winprocs,  {$ENDIF} {for dpmi interrupts}
			{$ENDIF}
			help,
			dbg,
			global,
			strings, {coping with nul-terminated strings that dos interrupts use}
			minilib,
			messtext;


const
	XCopyExitMess : array[1..5] of pchar
													= ('No Files Found',
															'Ctrl-C Pressed',
															'',
															'Low memory?',
															'Disk write error');
	BackupRestoreExitMess : array[1..4] of PChar
													= ('No Files Found',
															'File-share conflicts',
															'Ctrl-C Pressed',
															'Error (DOS Ver?)');

{*****************************************************************
 ***                                                           ***
 ***               DOS COMMANDS/EXECS                          ***
 ***                                                           ***
 *****************************************************************}


{*****************************************
 **             DOS SHELL              ***
 *****************************************}
{User shell out to Dos}
procedure DosShell;
begin
	DOSCOmmand('',rnNone);
end;

{*****************************
 ***     RUN COMMAND.COM   ***
 *****************************}
{Runs program (or just command.com) through command.com}
function DOSCommand(s : string; rnType : word) : word;
var ComSpec : string;
		Message : string;

begin
	{$IFNDEF WIndows}
	ComSpec := GetEnv('COMSPEC');
	if ComSpec = '' then ComSpec := 'C:\COMMAND.COM';

	if S<>'' then Message := 'Executing '+S+'...' else Message := '';

	if S <>'' then S := ' /C '+S;         {If a command asked for, put /C execute on}
	DOSCommand := Run(ComSpec, S, rnType, Message);          {Execute DOS COMMAND with command in S}
	{$ENDIF}
end;


{*****************************
 ***     RUN PROGRAM       ***
 *****************************}
{Runs program directly - using Exec procedure}
function Run(Prog, Parameters : string; rnType : word; Message : string) : word;
var
	DosExit : word;
	DosErr : word;
	BufferSize : longint;
	ErrS : string;
	SaveScreenMode : word;
	ProgCh : PChar; {for windows-format stuff}

begin
	if pos('.BAT',ucase(Prog))>0 then begin
		Run := DOSCommand(Prog+' '+Parameters, rnType);
		exit;
	end;

	{----- Release Available Memory ------}
{	RunAllTasks(LowMemoryTasks);{}

{$IFDEF MSDOS}
{	OvrClearBuf;
	BufferSize := OvrGetBuf;
	OvrSetBuf($A000);                       {CLear overlay buffer}
{$ENDIF}

	{$IFDEF Windows}
		ProgCh := StrFromPS(Prog+Parameters);
		DosErr := WinExec(ProgCh, SW_SHOW);
		StrDispose(ProgCh);
		Run := DosErr;
		if rnType = rnCheck then begin
			if DosErr<32 then ProgramWarning('WINEXEC ERROR '+N2Str(DosErr)+CRLF+Prog+' '+Parameters);
		end;

	{$ELSE}
		{---- Shut Down Environment ----------}
		SaveScreenMode := SCreenMode;
		DoneSysError;
		DoneEvents;
		DoneVideo;
		DoneDosMem;  {releases all cash buffers and minimises heap}
		SwapVectors;

		{---- Call DOS -----------------------}
		writeln(Message);{}

		Exec(Prog, Parameters);             {Execute program - needs complete segment to start}

		{Store DosError field}
		DosErr := DosError;									{Store 'cos it gets cleared}
		Run := DosErr; {return to caller}

		{---- Re-start Environment -----------}
		SwapVectors;
		InitDosMem;

		if (rnType and rnCheck)>0 then
			{let user check messages before the screen is overwritten by
			the application}
			if (DosErr<>2) and (DosErr<>8) and (DosErr<>3) then begin
				{Don't bother for file/path not found, or not enough memory, as
				these don't do anything}
				writeln('');
				writeln('Done. Please check messages and press any key');
				DoneBleep;
				repeat until Keypressed;
				while KeyPressed do ReadKey;
			end;

	{	ScreenMode := SaveScreenMode;	{doesn't work properly here - see setscreenmode below}
		InitVideo;
		InitEvents;
		InitSysError;
		{$IFDEF MSDOS}
		{	OvrSetBuf(BufferSize);{}
		{$ENDIF}

		{---- Redraw desktop -----------------}
		if (Application<> nil) then begin
			Application^.SetScreenMode(SaveScreenMode);{reset video/screen in case WP has changed}
			Application^.Redraw;
		end;

		{---- Check Execution OK -------------}

		if rnType = rnCheck then begin
			if (DosErr<>0) then ProgramWarning('DOS ERROR: '+IOError(DosErr)+#13#10
																	+Prog+' '+Parameters, hcDosErrorMsg);

			{Check dos exit code}
			DosExit := DosExitCode;             {Store 'cos I think it gets cleared}
			if (DosExit>0) then begin
				ErrS := 'DOS EXIT CODE $'+hex(DosExit)+' ';

				if pos('XCOPY',ucase(Prog))>0 then ErrS := ErrS + XCopyExitMess[DosExit]^;
				if (pos('BACKUP',ucase(Prog))>0)
					or (pos('RESTORE',ucase(Prog))>0) then ErrS := ErrS + BackupRestoreExitMess[DosExit]^;

				ProgramWarning(ErrS+#13#10+Prog+'  '+Parameters, hcDosErrorMsg);
				Run := DosExit;
			end;
		end;
	{$ENDIF} {if not windows}

end;

{*****************************************************************
 ***                                                           ***
 ***               INTERRUPTS                                  ***
 ***                                                           ***
 *****************************************************************}
const
	VideoInt        = $10;
	ClockInt        = $1C;
	TimerInt        = ClockInt;
	DOSInt          = $21;
	DPMIInt         = $31;
	KeyboardInt     = 9;
	MouseInt        = $33;

	GetRMIntVec     = $200;
	SetRMIntVec     = $201;
	GetFaultHandler = $202;
	SetFaultHandler = $203;

	SimIntr         = $300;
	CallFarRealProc = $301;
	CallIntRealProc = $302;
	AllocRMCallBack = $303;
	FreeRMCallBack  = $304;

	GetDiskSerialNo = $6900;

{************************************************
 ***       DPMI SIMULATED INTERRUPT           ***
 ************************************************}
type
{$IFDEF MSDOS}
	{--- Real mode ---}
	TREgisters = Registers;
{$ELSE}
	{-- Protected Mode ---}
	TREgisters = Registers;

	{for use in DPMI mode in simulating real interrupt}
	TDPMIRealRegs = record
		case Byte of
			0: (EDI, ESI, EBP, ERes, EBX, EDX, ECX, EAX: Longint;
					Flags, ES, DS, FS, GS, IP, CS, SP, SS: Word);
			1: (DI, DIH, SI, SIH, BP, BPH, EResL, EResH: Word;
					case Byte of
						0: (BX, BXH, DX, DXH, CX, CXH, AX, AXH: Word);
						1: (BL, BH, BLH, BHH, DL, DH, DLH, DHH,
								CL, CH, CLH, CHH, AL, AH, ALH, AHH: Byte));
	end; { TRealRegs }


function DPMIIntr(IntNo: Byte; var Regs: TDPMIRealRegs): Boolean; assembler;
asm
	mov ax, SimIntr
	mov bl, IntNo
	mov bh, 0
	mov cx, 0
	les di, Regs
	int DPMIInt
	mov ax, 1
	jnc @Exit
	mov ax, 0
@Exit:
end; { DPMISimIntr }
{$ENDIF}


{*****************************************************************
 ***                                                           ***
 ***                   DOS FUNCTIONS                           ***
 ***                                                           ***
 *****************************************************************}

{*********************************************************
 ***                  GASP!                            ***
 *********************************************************}
procedure GaspMultiplex;

var
{$IFDEF MSDOS}	Regs : TRegisters; {$ENDIF}
{$IFDEF DPMI}		Regs : TDPMIRealRegs; {$endif}

begin
	{-- Call DOS Multiplexer ---}
	{DPMI Mode Time Slice- It's for TSR's, I think OS/2 has a hook in there...}
	Regs.AX := 1680;

	{$IFDEF DPMI} 	DPMIIntr($2F, Regs); {}{$ENDIF}
	{$IFDEF MSDOS} 	Intr($2F, Regs);			{}{$ENDIF}
end;

procedure GaspCPUHalt;
begin
	{--- CPU Halt ----}
	asm
		{load DX,AX with millisecs}
		MOV	DX, 0
		MOV AX, 10
		STI
		HLT
		DB 035h,0CAh
	end;{}
end;

procedure Gasp;
begin {GaspMultiplex;{} end;

{******************************************************************
 ***                    GET DISK INFO                           ***
 ******************************************************************}
type
	{ DOS interrupt record structure for getting disk info}
	TInfoBuffer = record
		InfoLevel    : Word;
		SerialNumber : Longint;
		VolumeLabel  : array[1..11] of Char;
		FileSystem   : array[1..8]  of Char;
	end; { TInfoBuffer }

function GetDiskInfo(Drive: Char; var SerialNo : longint; var VolumeLabel, FileSystem : PChar): Boolean;
var
	InfoBuffer : TInfoBuffer;

{$IFDEF MSDOS}
	Regs : TRegisters;
{$ELSE}
	Regs : TDPMIRealRegs;
	Address: TlongWord;  {lo = selector, hi = segment}
	Success: Boolean;
	Result: Longint;
{$endif}

begin
{$ifdef MSDOS}
	with Regs do
	begin
		AX := GetDiskSerialNo;
		BX := Ord(Drive) - 64;
		DS := Seg(InfoBuffer);
		DX := Ofs(InfoBuffer);
	end;
	MsDos(Regs);
	GetDiskInfo := Regs.Flags and fCarry = 0;

{$else}

	{---- DPMI Mode -----}
	{puts aside some real memory, calls the DPMI interrupt which puts the
	info in the real memory bit, then copies the info to infobuffer}

	GetDiskInfo := False;
	{ Allocate some real mode memory }
	Result := GlobalDosAlloc(SizeOf(InfoBuffer));
	if Result = 0 then Exit;
	Address := TLongWord(Result);
	FillChar(Regs, SizeOf(Regs), 0);

	{call interrupt}
	with Regs do
	begin
		AX := GetDiskSerialNo;
		BX := Ord(Drive) - 64;
		DS := Address.Hi; {Segment}
		DX := 0;
	end;
	Success := DPMIIntr(DosInt, Regs);

	{ Save the returned data - address.lo = selector}
	Move(Ptr(Address.Lo, 0)^, InfoBuffer, SizeOf(InfoBuffer));
	{ Tidy up }
	GlobalDosFree(Address.Lo);
	if not Success then	Exit;
	GetDiskInfo := True;
{$endif}

	SerialNo := InfoBuffer.SerialNumber;
	VolumeLabel := StrNew(@InfoBuffer.VolumeLabel[1]);
	FileSystem := StrNew(@InfoBuffer.FileSystem[1]);
end; { GetDiskInfo }


{******************************************************************
 ***                    CHECK FOR SHARE.EXE                     ***
 ******************************************************************}
{From 1.18 of the Borland Pascal Problem solver}
function IsShareLoaded : boolean;
var
	F : file of word;
	Data : word;
	ShareNotLoaded : boolean;

begin
	Assign(F, 'temp.$$$');
	Rewrite(F);
	Write(F, data);
	asm
		mov ShareNotLoaded, True
		{$IFDEF Windows}
			mov bx, TFileRec(F).Handle
		{$ELSE}
			mov bx, FileRec(F).Handle
		{$ENDIF}
		mov cx, 0
		mov dx, 0
		mov si, 0
		mov di, 1
		mov al, 0
		mov ah, $5C
		int $21
		jc @@NoError
		dec ShareNotLoaded
		@@NoError:
	end;
	IsShareLoaded := not ShareNotLoaded;
	Close(F);
	Erase(F);
end;

{******************************************************************
 ***                    SET NUMBER FILE HANDLES                 ***
 ******************************************************************}

procedure SetNumFileHandles;
var
	Regs: TRegisters;

begin
	Regs.AH := $67;  {Set handle count - see p712 DOS programmers ref}
	Regs.BX := Num;  {Set number of handles desired}

	msdos(regs);

	LastDOSFuncResult := Regs.AX;

	{Check for errors}
	if (Regs.Flags and $01)>0 then begin
		{check carry flag - This seems to be the one}
		ProgramWarning('Error setting #File Handles='+N2Str(Num)+#13#10
										+'Regs.AX='+IOError(Regs.AX),
										hcFileHandleErrorMsg);
		{no handles by default 20 so no point in setting to less than that}
		if (Regs.AX = 8) and (Num>=40) then SetNumFileHandles(Num div 2); {try again with less}
	end;
end;


{*****************************************************************
 ***                                                           ***
 ***                   FILE MAINTENANCE                        ***
 ***                                                           ***
 *****************************************************************}

{****************************************************************
 ***                    DELETE                                ***
 ****************************************************************}

procedure DeleteFile;
var
	Regs : TRegisters;

begin
	{Calls interrupt $21 - msdos - with $41 - delete file (handle)}
	{see p631 of the DOS Programmers reference, and p261}

	regs.ah := $41;   {delete file}

	{$IFDEF Windows}
	{$ELSE}
		{convert filename to nul-terminated string...}
		FileName := FileName+#0;            {add nul terminator}
		regs.ds := seg(FileName[1]);
		regs.dx := ofs(FileName[1]);   {bypass first - length - byte}

		msdos(regs);
	{$ENDIF}

	LastDOSFuncResult := Regs.AX;
	{Should check return codes}
end;

{****************************************************************
 ***                    IS THERE                              ***
 ****************************************************************}
function FileExists;
var CheckFile : text;
		LastIOResult : integer;

begin
	FileMode := fmShareReadOnly; {just want to look...}
{$I-}
	Assign(CheckFile, FileName);
	Reset(CheckFile);
	LastIOResult := IOResult; {store & clear}

	if (LastIOResult = 2) or (LastIOResult = 3) then
		{Not there}
		FileExists := False
	else begin
		FileExists := True;
		if LastIOResult=0 then close(CheckFile);
	end;
{$I+}

	FileMode := fmDefault;
end;

{****************************************************************
 ***                    RENAME                                ***
 ****************************************************************}
{needs to be in default directory}
procedure RenameFile;
var F : text;
begin
	Assign(F, FromName);
	Rename(F, ToName);
end;

{****************************************************************
 ***                    COPY                                  ***
 ****************************************************************}
{copout, uses dos copy command}
procedure DOSCopyFile(const FromName, ToName : FNameStr);
begin
{$IFDEF Windows}
	DosCommand('COPY '+StrPas(FromName)+' '+StrPas(ToName),rnNone);
{$ELSE}
	DosCommand('COPY '+FromName+' '+ToName,rnNone);
{$ENDIF}

end;


{modified, From Dr TS, Garbo}
function CopyFile(const FromName, ToName : FNameStr) : integer;
type 	Tbuffer = array [1..65535] of char;
			Pbuffer = ^Tbuffer;  { Use the heap }

var Buffer : PBuffer;     { for the buffer }
		f1, f2 : file;
		bufferSize, readCount, writeCount : word;

		{user info}
		ProBox : PProgressBox;
		Timer  : TTimer;
		BlockNo : word;
		ProBoxMsg : string;
		FromSize : longint;

		LastIOResult : integer;

		function CheckIOResult : integer;
		begin
			LastIOResult := IOresult;
			CheckIOResult := LastIOResult;
			CopyFile := LastIOResult;
		end;


begin
	CopyFile := 0;

	{Check memory}
	BUfferSize := 65535;
	if BufferSize>MaxAvail then BufferSize := MaxAvail div 2;

	FromSize := GetFileSize(FromName);

	{assign the files}
	Assign(f1, FromName);
	Assign(f2, ToName);

	{open the files, read only}
{$I-}
	FileMode := 0;                { To read also read-only files }

	Reset (f1, 1); 						    { Note the record size 1, important! }
	if CheckIOResult <> 0 then exit;   { Does the file exist? }
	FileMode := fmShareReadWrite;	{ set to read/write files}

	Rewrite (f2, 1); 							{ Open the target }
	if CheckIOResult <> 0 then begin close(f1); exit; end;
{$I+}

	GetMem(Buffer, BufferSize);              { Create the buffer, on the heap }

	ProBox := nil;
	BlockNo := 0;
	Timer.Start;

	{$IFDEF Windows}
		ProBoxMsg := StrPas(FromName)+' to '+StrPas(ToName);
	{$ELSE}
		ProBoxMsg := 'Copying '+GetFileName(FromName)+' to '+GetFileName(ToName);
		ProBoxMsg := ProBoxMsg + ' ('+N2Str(FromSize div 1000)+'kb)';
	{$ENDIF}

	repeat                        { Do the copying }
		BlockRead(f1, Buffer^, BufferSize, ReadCount);
		{$I-} BlockWrite (f2, buffer^, ReadCount, WriteCount); {$I+}

		{inform user on progress}
		inc(BlockNo);
		if ProBox <> nil then
			ProBox^.Update(ProBoxMsg+#13,BlockNo,FromSize div BufferSize)
		else
			if Timer.Secs>1 then begin
				ProBox := NewProgressBox('COPY FILE',ProBoxMsg,mfSkipButton, hcNoContext);
				ProBox^.DisplayOptions := ProBox^.DisplayOptions and not bpCounter;
			end;

	until (readCount = 0) or (writeCount <> readCount) or (CheckIOResult<>0) or ((ProBox<>nil) and (ProBox^.Command=cmCancel));

	close (f1); close (f2);
	FreeMem(Buffer, BufferSize); { Release the buffer from the heap }

	if ProBox<>nil then dispose(ProBox, done);

	if LastIOResult<>0 then
		Debug.Writeln('COPY FILE '+FRomName+'  '+ToName+' FAILED? IOresult='+N2Str(LastIOREsult));

	FileMode := fmDefault;
end;

{as above, slower but copes with files locked by kameleon & then copied}
procedure CopyStream(const FromName, ToName : FNameStr);
var	S1,S2 : PDosStream;
		REc : longint;
		ProBox : PProgressBox;
		B : byte;
		Size : longint;
		COmmand : word;

begin
	{Check memory}
	{assign the files}
	New(S1, init(FromName,stOpenShare));
	New(S2, init(ToName,stCreate));{}

	if S1^.Status<>stOK then begin
		ProgramWarning('Could not open file for copying'#13#10+FromName+#13#10+IOError(S1^.ErrorInfo), hcIOErrorMsg);
		dispose(S1, done);
		dispose(S2, done);
		exit;
	end;

	if S2^.Status<>stOK then begin
		ProgramWarning('Could not open file for copying'#13#10+ToName+#13#10+IOError(S2^.ErrorInfo), hcIOErrorMsg);
		dispose(S1, done);
		dispose(S2, done);
		exit;
	end;{}

	ProBox := nil;
	Size := S1^.GetSize-1;
	Rec := 0;
	Command := cmOK;

	while (Rec<Size) and (Command<>cmCancel) do begin
		S1^.Read(B, 1);
		S2^.Write(B, 1);

		{inform user on progress, if taking longer than 1 sec}
		if Rec mod 10 = 0 then begin
			if ProBox <> nil then begin
				ProBox^.Update('Copying...',Rec,Size);
				Command := ProBox^.Command;
			end else
				if Timer.Secs>1 then
					{$IFDEF Windows}
						ProBox := NewProgressBox('Copying File',StrPas(FromName)+' to '+StrPas(ToName),mfSkipButton, hcNoContext);
					{$ELSE}
						ProBox := NewProgressBox('Copying File',FromName+' to '+ToName,mfSkipButton, hcNoContext);
					{$ENDIF}
		end;

		{check status}
		if S1^.Status<>stOK then begin
			ProgramWarning('Error Copying From File'#13#10+FromName+#13#10+IOError(S1^.ErrorInfo), hcIOErrorMsg);{}
			S1^.Reset;
			dec(Rec);
			S1^.Seek(Rec);
			{COmmand := cmCancel;{}
		end;

		if S2^.Status<>stOK then begin
			ProgramWarning('Error Copying To File'#13#10+ToName+#13#10+IOError(S2^.ErrorInfo), hcIOErrorMsg);
			COmmand := cmCancel;
		end;

		inc(Rec);
	end;

	dispose(S1, done);
	dispose(S2, done);

	if ProBox<>nil then dispose(ProBox, done);

end;


{****************************************************************
 ***                 GET FILE'S DATE                          ***
 ****************************************************************}

procedure GetFileDate;
var	F : text;
		ftime : Longint; { For Get/SetFTime}
		{$IFDEF Windows}
			dt : TDateTime; { For Pack/UnpackTime }
		{$ELSE}
			dt : DateTime;
		{$ENDIF}
		LastIO : integer;

begin
	Date.Clear;
{$I-}
	FileMode := fmShareREadOnly;
	Assign(F, FileName);
	reset(F); {don't rewrite - it may be in use}
	LastIO := IOResult;

	if LastIO = 0 then begin
		GetFTime(F,ftime); { Get creation time }
		LastIO := IOREsult;
		if LastIO = 0 then begin
			UnpackTime(ftime,dt);

			with dt do begin
				Date.SetToNum(Day, Month, Year);
				Time.SetToNum(Hour, Min, Sec);
			end;

		end else
			ProgramWarning('Could not date file '+Filename+#13#10+IOError(LastIO), hcIOErrorMsg);

		Close(f);   { Close file }

	end else
		ProgramWarning('Could not open file '+Filename+' for dating'+#13#10+IOError(LastIO), hcIOErrorMsg);

	FileMode := fmDefault;
{$I+}
end;

{****************************************************************
 ***                 GET FILE'S SIZE                          ***
 ****************************************************************}

function GetFileSize;
var	SFile : file of byte;
		LastIOResult : integer;
begin
{$I-}
	FileMode := fmShareReadOnly; {only need to look...}

	{Open file for sizing & to test that it exists}
	Assign(SFile, FileName);  {Open ready for sizing}
	Reset(SFile);

	{Check to see if file opened OK}
	LastIOResult := IOResult;
	if (LastIOResult<>0) then begin
		GetFileSize := -LastIOResult;
		exit;
	end;

	GetFileSize := filesize(SFile);           {Size it}
	close(SFile);

	FileMode := fmDefault;
{$I+}
end;



{*****************************************************************
 ***                                                           ***
 ***                     PATH METHODS                          ***
 ***                                                           ***
 *****************************************************************}

{=============== SET DEFAULT DIRECTORY ====================}
procedure SetCurrentDirectory;
var
	Regs: TRegisters;

begin
	{Calls interrupt $21 - msdos - with $3B - Set Directory
	{see p622 of the DOS Programmers reference}

	regs.ah := $3B;   {set dir}

	DirName := DirName+#0;            {add nul terminator}
	regs.ds := seg(DirName[1]);
	regs.dx := ofs(DirName[1]);   {bypass first - length - byte}

	msdos(regs);

	LastDOSFuncResult := Regs.AX;

	{Should check return codes}
end;

{=============== GET DEFAULT DIRECTORY ====================}
function GetCurrentDirectory;
var
	Regs: TRegisters;
	Dir : PChar;

begin
	{Calls interrupt $21 - msdos - with $47 - Get Directory
	{see p662 of the DOS Programmers reference}

	regs.ah := $47;   {get dir}

	regs.dl := 0;			{default drive}

	regs.ds := seg(Dir);
	regs.dx := ofs(Dir);

	msdos(regs);

	LastDOSFuncResult := Regs.AX;{}

	if Regs.AX<>$F then {invalid drive}
		{Dir is simply a "scratch buffer" - nul terminated string}
		GetCurrentDirectory := '\'+StrPas(Dir)
	else
		GetCurrentDirectory := '';

end;

procedure CheckPath(var S : string);
begin
	if not ValidPath(S) then begin
		ProgramWarning('Path '+S+' invalid', hcInvalidPathMsg);
{		S := '';{}
	end;
end;


function ValidPath;
var CheckFile : Text;
		LastIOResult : integer;

begin
{$I-}
	ValidPath := True;
	if S <> '' then begin
		FileMode := fmShareReadOnly; {just lookin'}
		Assign(CheckFile, S+'$$$');
		Reset(CheckFile);{}
		LastIOResult := IOResult; {store & clear}

		if LastIOResult = 3 then {path not found}
			ValidPath := False
		else begin
			{file not found, etc}
			ValidPath := True;
			if LastIOResult=0 then close(CheckFile);
		end; {}
		FileMode := fmDefault;
	end;
{$I+}
end;


{*****************************************************************
 ***                                                           ***
 ***                  PATH STRING PROCESSING                   ***
 ***                                                           ***
 *****************************************************************}
procedure SplitProgParams(const S : string; var Prog, Params : string);
begin
	Params 	:= delspace(copy(S,	pos(' ', S)+1,255));
	Prog 		:= delspace(copy(S,	1, 						pos(' ', S)-1));
end;

function EmptyFileName(const FIleName : FNameStr) : boolean;
begin
{$IFDEF Windows}
	EmptyFileName := (FileName = nil) or (FIleName[0]='.');
{$ELSE}
	EmptyFileName := (Filename = '') or (FileName[1]='.');
{$ENDIF}
end;

function GetFileName;
begin
	while pos('\',S)>0 do S := Copy(S,pos('\',S)+1,255); {chop out up to \'s}
	while pos(':',S)>0 do S := Copy(S,pos(':',S)+1,255); {chop out colons}
	GetFileName := S;
end;

function GetJustFileName;
begin
	S := GetFileName(S);
	if pos('.',S)>0 then S := Copy(S,1,pos('.',S)-1);
	GetJustFileName := S;
end;

function GetPath;
begin
	GetPath := Copy(S, 1, length(S)-length(GetFileName(S))); {all of string minus file name...}
end;

function GetExt;
begin
	if pos('.',S)=0 then
		GetExt := ''
	else
		GetExt := Copy(S, pos('.',S)+1, length(S)); {from period onwards}
end;


function ChkEndSlash(S : string) : string;
begin
	if (S<>'') and (S[length(S)] <> '\') then ChkEndSlash := S + '\' else ChkEndSlash := S;
end;




begin
{$IFDEF fixit}	write('Initialising DosUtils unit...'); {$ENDIF}

	{get dos version & store in single format so easy to check}
	DosVer := hi(DosVersion) + (lo(DosVersion) div 10);

	{Automatically increase number of file handles on startup}
	{$IFDEF MSDOS}	SetNumFileHandles(30); {$ENDIF}
	{$IFDEF DPMI}		SetNumFileHandles(60); {$ENDIF}

	{$IFNDEF SingleUser}{$IFNDEF WIndows}
		if not IsShareLoaded then
			ProgramWarning('Multi-user but SHARE not loaded!', hcNoShareMsg);
	{$ENDIF}{$ENDIF}

	{$IFDEF fixit} writeln('...done'); {$ENDIF}
end.
