Contributor: ANDREW EIGUS             

{
I'm very glad to be useful and to post the enhanced DOS unit for Turbo Pascal
7.0. It includes lots of nice routines written on inline asm, combined with
short comments and explanations. All you have in standard DOS unit you may
find in EnhDOS as well except of Exec and SwapVectors. Sure, the full source
code!

What is good?
-----------------

1. Fast! (because of the asm)
2. Flexible! (less procedures, more functions, lots of parameters)
3. Good error-handling routines. (don't need to care to check errors at all)
4. _Strong_ file service. (lots of file functions)
5. Lots of additional DOS service functions that can't be found in any standard
   or non-standard Pascal, C,... library.
6. Windows (tm) compatible (means you may use these routines when developing
   Windows (tm) applications.
7. Own memory allocate/release routines. (used DOS memory allocation)
8. Free. Released to a Public Domain.

What is bad?
-----------------

1. Requires Borland Turbo Pascal version 7.0 or later (7.01)
2. Requires DOS 3.1 or later. Sorry guys, wanna cool service - need later DOS.
3. Won't run on XT personal computers. (uses 286 instructions)
4. No more strings. (all string-type names are of PChar type)
5. Exec and SwapVectors not implemented. If you'd like this code, I will
   continue modifying this unit and will eventually add the above functions
   too.

Well, routines were checked on IBM PS/2 386SX, seems like work fine!

Greetingz to
-----------------

 Bas van Gaalen (cool asm programmer and my PASCAL area friend ;)
 Dj Murdoch (best explainer ;)
 Gayle Davis (SWAG live forever) Feel free to place it into a next SWAG bundle.
 Ralph Brown (brilliant idea to make the interrupt list)
 Alex Grischenko (whose asm help was very appreciated)
 ...and all of you, guys!

Material used
-----------------

Borland Pascal 7.0 Runtime Library source code
Ralph Brown's Interrupt List
Tech Help 4.0


You may use this source-code-software in ANY purpose. Code may be changed.
If some of the routines won't work, please send me a message.
If you don't mind, please leave my copyright strings as they are.}

Unit EnhDOS;
(*
  Turbo Pascal 7.0 - ENHDOS.PAS

  Enhanced DOS interface unit for DOS 3.1+ ***  Version 1.1  April, 1994.
  Copyright (c) 1994  by Andrew Eigus           Fidonet 2:5100/33

  Runtime Library Portions Copyright (c) 1991,92 Borland International }

  THIS UNIT SOURCE IS FREE
*)

interface

{$X+} { Enable extended syntax }
{$G+} { Enable 286+ instructions }

const

  { My copyright information }

  Copyright : PChar = 'Portions Copyright (c) 1994 by Andrew Eigus';

  { GetDriveType return values }

  dtError     = $00; { Bad drive }
  dtFixed     = $01; { Fixed drive }
  dtRemovable = $02; { Removable drive }
  dtRemote    = $03; { Remote (network) drive }

  { Handle file open modes (om) constants }

  omRead           = $00; { Open file for input only }
  omWrite          = $01; { Open file for output only }
  omReadWrite      = $02; { Open file for input or/and output (both modes) }
  omShareCompat    = $00; { Modes used when SHARE.EXE loaded }
  omShareExclusive = $10;
  omShareDenyWrite = $20;
  omShareDenyRead  = $30;
  omShareDenyNone  = $40;

  { Maximum file name component string lengths }

  fsPathName       = 79;
  fsDirectory      = 67;
  fsFileSpec       = 12;
  fsFileName       = 8;
  fsExtension      = 4;

  { FileSplit return flags }

  fcExtension      = $0001;
  fcFileName       = $0002;
  fcDirectory      = $0004;
  fcWildcards      = $0008;

  { File attributes (fa) constants }

  faNormal         = $00;
  faReadOnly       = $01;
  faHidden         = $02;
  faSysFile        = $04;
  faVolumeID       = $08;
  faDirectory      = $10;
  faArchive        = $20;
  faAnyFile        = $3F;

  { Seek start offset (sk) constants }

  skStart = 0; { Seek position relative to the beginning of a file }
  skPos   = 1; { Seek position relative to a current file position }
  skEnd   = 2; { Seek position relative to the end of a file }

  { Error handler function (fr) result codes }

  frOk    = 0; { Continue program }
  frRetry = 1; { Retry function once again }

  { Function codes (only passed to error handler routine) (fn) constants }

  fnGetDPB         = $3200;
  fnGetDiskSize    = $3600;
  fnGetDiskFree    = $3601;
  fnGetCountryInfo = $3800;
  fnSetDate        = $2B00;
  fnSetTime        = $2D00;
  fnIsFixedDisk    = $4408;
  fnIsNetworkDrive = $4409;
  fnCreateDir      = $3900;
  fnRemoveDir      = $3A00;
  fnGetCurDir      = $4700;
  fnSetCurDir      = $3B00;
  fnDeleteFile     = $4100;
  fnRenameFile     = $5600;
  fnGetFileAttr    = $4300;
  fnSetFileAttr    = $4301;
  fnFindFirst      = $4E00;
  fnFindNext       = $4F00;
  fnCreateFile     = $5B00;
  fnCreateTempFile = $5A00;
  fnOpenFile       = $3D00;
  fnRead           = $3F00;
  fnWrite          = $4000;
  fnSeek           = $4200;
  fnGetFDateTime   = $5700;
  fnSetFDateTime   = $5701;
  fnCloseFile      = $3E00;
  fnMemAlloc       = $4800;
  fnMemFree        = $4900;

  { DOS 3.x+ errors/return codes }

  dosrOk                = 0;   { Success }
  dosrInvalidFuncNumber = 1;   { Invalid DOS function number }
  dosrFileNotFound      = 2;   { File not found }
  dosrPathNotFound      = 3;   { Path not found }
  dosrTooManyOpenFiles  = 4;   { Too many open files }
  dosrFileAccessDenied  = 5;   { File access denied }
  dosrInvalidFileHandle = 6;   { Invalid file handle }
  dosrNotEnoughMemory   = 8;   { Not enough memory }
  dosrInvalidEnvment    = 10;  { Invalid environment }
  dosrInvalidFormat     = 11;  { Invalid format }
  dosrInvalidAccessCode = 12;  { Invalid file access code }
  dosrInvalidDrive      = 15;  { Invalid drive number }
  dosrCantRemoveDir     = 16;  { Cannot remove current directory }
  dosrCantRenameDrives  = 17;  { Cannot rename across drives }
  dosrNoMoreFiles       = 18;  { No more files }

type

  TPathStr = array[0..fsPathName] of Char;
  TDirStr  = array[0..fsDirectory] of Char;
  TNameStr = array[0..fsFileName] of Char;
  TExtStr  = array[0..fsExtension] of Char;
  TFileStr = array[0..fsFileSpec] of Char;

  { Disk information block structure }

  PDiskParamBlock = ^TDiskParamBlock;
  TDiskParamBlock = record
    Drive : byte;             { Disk drive number (0=A, 1=B, 2=C...) }
    SubunitNum : byte;        { Sub-unit number from driver device header }
    SectSize : word;          { Number of bytes per sector }
    SectPerClust : byte;      { Number of sectors per cluster -1
                                (max sector in cluster) }
    ClustToSectShft : byte;   { Cluster-to-sector shift }
    BootSize : word;          { Reserved sectors (boot secs; start of root dir}
    FATCount : byte;          { Number of FATs }
    MaxDir : word;            { Number of directory entries allowed in root }
    DataSect : word;          { Sector number of first data cluster }
    Clusters : word;          { Total number of allocation units (clusters)
                                +2 (number of highest cluster) }
    FATSectors : byte;        { Sectors needed by first FAT }
    RootSect : word;          { Sector number of start of root directory }
    DeviceHeader : pointer;   { Address of device header }
    Media : byte;             { Media descriptor byte }
    AccessFlag : byte;        { 0 if drive has been accessed }
    NextPDB : pointer         { Address of next DPB (0FFFFh if last) }
  end;

  { Disk allocation data structure }

  PDiskAllocInfo = ^TDiskAllocInfo;
  TDiskAllocInfo = record
    FATId : byte;             { FAT Id }
    Clusters : word;          { Number of allocation units (clusters) }
    SectPerClust : byte;      { Number of sectors per cluster }
    SectSize : word           { Number of bytes per sector }
  end;

  { Country information structure }

  PCountryInfo = ^TCountryInfo;
  TCountryInfo = record
    DateFormat : word; { Date format value may be one of the following:
                         0 - Month, Day, Year     (USA)
                         1 - Day, Month, Year     (Europe)
                         2 - Year, Month, Day     (Japan) }

    CurrencySymbol : array[0..4] of Char; { Currency symbol string }
    ThousandsChar : byte; { Thousands separator character }
    reserved1 : byte;
    DecimalChar : byte;   { Decimal separator character }
    reserved2 : byte;
    DateChar : byte;      { Date separator character }
    reserved3 : byte;
    TimeChar : byte;      { Time separator character }
    reserved4 : byte;
    CurrencyFormat : byte; { Currency format:
                             $XXX.XX
                             XXX.XX$
                             $ XXX.XX
                             XXX.XX $
                             XXX$XX }

    Digits : byte;          { Number of digits after decimal in currency }
    TimeFormat : byte;      { Time format may be one of the following:
                              bit 0 = 0 if 12 hour clock
                                  1 if 24 hour clock }

    MapRoutine : pointer;   { Address of case map routine FAR CALL,
                              AL - character to map to upper case [>=80h] }

    DataListChar : byte;    { Data-list separator character }
    reserved5 : byte;
    reserved6 : array[1..10] of Char
  end;

  THandle = Word; { Handle type (file handle and memory handle functions) }

  { Error handler function }

  TErrorFunc = function(ErrCode : integer; FuncCode : word) : byte;

  { Search record used by FindFirst and FindNext }

  TSearchRec = record
    Fill : array[1..21] of Byte;
    Attr : byte;
    Time : longint;
    Size : longint;
    Name : TFileStr
  end;

  { Date and time record used by PackTime and UnpackTime }

  TDateTime = record
    Year,
    Month,
    Day,
    Hour,
    Min,
    Sec : word
  end;


var
  DOSResult : integer; { Error status variable }
  TempStr : array[0..High(String)] of Char;

function SetErrorHandler(Handler : TErrorFunc) : pointer;
function Pas2PChar(S : string) : PChar;

function GetInDOSFlag : boolean;
function GetDOSVersion : word;
function GetSwitchChar : char;
function SetSwitchChar(Switch : char) : byte;
function GetCountryInfo(var Info : TCountryInfo) : integer;
procedure GetDate(var Year : word; var Month, Day, DayOfWeek : byte);
function SetDate(Year : word; Month, Day : byte) : boolean;
procedure GetTime(var Hour, Minute, Second, Sec100 : byte);
function SetTime(Hour, Minute, Second, Sec100 : byte) : boolean;
function GetCBreak : boolean;
function SetCBreak(Break : boolean) : boolean;
function GetVerify : boolean;
function SetVerify(Verify : boolean) : boolean;
function GetArgCount : integer;
function GetArgStr(Dest : PChar; Index : integer; MaxLen : word) : PChar;
function GetEnvVar(VarName : PChar) : PChar;
function GetIntVec(IntNo : byte; var Vector : pointer) : pointer;
function SetIntVec(IntNo : byte; Vector : pointer) : pointer;

function GetDTA : pointer;
function GetCurDisk : byte;
function SetCurDisk(Drive : byte) : byte;
procedure GetDriveAllocInfo(Drive : byte; var Info : TDiskAllocInfo);
function GetDPB(Drive : byte; var DPB : TDiskParamBlock) : integer;
function DiskSize(Drive : byte) : longint;
function DiskFree(Drive : byte) : longint;
function IsFixedDisk(Drive : byte) : boolean;
function IsNetworkDrive(Drive : byte) : boolean;
function GetDriveType(Drive : byte) : byte;

function CreateDir(Dir : PChar) : integer;
function RemoveDir(Dir : PChar) : integer;
function GetCurDir(Drive : byte; Dir : PChar) : integer;
function SetCurDir(Dir : PChar) : integer;

function DeleteFile(Path : PChar) : integer;
function RenameFile(OldPath, NewPath : PChar) : integer;
function ExistsFile(Path : PChar) : boolean;
function GetFileAttr(Path : PChar) : integer;
function SetFileAttr(Path : PChar; Attr : word) : integer;
function FindFirst(Path : PChar; Attr: word; var F : TSearchRec) : integer;
function FindNext(var F : TSearchRec) : integer;
procedure UnpackTime(P : longint; var T : TDateTime);
function PackTime(var T : TDateTime) : longint;

function h_CreateFile(Path : PChar) : THandle;
function h_CreateTempFile(Path : PChar) : THandle;
function h_OpenFile(Path : PChar; Mode : byte) : THandle;
function h_Read(Handle : THandle; var Buffer; Count : word) : word;
function h_Write(Handle : THandle; var Buffer; Count : word) : word;
function h_Seek(Handle : THandle; SeekPos : longint; Start : byte) : longint;
function h_FilePos(Handle : THandle) : longint;
function h_FileSize(Handle : THandle) : longint;
function h_Eof(Handle : THandle) : boolean;
function h_GetFTime(Handle : THandle) : longint;
function h_SetFTime(Handle : THandle; DateTime : longint) : longint;
function h_CloseFile(Handle : THandle) : integer;

function MemAlloc(Size : longint) : pointer;
function MemFree(P : pointer) : integer;

function FileSearch(Dest, Name, List : PChar) : PChar;
function FileExpand(Dest, Name : PChar) : PChar;
function FileSplit(Path, Dir, Name, Ext : PChar) : word;

implementation

{$IFDEF Windows}
{$DEFINE ProtectedMode}
{$ENDIF}

{$IFDEF DPMI}
{$DEFINE ProtectedMode}
{$ENDIF}

{$IFDEF Windows}

uses WinTypes, WinProcs, Strings;

{$ELSE}

uses Strings;

{$ENDIF}

const DOS = $21; { DOS interrupt number }

var
  ErrorHandler : TErrorFunc;

Function SetErrorHandler;
{ Sets the new error handler to hook all errors returned by EnhDOS functions,
  and returns the pointer to an old interrupt handler routine }
Begin
  SetErrorHandler := @ErrorHandler;
  ErrorHandler := Handler
End; { SetErrorHandler }

Function Pas2PChar(S : string) : PChar;
{ Returns PChar type equivalent of the S variable. Use this function
  to convert strings to PChars }
Begin
  Pas2PChar := StrPCopy(TempStr, S)
End; { Pas2PChar }

{$IFDEF Windows}

procedure AnsiDosFunc; assembler;
asm
  PUSH DS
  PUSH CX
  PUSH AX
  MOV SI,DI
  PUSH ES
  POP DS
  LEA DI,TempStr
  PUSH SS
  POP ES
  MOV CX,fsPathName
  CLD
@@1:
  LODSB
  OR  AL,AL
  JE  @@2
  STOSB
  LOOP @@1
@@2:
  XOR AL,AL
  STOSB
  LEA DI,TempStr
  PUSH SS
  PUSH DI
  PUSH SS
  PUSH DI
  CALL AnsiToOem
  POP AX
  POP CX
  LEA DX,TempStr
  PUSH SS
  POP DS
  INT DOS
  POP DS
end; { AnsiDosFunc /Windows }

{$ELSE}

procedure AnsiDosFunc; assembler;
asm
  PUSH DS
  MOV DX,DI
  PUSH ES
  POP DS
  INT DOS
  POP DS
end; { AnsiDosFunc }

{$ENDIF}

Function GetInDOSFlag; assembler;
{ GETINDOSFLAG - DOS service function
  Description: Returns the current state of InDOS flag; fn=34h
  Returns: True if a DOS operation is being performed, False if there is
           no DOS command that currently is running }
Asm
  MOV AH,34h
  INT DOS
  MOV AL,BYTE PTR [ES:BX]
End; { GetInDOSFlag }

Function GetDOSVersion; assembler;
{ GETDOSVERSION - DOS service function
  Description: Retrieves DOS version number; fn=30h
  Returns: Major DOS version number in low-order byte,
           minor version number in high-order byte of word }
Asm
  MOV AH,30h
  INT DOS
End; { GetDOSVersion }

Function GetSwitchChar; assembler;
{ GETSWITCHCHAR - DOS service function
  Description: Retrieves DOS command line default switch character; fn=37h
  Returns: Switch character ('/', '-', ...) or FFh if unsupported subfunction }
Asm
  MOV AH,37h
  XOR AL,AL
  INT DOS
  CMP AL,0FFh
  JE  @@1
  MOV AL,DL
@@1:
End; { GetSwitchChar }

Function SetSwitchChar; assembler;
{ SETSWITCHCHAR - DOS service function
  Description: Sets new DOS command line switch character; fn=37h
  Returns: FFh if unsupported subfunction, any other value success }
Asm
  MOV AX,3701h
  MOV DL,Switch
  INT DOS
End; { SetSwitchChar }

Function GetCountryInfo; assembler;
{ GETCOUNTRYINFO - DOS service function
  Description: Retrieves country information; fn=38h
  Returns: Country code if successful, negative DOS error code otherwise }
Asm
@@1:
  PUSH DS
  MOV AH,38h
  XOR AL,AL
  LDS DX,Info
  INT DOS
  POP DS
  JC  @@2
  MOV AX,BX
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnGetCountryInfo { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@3:
End; { GetCountryInfo }

Procedure GetDate; assembler;
{ GETDATE - DOS service function
  Description: Retrieves the current date set in the operating system.
               Ranges of the values returned are: Year 1980-2099,
               Month 1-12, Day 1-31 and DayOfWeek 0-6 (0 corresponds to
               Sunday) }
Asm
  MOV AH,2AH
  INT DOS
  XOR AH,AH
  LES DI,DayOfWeek
  STOSB
  MOV AL,DL
  LES DI,Day
  STOSB
  MOV AL,DH
  LES DI,Month
  STOSB
  XCHG AX,CX
  LES DI,Year
  STOSW
End; { GetDate }

Function SetDate; assembler;
{ SETDATE - DOS service function
  Description: Sets the current date in the operating system. Valid
               parameter ranges are: Year 1980-2099, Month 1-12 and
               Day 1-31
  Returns: True if the date was set, False if the date is not valid }
Asm
  MOV CX,Year
  MOV DH,Month
  MOV DL,Day
  MOV AH,2BH
  INT DOS
  CMP AL,0
  JE  @@1
  MOV DOSResult,AX
  PUSH AX
  PUSH fnSetDate
  CALL ErrorHandler
  MOV AL,True
@@1:
  NOT AL
End; { SetDate }

Procedure GetTime; assembler;
{ GETTIME - DOS service function
  Description: Returns the current time set in the operating system.
               Ranges of the values returned are: Hour 0-23, Minute 0-59,
               Second 0-59 and Sec100 (hundredths of seconds) 0-99 }
Asm
  MOV AH,2CH
  INT DOS
  XOR AH,AH
  MOV AL,DL
  LES DI,Sec100
  STOSB
  MOV AL,DH
  LES DI,Second
  STOSB
  MOV AL,CL
  LES DI,Minute
  STOSB
  MOV AL,CH
  LES DI,Hour
  STOSB
End; { GetTime }

Function SetTime; assembler;
{ SETTIME - DOS service function
  Description: Sets the time in the operating system. Valid
               parameter ranges are: Hour 0-23, Minute 0-59, Second 0-59 and
               Sec100 (hundredths of seconds) 0-99
  Returns: True if the time was set, False if the time is not valid }
Asm
  MOV CH,Hour
  MOV CL,Minute
  MOV DH,Second
  MOV DL,Sec100
  MOV AH,2DH
  INT DOS
  CMP AL,0
  JE  @@1
  MOV DOSResult,AX
  PUSH AX
  PUSH fnSetTime
  CALL ErrorHandler
  MOV AL,True
@@1:
  NOT AL
End; { SetTime }

Function GetCBreak; assembler;
{ GETCBREAK - DOS service function
  Description: Retrieves Control-Break state; fn=3300h
  Returns: Current Ctrl-Break state }
Asm
  MOV AX,3300h
  INT DOS
  MOV AL,DL
End; { GetCBreak }

Function SetCBreak; assembler;
{ SETCBREAK - DOS service function
  Description: Sets new Control-Break state; fn=3300h
  Returns: Old Ctrl-Break state }
Asm
  CALL GetCBreak
  PUSH AX
  MOV AX,3301h
  MOV DL,Break
  INT DOS
  POP AX
End; { SetCBreak }

Function GetVerify; assembler;
{ GETVERIFY - DOS service function
  Description: Returns the state of the verify flag in DOS.
               When off (False), disk writes are not verified.
               When on (True), all disk writes are verified to insure proper
               writing; fn=54h
  Returns: State of the verify flag }
Asm
  MOV AH,54H
  INT DOS
End; { GetVerify }

Function SetVerify; assembler;
{ SETVERIFY - DOS service function
  Description: Sets the state of the verify flag in DOS; fn=2Eh
  Returns: Previous state of the verify flag }
Asm
  CALL GetVerify
  PUSH AX
  MOV AL,Verify
  MOV AH,2EH
  INT DOS
  POP AX
End; { SetVerify }

{$IFDEF Windows}

Procedure ArgStrCount; assembler;
Asm
  LDS SI,CmdLine
  CLD
@@1:
  LODSB
  OR  AL,AL
  JE  @@2
  CMP AL,' '
  JBE @@1
@@2:
  DEC SI
  MOV BX,SI
@@3:
  LODSB
  CMP AL,' '
  JA  @@3
  DEC SI
  MOV AX,SI
  SUB AX,BX
  JE  @@4
  LOOP @@1
@@4:
End; { ArgStrCount /Windows }

Function GetArgCount; assembler;
{ GETARGCOUNT - DOS service function
  Description: Returns the number of parameters passed to the
               program on the command line
  Returns: Actual number of command line parameters }

Asm
  PUSH DS
  XOR  CX,CX
  CALL ArgStrCount
  XCHG AX,CX
  NEG AX
  POP DS
End; { GetArgCount /Windows }

Function GetArgStr; assembler;
{ GETARGSTR - DOS service function
  Description: Returns the specified parameter from the command line
  Returns: ASCIIZ parameter, or an empty string if Index is less than zero
           or greater than GetArgCount. If Index is zero, GetArgStr returns
           the filename of the current module. The maximum length of the
           string returned in Dest is given by the MaxLen parameter. The
           returned value is Dest }

Asm
  MOV CX,Index
  JCXZ @@2
  PUSH DS
  CALL ArgStrCount
  MOV SI,BX
  LES DI,Dest
  MOV CX,MaxLen
  CMP CX,AX
  JB  @@1
  XCHG AX,CX
@@1:
  REP MOVSB
  XCHG AX,CX
  STOSB
  POP DS
  JMP @@3
@@2:
  PUSH HInstance
  PUSH WORD PTR [Dest+2]
  PUSH WORD PTR [Dest]
  MOV AX,MaxLen
  INC AX
  PUSH AX
  CALL GetModuleFileName
@@3:
  MOV AX,WORD PTR [Dest]
  MOV DX,WORD PTR [Dest+2]
End; { GetArgStr /Windows }

{$ELSE}

Procedure ArgStrCount; assembler;
Asm
  MOV DS,PrefixSeg
  MOV SI,80H
  CLD
  LODSB
  MOV DL,AL
  XOR DH,DH
  ADD DX,SI
@@1:
  CMP SI,DX
  JE  @@2
  LODSB
  CMP AL,' '
  JBE @@1
  DEC SI
@@2:
  MOV BX,SI
@@3:
  CMP SI,DX
  JE  @@4
  LODSB
  CMP AL,' '
  JA  @@3
  DEC SI
@@4:
  MOV AX,SI
  SUB AX,BX
  JE  @@5
  LOOP @@1
@@5:
End; { ArgStrCount }

Function GetArgCount; assembler;
{ GETARGCOUNT - DOS service function
  Description: Returns the number of parameters passed to the
               program on the command line
  Returns: Actual number of command line parameters }
Asm
  PUSH DS
  XOR CX,CX
  CALL ArgStrCount
  XCHG AX,CX
  NEG AX
  POP DS
End; { GetArgCount }

Function GetArgStr; assembler;
{ GETARGSTR - DOS service function
  Description: Returns the specified parameter from the command line
  Returns: ASCIIZ parameter, or an empty string if Index is less than zero
           or greater than GetArgCount. If Index is zero, GetArgStr returns
           the filename of the current module. The maximum length of the
           string returned in Dest is given by the MaxLen parameter. The
           returned value is Dest }
Asm
  PUSH DS
  MOV CX,Index
  JCXZ @@1
  CALL ArgStrCount
  MOV SI,BX
  JMP @@4
@@1:
  MOV AH,30H
  INT DOS
  CMP AL,3
  MOV AX,0
  JB  @@4
  MOV DS,PrefixSeg
  MOV ES,DS:WORD PTR 2CH
  XOR DI,DI
  CLD
@@2:
  CMP AL,ES:[DI]
  JE  @@3
  MOV CX,-1
  REPNE SCASB
  JMP @@2
@@3:
  ADD DI,3
  MOV SI,DI
  PUSH ES
  POP DS
  MOV CX,256
  REPNE SCASB
  XCHG AX,CX
  NOT AL
@@4:
  LES DI,Dest
  MOV CX,MaxLen
  CMP CX,AX
  JB  @@5
  XCHG AX,CX
@@5:
  REP MOVSB
  XCHG AX,CX
  STOSB
  MOV AX,WORD PTR [Dest]
  MOV DX,WORD PTR [Dest+2]
  POP DS
End; { GetArgStr }

{$ENDIF}

Function GetEnvVar;
{ GETENVVAR - DOS service function
  Description: Retrieves a specified DOS environment variable
  Returns: A pointer to the value of a specified variable,
           i.e. a pointer to the first character after the equals
           sign (=) in the environment entry given by VarName.
           VarName is case insensitive. GetEnvVar returns NIL if
           the specified environment variable does not exist }
var
  L : word;
  P : PChar;
Begin
  L := StrLen(VarName);
{$IFDEF Windows}
  P := GetDosEnvironment;
{$ELSE}
  P := Ptr(Word(Ptr(PrefixSeg, $2C)^), 0);
{$ENDIF}
  while P^ <> #0 do
  begin
    if (StrLIComp(P, VarName, L) = 0) and (P[L] = '=') then
    begin
      GetEnvVar := P + L + 1;
      Exit;
    end;
    Inc(P, StrLen(P) + 1)
  end;
  GetEnvVar := nil
End; { GetEnvVar }

Function GetIntVec; assembler;
{ GETINTVEC - DOS service function
  Description: Retrieves the address stored in the specified interrupt vector
  Returns: A pointer to this address }
Asm
  MOV AL,IntNo
  MOV AH,35H
  INT DOS
  MOV AX,ES
  LES DI,Vector
  CLD
  MOV DX,BX
  XCHG AX,BX
  STOSW
  XCHG AX,BX
  STOSW
  XCHG AX,DX
End; { GetIntVec }

Function SetIntVec; assembler;
{ SETINTVEC - DOS Service function
  Description: Sets the address in the interrupt vector table for the
               specified interrupt
  Returns: The old address of the specified interrupt vector }
Asm
  LES DI,Vector
  PUSH WORD PTR IntNo
  PUSH ES
  PUSH DI
  PUSH CS
  CALL GetIntVec
  PUSH DX
  PUSH AX
  PUSH DS
  LDS DX,Vector
  MOV AL,IntNo
  MOV AH,25H
  INT DOS
  POP DS
  POP AX
  POP DX
End; { SetIntVec }

Function GetDTA; assembler;
{ GETDTA - DOS service function
  Description: Retrieves a pointer address to a DOS data exchange buffer (DTA).
               By default, DTA address has the offset PSP+80h and the size of
               128 bytes. DTA is used to access files with the FCB method;
               fn=2Fh
  Returns: A pointer address to DTA }
Asm
  MOV AH,2Fh
  INT DOS
  MOV DX,BX { store offset }
  MOV AX,ES { store segment }
End; { GetDTA }

Function GetCurDisk; assembler;
{ GETCURDISK - DOS disk service function
  Description: Retrieves number of disk currently being active; fn=19h
  Returns: Default (current, active) disk number }
Asm
  MOV AH,19h
  INT DOS
End; { GetCurDisk }

Function SetCurDisk; assembler;
{ SETCURDISK - DOS disk service function
  Description: Sets current (default/active) drive; fn=0Eh
  Returns: Number of disks in the system }
Asm
  MOV AH,0Eh
  MOV DL,Drive
  INT DOS
End; { SetCurDisk }

Procedure GetDriveAllocInfo; assembler;
{ GETDRIVEALLOCINFO - DOS disk service function
  Description: Retrieves disk allocation information; fn=1Ch
  Retrieves Info structure }
Asm
  PUSH DS
  MOV AH,1Ch
  MOV DL,Drive
  INT DOS
  MOV AH,BYTE PTR [DS:BX]
  LES DI,Info
  MOV BYTE PTR ES:[DI],AH      { Info.FATId }
  MOV WORD PTR ES:[DI+1],DX    { Info.Clusters }
  MOV BYTE PTR ES:[DI+3],AL    { Info.SectorsPerCluster }
  MOV WORD PTR ES:[DI+4],CX    { Info.BytesPerSector }
  POP DS
End; { GetDriveAllocInfo }

Function GetDPB; assembler;
{ GETDPB - DOS disk service function (undocumented)
  Description: Returns a block of information that is useful for applications
               which perform sector-level access of disk drives supported by
               device drivers; fn=32h
  Returns: 0 if successful, negative dosrInvalidDrive error code otherwise
  Remarks: Use 0 for default drive }
Asm
  MOV DOSResult,dosrOk
  PUSH DS
  MOV AH,32h
  MOV DL,Drive
  INT DOS
  MOV WORD PTR [DPB],DS
  MOV WORD PTR [DPB+2],BX
  POP DS
  XOR AH,AH
  CMP AL,0FFh
  JNE @@1
  MOV DOSResult,dosrInvalidDrive
  PUSH DOSResult
  PUSH fnGetDPB
  CALL ErrorHandler
  MOV AX,DOSResult
  NEG AX
@@1:
End; { GetDPB }

Function DiskSize; assembler;
{ DISKSIZE - DOS disk service function
  Description: Retrieves total disk size; fn=36h
  Returns: Total disk size in bytes if successful, negative dosrInvalidDrive
           error code otherwise
  Remarks: Use 0 for default drive }
Asm
@@1:
  MOV AH,36h
  MOV DL,Drive
  INT DOS
  CMP AX,0FFFFh
  JE  @@2
  MOV BX,DX
  IMUL CX
  IMUL BX
  JMP @@3
@@2:
  MOV DOSResult,dosrInvalidDrive
  PUSH DOSResult
  PUSH fnGetDiskSize
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  MOV AX,DOSResult
  NEG AX
  XOR DX,DX
@@3:
End; { DiskSize }

Function DiskFree; assembler;
{ DISKFREE - DOS disk service function
  Description: Retrieves amount of free disk space; fn=36h
  Returns: Amount of free disk space in bytes if successful,
           negative dosrInvalidDrive error code otherwise
  Remarks: Use 0 for default drive }
Asm
@@1:
  MOV AH,36h
  MOV DL,Drive
  INT DOS
  CMP AX,0FFFFh
  JE  @@2
  IMUL CX
  IMUL BX
  JMP @@3
@@2:
  MOV DOSResult,dosrInvalidDrive
  PUSH DOSResult
  PUSH fnGetDiskFree
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  MOV AX,DOSResult
  NEG AX
  XOR DX,DX
@@3:
End; { DiskFree }

Function IsFixedDisk; assembler;
{ ISFIXEDDISK - DOS disk service function
  Description: Ensures whether the specified disk is fixed or removable;
               fn=4408h
  Returns: True, if the disk is fixed, False - otherwise
  Remarks: Use 0 for default (current) drive }
Asm
  MOV AX,4408h
  MOV BL,Drive
  INT DOS
  JNC @@1
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnIsFixedDisk  { store function code }
  CALL ErrorHandler
@@1:
End; { IsFixedDisk }

Function IsNetworkDrive; assembler;
{ ISNETWORKDRIVE - DOS disk service function
  Description: Ensures whether the specified disk drive is a network drive;
               fn=4409h
  Returns: True if drive is a network drive, False if it's a local drive
  Remarks: Use 0 for detecting the default (current) drive }
Asm
  MOV AX,4409h
  MOV BL,Drive
  INT DOS
  JNC @@1
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnIsNetworkDrive  { store function code }
  CALL ErrorHandler
@@1:
End; { IsNetworkDrive }

Function GetDriveType(Drive : byte) : byte; assembler;
{ GETDRIVETYPE - Disk service function
  Description: Detects the type of the specified drive. Uses IsFixedDisk and
               IsNetworkDrive functions to produce a result value
  Returns: One of (dt) constants (see const section)
  Remarks: Use 0 for detecting the default (current) drive }
Asm
  PUSH WORD PTR Drive
  CALL IsNetworkDrive
  XOR BL,BL
  CMP DOSResult,dosrOk
  JNE @@3
  CMP AL,True
  JNE @@1
  MOV BL,dtRemote
  JMP @@3
@@1:
  PUSH WORD PTR Drive
  CALL IsFixedDisk
  XOR BL,BL
  CMP DOSResult,dosrOk
  JNE @@3
  CMP AL,True
  JNE @@2
  MOV BL,dtFixed
  JMP @@3
@@2:
  MOV BL,dtRemovable
@@3:
  MOV AL,BL
End; { GetDriveType }

Function CreateDir; assembler;
{ CREATEDIR - DOS directory function
  Description: Creates a directory; fn=39h
  Returns: 0 if successful, negative DOS error code otherwise }
Asm
@@1:
  PUSH DS
  LDS DX,Dir
  MOV AH,39h
  INT DOS
  POP DS
  JC  @@2
  XOR AX,AX
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnCreateDir  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@3:
End; { CreateDir }

Function RemoveDir; assembler;
{ REMOVEDIR - DOS directory function
  Description: Removes (deletes) a directory; fn=3Ah
  Returns: 0 if successful, negative DOS error code otherwise }
Asm
@@1:
  PUSH DS
  LDS DX,Dir
  MOV AH,3Ah
  INT DOS
  POP DS
  JC  @@2
  XOR AX,AX
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnRemoveDir  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@3:
End; { RemoveDir }

Function GetCurDir; assembler;
{ GETCURDIR - DOS directory function
  Description: Retrieves current (active) directory name; fn=47h
  Returns: 0 if successful, negative DOS error code otherwise }
Asm
@@1:
  PUSH DS
  LDS SI,Dir
  MOV DL,Drive
  MOV AH,47h
  INT DOS
  POP DS
  JC  @@2
  XOR AX,AX
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnGetCurDir  { store function number }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@3:
End; { GetCurDir }

Function SetCurDir; assembler;
{ SETCURDIR - DOS directory function
  Description: Sets current (active) directory; fn=3Bh
  Returns: 0 if successful, negative DOS error code otherwise }
Asm
@@1:
  PUSH DS
  LDS DX,Dir
  MOV AH,3Bh
  INT DOS
  POP DS
  JC  @@2
  XOR AX,AX
  MOV DOSResult,AX
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnSetCurDir  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@3:
End; { SetCurDir }

Function DeleteFile; assembler;
{ DELETEFILE - DOS file function
  Description: Deletes a file; fn=41h
  Returns: 0 if successful, negative DOS error code otherwise }
Asm
@@1:
  PUSH DS
  LDS DX,Path
  MOV AH,41h
  INT DOS
  POP DS
  JC  @@2
  XOR AX,AX
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnDeleteFile  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@3:
End; { DeleteFile }

Function RenameFile; assembler;
{ RENAMEFILE - DOS file function
  Description: Renames/moves a file; fn=56h
  Returns: 0 if successful, negative error code otherwise }
Asm
@@1:
  PUSH DS
  LDS DX,OldPath
  LES DI,NewPath
  MOV AH,56h
  INT DOS
  POP DS
  JC  @@2
  XOR AX,AX
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnRenameFile  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@3:
End; { RenameFile }

Function ExistsFile; assembler;
{ EXISTSFILE - DOS file function
  Description: Determines whether the file exists; fn=4Eh
  Returns: TRUE if the file exists, FALSE - otherwise }
Asm
  PUSH DS
  LDS DX,Path
  MOV AH,4Eh
  INT DOS
  POP DS
  JNC @@1
  XOR AL,AL
  JMP @@2
@@1:
  MOV AL,True
@@2:
End; { ExistsFile }

Function GetFileAttr; assembler;
{ GETFILEATTR - DOS file function
  Description: Gets file attributes; fn=43h,AL=0
  Returns: File attributes if no error, negative DOS error code otherwise }
Asm
@@1:
  PUSH DS
  LDS DX,Path
  MOV AX,4300h
  INT DOS
  POP DS
  JC  @@2
  MOV AX,CX
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnGetFileAttr  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@3:
End; { GetFileAttr }

Function SetFileAttr; assembler;
{ SETFILEATTR - DOS file function
  Description: Sets file attributes; fn=43h,AL=1
  Returns: 0 if no error, negative DOS error code otherwise }
Asm
@@1:
  PUSH DS
  LDS DX,Path
  MOV CX,Attr
  MOV AX,4301h
  INT DOS
  POP DS
  JC  @@2
  XOR AX,AX
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnSetFileAttr  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@3:
End; { GetFileAttr }

Function FindFirst; assembler;
{ FINDFIRST - DOS file service function
  Description: Searches the specified (or current) directory for
               the first entry that matches the specified filename and
               attributes; fn=4E00h
  Returns: 0 if successful, negative DOS error code otherwise }
Asm
@@1:
  PUSH DS
  LDS DX,F
  MOV AH,1AH
  INT DOS
  POP DS
  LES DI,Path
  MOV CX,Attr
  MOV AH,4EH
  CALL AnsiDosFunc
  MOV DOSResult,dosrOk
  JC  @@2
{$IFDEF Windows}
  LES DI,F
  ADD DI,OFFSET TSearchRec.Name
  PUSH ES
  PUSH DI
  PUSH ES
  PUSH DI
  CALL OemToAnsi
{$ENDIF}
  XOR AX,AX
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnFindFirst  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
@@3:
  NEG AX
End; { FindFirst }

Function FindNext; assembler;
{ FINDNEXT - DOS file service function
  Description: Returs the next entry that matches the name and
               attributes specified in a previous call to FindFirst.
               The search record must be one passed to FindFirst
  Returns: 0 if successful, negative DOS error code otherwise }
Asm
@@1:
  PUSH DS
  LDS DX,F
  MOV AH,1AH
  INT DOS
  POP DS
  MOV AH,4FH
  MOV DOSResult,dosrOk
  INT DOS
  JC  @@2
{$IFDEF Windows}
  LES DI,F
  ADD DI,OFFSET TSearchRec.Name
  PUSH ES
  PUSH DI
  PUSH ES
  PUSH DI
  CALL OemToAnsi
{$ENDIF}
  XOR AX,AX
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnFindNext  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
@@3:
  NEG AX
End; { FindNext }

Procedure UnpackTime; assembler;
{ UNPACKTIME - Service function
  Description: Converts a 4-byte packed date/time returned by
               FindFirst, FindNext or GetFTime into a TDateTime record }
Asm
  LES DI,T
  CLD
  MOV AX,WORD PTR [P+2]
  MOV CL,9
  SHR AX,CL
  ADD AX,1980
  STOSW
  MOV AX,WORD PTR [P+2]
  MOV CL,5
  SHR AX,CL
  AND AX,15
  STOSW
  MOV AX,WORD PTR [P+2]
  AND AX,31
  STOSW
  MOV AX,P.Word[0]
  MOV CL,11
  SHR AX,CL
  STOSW
  MOV AX,WORD PTR [P+2]
  MOV CL,5
  SHR AX,CL
  AND AX,63
  STOSW
  MOV AX,WORD PTR [P]
  AND AX,31
  SHL AX,1
  STOSW
End; { UnpackTime }

Function PackTime; assembler;
{ PACKTIME - Service function
  Decription: Converts a TDateTime record into a 4-byte packed
              date/time used by SetFTime
  Returns: 4-byte long integer corresponding to packed date/time }
Asm
  PUSH DS
  LDS SI,T
  CLD
  LODSW
  SUB AX,1980
  MOV CL,9
  SHL AX,CL
  XCHG AX,DX
  LODSW
  MOV CL,5
  SHL AX,CL
  ADD DX,AX
  LODSW
  ADD DX,AX
  LODSW
  MOV CL,11
  SHL AX,CL
  XCHG AX,BX
  LODSW
  MOV CL,5
  SHL AX,CL
  ADD BX,AX
  LODSW
  SHR AX,1
  ADD AX,BX
  POP DS
End; { PackTime }

Function h_CreateFile; assembler;
{ H_CREATEFILE - DOS Handle file function
  Description: Creates a file; fn=3Ch
  Returns: File handle if successful, 0 if unsuccessful }
Asm
@@1:
  PUSH DS
  LDS DX,Path
  MOV CX,0
  MOV AH,5Bh
  INT DOS
  POP DS
  JC  @@2
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnCreateFile  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  XOR AX,AX
@@3:
End; { h_CreateFile }

Function h_CreateTempFile; assembler;
{ H_CREATETEMPFILE - DOS Handle file function
  Description: Creates a temporary file; fn=5Ah
  Returns: File handle if successful, 0 if unsuccessful }
Asm
@@1:
  PUSH DS
  LDS DX,Path
  MOV CX,0 { file attribute here, 0 used for normal }
  MOV AH,5Ah
  INT DOS
  POP DS
  JC  @@2
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnCreateTempFile  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  XOR AX,AX
@@3:
End; { h_CreateTempFile }

Function h_OpenFile; assembler;
{ H_OPENFILE - DOS Handle file function
  Description: Opens a file for input, output or input/output; fn=3Dh
  Returns: File handle if successful, 0 if unsuccessful }
Asm
@@1:
  PUSH DS
  LDS DX,Path
  MOV AH,3Dh
  MOV AL,Mode
  INT DOS
  POP DS
  JC  @@2
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnOpenFile  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  XOR AX,AX
@@3:
End; { h_OpenFile }

Function h_Read; assembler;
{ H_READ - DOS Handle file function
  Description: Reads a memory block from file; fn=3Fh
  Returns: Actual number of bytes read }
Asm
@@1:
  PUSH DS
  LDS DX,Buffer
  MOV CX,Count
  MOV BX,Handle
  MOV AH,3Fh
  INT DOS
  POP DS
  MOV DOSResult,dosrOk
  JNC @@2
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnRead  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
@@2:
End; { h_Read }

Function h_Write; assembler;
{ H_WRITE - DOS Handle file function
  Description: Writes a memory block to file; fn=40h
  Returns: Actual number of bytes written }
Asm
@@1:
  PUSH DS
  LDS DX,Buffer
  MOV CX,Count
  MOV BX,Handle
  MOV AH,40h
  INT DOS
  POP DS
  MOV DOSResult,dosrOk
  JNC @@2
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnWrite  { store function code }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
@@2:
End; { h_Write }

Function h_Seek; assembler;
{ H_SEEK - DOS Handle file function
  Description: Seeks to a specified file position; fn=42h
               Start is one of the (sk) constants and points to a relative
               seek offset position
  Returns: Current file position if successful, 0 - otherwise }
Asm
@@1:
  MOV CX,WORD PTR [SeekPos+2]
  MOV DX,WORD PTR [SeekPos]
  MOV BX,Handle
  MOV AL,Start
  MOV AH,42h
  MOV DOSResult,dosrOk
  INT DOS
  JNC @@2
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnSeek  { store function number }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
@@2:
End; { h_Seek }

Function h_FilePos;
{ H_GETPOS - DOS Handle file function
  Description: Calls h_Seek to determine file active position
  Returns: Current file (seek) position number in long integer }
Begin
  h_FilePos := h_Seek(Handle, 0, skPos)
End; { h_FilePos }

Function h_FileSize;
{ H_FILESIZE - DOS Handle file function
  Description: Determines file size
  Returns: File size in bytes }
var SavePos, Size : longint;
Begin
  SavePos := h_FilePos(Handle);
  h_FileSize := h_Seek(Handle, 0, skEnd);
  h_Seek(Handle, SavePos, skStart)
End; { h_FileSize }

Function h_Eof; assembler;
{ H_EOF - DOS Handle file function
  Description: Checks if the current file position is equal to file size
               and then returns True
  Returns: True if end of file detected, False - otherwise }
var Size : longint;
Asm
  PUSH Handle
  CALL h_FileSize               { Get file size in AX:DX }
  MOV WORD PTR [Size],AX        { Store high word }
  MOV WORD PTR [Size+2],DX      { Store low word }
  PUSH Handle
  CALL h_FilePos                 { Get current file position }
  XOR CL,CL
  CMP AX,WORD PTR [Size]
  JNE @@1
  CMP DX,WORD PTR [Size+2]
  JNE @@1
  MOV CL,True
@@1:
  MOV AL,CL
End; { h_GetPos }

Function h_GetFTime; assembler;
{ H_GETFTIME - DOS Handle file function
  Description: Returns file update date and time values; fn=5700h
  Returns: Date and time values in long integer
           or negative DOS error code if an error occured }
Asm
@@1:
  MOV BX,Handle
  MOV AX,5700h { read date and time }
  MOV DOSResult,dosrOk
  INT DOS
  JNC @@2
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnGetFDateTime  { store function number }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@2:
End; { h_GetFTime }

Function h_SetFTime; assembler;
{ H_SETFTIME - DOS Handle file function
  Description: Sets file date and time; fn=5701h
  Returns: New date and time values in long integer
           or negative DOS error code if an error occured }
Asm
@@1:
  MOV CX,WORD PTR [DateTime]
  MOV DX,WORD PTR [DateTime+2]
  MOV BX,Handle
  MOV AX,5701h { read date and time }
  MOV DOSResult,dosrOk
  INT DOS
  JNC @@2
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnSetFDateTime  { store function number }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@2:
End; { h_SetFTime }

Function h_CloseFile; assembler;
{ H_CLOSEFILE - DOS Handle file function
  Description: Closes open file; fn=3Eh
  Returns: 0 if successful, negative DOS error code otherwise }
Asm
@@1:
  MOV BX,Handle
  MOV AH,3Eh
  INT DOS
  JC  @@2
  XOR AX,AX
  MOV DOSResult,dosrOk
  JMP @@3
@@2:
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnCloseFile  { store function number }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  POP AX
  NEG AX
@@3:
End; { h_CloseFile }

Function MemAlloc; assembler;
Asm
@@1:
  MOV DOSResult,dosrOk
  MOV AX,WORD PTR [Size]
  MOV DX,WORD PTR [Size+2]
  MOV CX,16
  DIV CX
  INC AX
  MOV BX,AX
  MOV AH,48h
  INT DOS
  JNC @@2
  MOV DOSResult,AX { save error code in global variable }
  PUSH AX     { store error code }
  PUSH fnMemAlloc  { store function number }
  CALL ErrorHandler
  CMP AL,frRetry
  JE  @@1
  XOR AX,AX
@@2:
  MOV DX,AX
  XOR AX,AX
End; { MemAlloc }

Function MemFree; assembler;
Asm
  MOV DOSResult,dosrOk
  MOV ES,WORD PTR [P+2]
  MOV AH,49h
  INT DOS
  JNC @@1
  MOV DOSResult,AX
  PUSH AX
  PUSH fnMemFree
  CALL ErrorHandler
@@1:
  MOV AX,DOSResult
  NEG AX
End; { MemFree }

Function FileSearch; assembler;
{ FileSearch searches for the file given by Name in the list of }
{ directories given by List. The directory paths in List must   }
{ be separated by semicolons. The search always starts with the }
{ current directory of the current drive. If the file is found, }
{ FileSearch stores a concatenation of the directory path and   }
{ the file name in Dest. Otherwise FileSearch stores an empty   }
{ string in Dest. The maximum length of the result is defined   }
{ by the fsPathName constant. The returned value is Dest.       }
Asm
  PUSH DS
  CLD
  LDS SI,List
  LES DI,Dest
  MOV CX,fsPathName
@@1:
  PUSH DS
  PUSH SI
  JCXZ @@3
  LDS SI,Name
@@2:
  LODSB
  OR  AL,AL
  JE  @@3
  STOSB
  LOOP @@2
@@3:
  XOR AL,AL
  STOSB
  LES DI,Dest
  MOV AX,4300H
  CALL AnsiDosFunc
  POP SI
  POP DS
  JC  @@4
  TEST CX,18H
  JE  @@9
@@4:
  LES DI,Dest
  MOV CX,fsPathName
  XOR AH,AH
  LODSB
  OR  AL,AL
  JE  @@8
@@5:
  CMP AL,';'
  JE  @@7
  JCXZ @@6
  MOV AH,AL
  STOSB
  DEC CX
@@6:
  LODSB
  OR  AL,AL
  JNE @@5
  DEC SI
@@7:
  JCXZ @@1
  CMP AH,':'
  JE  @@1
  MOV AL,'\'
  CMP AL,AH
  JE  @@1
  STOSB
  DEC CX
  JMP @@1
@@8:
  STOSB
@@9:
  MOV AX,WORD PTR [Dest]
  MOV DX,WORD PTR [Dest+2]
  POP DS
End; { FileSearch }

Function FileExpand; assembler;
{ FileExpand fully expands the file name in Name, and stores    }
{ the result in Dest. The maximum length of the result is       }
{ defined by the fsPathName constant. The result is an all }
{ upper case string consisting of a drive letter, a colon, a }
{ root relative directory path, and a file name. Embedded '.' }
{ and '..' directory references are removed, and all name and }
{ extension components are truncated to 8 and 3 characters. The }
{ returned value is Dest.                }

Asm
  PUSH DS
  CLD
  LDS SI,Name
  LEA DI,TempStr
  PUSH SS
  POP ES
  LODSW
  OR  AL,AL
  JE  @@1
  CMP AH,':'
  JNE @@1
  CMP AL,'a'
  JB  @@2
  CMP AL,'z'
  JA  @@2
  SUB AL,20H
  JMP @@2
@@1:
  DEC SI
  DEC SI
  MOV AH,19H
  INT DOS
  ADD AL,'A'
  MOV AH,':'
@@2:
  STOSW
  CMP [SI].Byte,'\'
  JE  @@3
  SUB AL,'A'-1
  MOV DL,AL
  MOV AL,'\'
  STOSB
  PUSH DS
  PUSH SI
  MOV AH,47H
  MOV SI,DI
  PUSH ES
  POP DS
  INT DOS
  POP SI
  POP DS
  JC  @@3
  XOR AL,AL
  CMP AL,ES:[DI]
  JE  @@3
{$IFDEF Windows}
  PUSH ES
  PUSH ES
  PUSH DI
  PUSH ES
  PUSH DI
  CALL OemToAnsi
  POP ES
{$ENDIF}
  MOV CX,0FFFFH
  XOR AL,AL
  CLD
  REPNE SCASB
  DEC DI
  MOV AL,'\'
  STOSB
@@3:
  MOV CX,8
@@4:
  LODSB
  OR  AL,AL
  JE  @@7
  CMP AL,'\'
  JE  @@7
  CMP AL,'.'
  JE  @@6
  JCXZ @@4
  DEC CX
{$IFNDEF Windows}
  CMP AL,'a'
  JB  @@5
  CMP AL,'z'
  JA  @@5
  SUB AL,20H
{$ENDIF}
@@5:
  STOSB
  JMP @@4
@@6:
  MOV CL,3
  JMP @@5
@@7:
  CMP ES:[DI-2].Word,'.\'
  JNE @@8
  DEC DI
  DEC DI
  JMP @@10
@@8:
  CMP ES:[DI-2].Word,'..'
  JNE @@10
  CMP ES:[DI-3].Byte,'\'
  JNE @@10
  SUB DI,3
  CMP ES:[DI-1].Byte,':'
  JE  @@10
@@9:
  DEC DI
  CMP ES:[DI].Byte,'\'
  JNE @@9
@@10:
  MOV CL,8
  OR  AL,AL
  JNE @@5
  CMP ES:[DI-1].Byte,':'
  JNE @@11
  MOV AL,'\'
  STOSB
@@11:
  LEA SI,TempStr
  PUSH SS
  POP DS
  MOV CX,DI
  SUB CX,SI
  CMP CX,79
  JBE @@12
  MOV CX,79
@@12:
  LES DI,Dest
  PUSH ES
  PUSH DI
{$IFDEF Windows}
  PUSH ES
  PUSH DI
{$ENDIF}
  REP MOVSB
  XOR AL,AL
  STOSB
{$IFDEF Windows}
  CALL AnsiUpper
{$ENDIF}
  POP AX
  POP DX
  POP DS
End; { FileExpand }

{$W+}
Function FileSplit;
{ FileSplit splits the file name specified by Path into its     }
{ three components. Dir is set to the drive and directory path  }
{ with any leading and trailing backslashes, Name is set to the }
{ file name, and Ext is set to the extension with a preceding   }
{ period. If a component string parameter is NIL, the           }
{ corresponding part of the path is not stored. If the path     }
{ does not contain a given component, the returned component    }
{ string is empty. The maximum lengths of the strings returned  }
{ in Dir, Name, and Ext are defined by the fsDirectory,         }
{ fsFileName, and fsExtension constants. The returned value is  }
{ a combination of the fcDirectory, fcFileName, and fcExtension }
{ bit masks, indicating which components were present in the    }
{ path. If the name or extension contains any wildcard          }
{ characters (* or ?), the fcWildcards flag is set in the       }
{ returned value.                                               }
var
  DirLen, NameLen, Flags : word;
  NamePtr, ExtPtr : PChar;
begin
  NamePtr := StrRScan(Path, '\');
  if NamePtr = nil then NamePtr := StrRScan(Path, ':');
  if NamePtr = nil then NamePtr := Path else Inc(NamePtr);
  ExtPtr := StrScan(NamePtr, '.');
  if ExtPtr = nil then ExtPtr := StrEnd(NamePtr);
  DirLen := NamePtr - Path;
  if DirLen > fsDirectory then DirLen := fsDirectory;
  NameLen := ExtPtr - NamePtr;
  if NameLen > fsFilename then NameLen := fsFilename;
  Flags := 0;
  if (StrScan(NamePtr, '?') <> nil) or (StrScan(NamePtr, '*') <> nil) then
    Flags := fcWildcards;
  if DirLen <> 0 then Flags := Flags or fcDirectory;
  if NameLen <> 0 then Flags := Flags or fcFilename;
  if ExtPtr[0] <> #0 then Flags := Flags or fcExtension;
  if Dir <> nil then StrLCopy(Dir, Path, DirLen);
  if Name <> nil then StrLCopy(Name, NamePtr, NameLen);
  if Ext <> nil then StrLCopy(Ext, ExtPtr, fsExtension);
  FileSplit := Flags;
End; { FileSplit }
{$W-}

Function StdErrorProc(ErrCode : integer; FuncCode : word) : byte; far;
assembler;
{ Default error handler procedure called from EnhDOS functions }
Asm
  MOV AL,frOk   { Return zero }
End; { StdErrorProc }


const WrongDOSVersion : PChar = 'DOS 3.1 or greater required.'#13#10'$';

Begin
  asm
    MOV AH,30h { Get DOS version }
    INT DOS
    CMP AL,3
    JGE @@continue { if greater than or equal to 3 then continue else exit }
    PUSH DS
    LDS DX,WrongDOSVersion
    MOV AH,09h
    INT DOS
    MOV AH,4Ch
    INT DOS
  @@continue:
    LES DI,Copyright
  end;
  DOSResult := dosrOk;
  SetErrorHandler(StdErrorProc)
End. { EnhDOS+ }

{ -------------------------------------   DEMO ------------------   }
{ ***** ENHDDEMO.PAS ***** }

Program DemoEnhDOS;
{ Copyright (c) 1994 by Andrew Eigus   Fido Net 2:5100/33 }
{ EnhDOS+ (Int21) demo program }

{$M 8192,0,0}
{ no heap size, couz using own memeory allocation }

(* Simple copy file program *)

uses EnhDOS, Strings;

const BufSize = 65535; { may be larger; you may allocate more }

var
  Buffer : pointer;
  InputFile, OutputFile : array[0..63] of Char;
  Handle1, Handle2 : THandle;
  BytesRead : word;

Function Int21ErrorHandler(ErrCode : integer; FuncCode : word) : byte; far;
var fn : array[0..20] of Char;
Begin
  case FuncCode of
    fnOpenFile: StrCopy(fn, 'h_OpenFile');
    fnCreateFile: StrCopy(fn, 'h_CreateFile');
    fnRead: StrCopy(fn, 'h_Read');
    fnWrite: StrCopy(fn, 'h_Write');
    fnSeek: StrCopy(fn, 'h_Seek');
    fnCloseFile: StrCopy(fn, 'h_CloseFile');
    fnMemAlloc: StrCopy(fn, 'MemAlloc');
    fnDeleteFile: Exit;
    else fn[0] := #0
  end;
  WriteLn('DOS Error ', ErrCode, ' in function ', FuncCode, ' (', fn, ')');
  { actually for function return code see fr consts in the EnhDOS const
    section }
End; { Int21ErrorHandler }

Begin
  SetErrorHandler(Int21ErrorHandler);

  WriteLn('EnhDOS+ demo program: copies one file to another');
  repeat
    if ParamCount > 0 then
      StrPCopy(InputFile, ParamStr(1))
    else
    begin
      Write('Enter file name to read from: ');
      ReadLn(InputFile)
    end;
    if ParamCount > 1 then
      StrPCopy(OutputFile, ParamStr(2))
    else
    begin
      Write('Enter file name to write to:  ');
      ReadLn(OutputFile)
    end;
    WriteLn
  until (StrLen(InputFile) > 0) and (StrLen(OutputFile) > 0);

  if not ExistsFile(InputFile) then
  begin
    WriteLn('File not found: ', InputFile);
    Halt(1)
  end;

  Buffer := MemAlloc(BufSize);

  Write('Copying... ');

  Handle1 := h_OpenFile(InputFile, omRead);
  if Handle1 <> 0 then
  begin
    DeleteFile(OutputFile);
    Handle2 := h_CreateFile(OutputFile);
    if Handle2 <> 0 then
    begin
      BytesRead := 1;

      while (BytesRead > 0) and (DOSResult = dosrOk) do
      begin
        BytesRead := h_Read(Handle1, Buffer^, BufSize);

        if DOSResult <> dosrOk then
          { read error then }
          WriteLn('Error reading from input file');

        if h_Write(Handle2, Buffer^, BytesRead) <> BytesRead then
          { write error then }
        begin
          WriteLn('Error writing to output file');
          DOSResult := $FF
        end
      end;
      if DOSResult = dosrOk then WriteLn('File copied OK');
      h_CloseFile(Handle2)
    end;
    h_CloseFile(Handle1)
  end;

  MemFree(Buffer)
End. { DemoEnhDOS }