home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
turpas70.zip
/
GDOS.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
2001-11-05
|
364KB
|
10,048 lines
{***************************************************************************}
{ }
{ Turbo Pascal Version 7.0 }
{ Graphic Vision Unit }
{ }
{ Copyright (c) 1997-2001 Jason G Burgon }
{ http://www.jayman.demon.co.uk }
{ email: jay@jayman.demon.co.uk }
{ }
{ Version: 2.20 }
{ Date : 3rd November 2001 }
{ }
{ This unit is released as "freeware". No liability is accepted for its use.}
{ It is released merely in the hope that it may be of use others. YOU must }
{ decide if this code is suitable for any purpose to which you put it. }
{ }
{ You can use the COMPILED code as part of any application you want, be it }
{ commercial or free. You can distribute the source code, the intermediate }
{ files GDOS.TPU and GDOS.TPP and its corresponding documentation ONLY if: }
{ }
{ (1) You did not obtain this software as part of the Graphic Vision(TM) }
{ software development package. }
{ }
{ (2) No part of the original distribution is changed in any way, including }
{ this statement. }
{ }
{ (3) You make no charge what so ever. No exceptions. }
{ }
{ (4) You do not include GDOS or its documentation as a part of a larger }
{ programming library without the copyright holders express written }
{ permission. }
{ }
{ All other rights not expressely given up in this statement are retained by}
{ the copyright holder. }
{ }
{---------------------------------------------------------------------------}
{ }
{ The GDOS API is fully documented in the GDOS.TPH (or GVISION.TPH) IDE help}
{ file. Please add GDOS.TPH to your Borland/Turbo Pascal 7.0 IDE's help }
{ files list (Help|Files - New) to obtain a detailed description of all }
{ public functions, variables, types and symbolic constants declared by this}
{ unit. It also contains How-to's, O/S information and code examples. }
{ }
{ If you have found and fixed a bug, or have improved on the original code, }
{ please send me your modified source code, plus documented details of the }
{ changes you have made, and I will consider them for inclusion in a later }
{ version. Please check you have the most recent version before reporting a }
{ any bugs, and please keep to the coding style and naming convensions used }
{ thoughtout this unit. I will not look at anything that is too unlike it. }
{ }
{ This GDOS unit was originally written as part of my Graphic Vision(tm) }
{ professional DOS/DPMI Graphical User Interface programming package. }
{ Please visit my website to download Graphic Vision, GV example programs, }
{ and to obtain the latest version of GDOS. }
{ }
{---------------------------------------------------------------------------}
{ }
{ Compilation Notes: }
{ }
{ The Windows version of this unit is completely untested, so will almost }
{ certainly not work properly when this unit is used as part of a native }
{ Windows 3.x program. Quite a few GDOS functions should probably not be }
{ part of the Windows version either. It needs someone with good knowledged }
{ of Win3x programming to get GDOS in shape for Win3.x native applications. }
{ }
{ User Settable Conditional Defines: }
{ }
{ LongNames - Produces a unit that can handle Windows 9.x long filenames. }
{ A few of the structures, string types and constants are }
{ redefined in order to make this possible. All System unit }
{ functions dealing with file/directory names are "hooked" so }
{ that they too handle long filenames. LongNames is defined }
{ automatically only for the Windows verion of this unit. }
{ }
{ TurboDos - Produces a smaller GDOS.TPP unit by using more assembler and }
{ calling those int $21 DOS functions that are known to be }
{ supported by the commonly used DPMI extenders. }
{ }
{ XMS30 - Uses XMS version 3.0 "super-extended" XMS functions for the }
{ (realmode-only) XMS extended memory interface. 3.0 functions }
{ make it possible to have access to all extendended memory, not}
{ just the first 64MB. }
{ }
{ The defualt DPMI version of GDOS is compiled with TurboDos undefined. You }
{ can set it on or off for both the DOS and DPMI versions of this unit if }
{ you wish. Defining TurboDos for DPMI means you're going to rely on a DOS }
{ extender, making some functions smaller, but probably slower and less }
{ robust. Clearing it for the DOS real mode version means larger, slower }
{ code will be produced - more of it will be Pascal and not assembler. }
{ }
{ The default version of GDOS.TPU is compiled with XMS30 undefined. This }
{ means that GDOS uses the XMS 2.0 API functions, so an XMS 3.0 compliant }
{ driver is not required. This is because some versions of HIMEM.SYS (such }
{ as that supplied with OpenDos 7.02) say they are 3.0 compliant when in }
{ fact they are not - use HIMEM.SYS supplied Windows 9x/ME instead. }
{ }
{***************************************************************************}
unit GDos;
{$I-,S-,G+,X+,F-,O-}
{$C FIXED PRELOAD PERMANENT}
interface
{$ifdef MSDOS}
{$define TurboDos} { Dos real mode apps don't use a DOS extender }
{$define TurboLong} { so Int 21 calls are used for everything, incl}
{.$define XMS30} { Remove the '.' to use the XMS 3.0 API }
{$endif MSDOS} { LFN functions when LongNames is defined. }
{$ifdef DPMI}
{$ifdef LongNames} { LFN functions are not supported by the Win9x }
{$undef TurboLong} { or other DOS extenders, so LFN functions must}
{$endif LongNames} { be called using DPMI server calls. }
{$endif DPMI}
{$ifdef Windows}
{$define TurboDos} { Win3.x/9x servers are known to support the }
{$define LongNames} { int 21 functions used in this unit. Long }
{$undef TurboLong} { filename functions are not though. }
{$endif Windows}
{ Simple types }
type
DWord = Longint;
PDword = ^DWord;
QWord = packed record
Lo: DWord;
Hi: DWord;
end;
PQWord = ^QWord;
{ Type conversion records }
WordRec = packed record
Lo, Hi: Byte;
end;
LongRec = packed record
Lo, Hi: Word;
end;
PtrRec = packed record
Ofs, Seg: Word;
end;
{ Simple pointer types }
NearPtr = Word;
PByte = ^Byte;
PShortInt = ^ShortInt;
PWord = ^Word;
PInteger = ^Integer;
PLongint = ^Longint;
PString = ^String;
PBoolean = ^Boolean;
PWordBool = ^WordBool;
{ Character set type }
PCharSet = ^TCharSet;
TCharSet = set of Char;
{ General arrays }
PByteArray = ^TByteArray;
TByteArray = packed array[0..65534] of Byte;
PWordArray = ^TWordArray;
TWordArray = packed array[0..32766] of Word;
PIntArray = ^TIntArray;
TIntArray = packed array[0..32766] of Integer;
PLongArray = ^TLongArray;
TLongArray = packed array[0..16382] of Longint;
PCharArray = ^TCharArray;
TCharArray = packed array[0..65534] of Char;
const
{ ASCII codes }
asNull = #0;
asBell = #7;
asBackSpace = #8;
asTab = #9;
asLF = #10;
asCR = #13;
asEOF = #26;
asEscape = #27;
asSpace = #32;
asDelete = #127;
{ Interrupt numbers }
intDos = $21;
{$ifdef DPMI}
{ DPMI interrupt number }
IntDPMI = $31;
{ DPMI function codes }
dpmiAllocSeg = $0000; { Allocate selector }
dpmiFreeSeg = $0001; { Free selector }
dpmiMapRealSeg = $0002; { Map real-mode segment to selector }
dpmiAllocSegs = $0003; { Allocate multiple selectors }
dpmiGetBaseAdr = $0006; { Get linear base address of segment }
dpmiSetSegBase = $0007; { Set selector base address }
dpmiSetSegSize = $0008; { Set selector size }
dpmiSetAccess = $0009; { Set selector access rights & type }
dpmiCloneSeg = $000A; { Create new selector with same props}
dpmiGetDesc = $000B; { Copy selectors LDT into 8-byte buf }
dpmiSetDesc = $000C; { Set selectors LDT from 8-byte buf }
dpmiGetRealInt = $0200; { Get real mode interrupt vector }
dpmiSetRealInt = $0201; { Set real mode interrupt vector }
dpmiGetExcpInt = $0202; { Get protected exception vector }
dmpiSetExcpInt = $0203; { Set protected exception vector }
dpmiGetProtInt = $0204; { Get protected mode interrupt vector}
dpmiSetProtInt = $0205; { Set protected mode interrupt vector}
dpmiCallRealInt = $0300; { Call real-mode interrupt }
dpmiCallRealFar = $0301; { Call far real-mode procedure }
dpmiCalliret = $0302; { Call real-mode with IRET frame }
dpmiAllocRMCB = $0303; { Allocate real mode call-back }
dpmiFreeRMCB = $0304; { Free real mode call-back }
dpmiGetInfo = $0400; { Get DPMI server information }
dpmiDisableInt = $0900; { Get and disable virtual intr state }
dpmiEnableInt = $0901; { Get and enable virtual intr state }
dpmiGetIntState = $0902; { Get virtual interrupt state }
{$endif DPMI}
{ Flags bit masks }
fCarry = $0001;
fParity = $0004;
fAuxiliary = $0010;
fZero = $0040;
fSign = $0080;
fOverflow = $0800;
{ File mode magic numbers }
fmClosed = $D7B0;
fmInput = $D7B1;
fmOutput = $D7B2;
fmInOut = $D7B3;
{ File attribute constants }
faReadOnly = $01;
faHidden = $02;
faSysFile = $04;
faVolumeID = $08;
faDirectory = $10;
faArchive = $20;
faAnyFile = faReadOnly + faHidden + faSysFile + faArchive;
faAnything = faAnyFile + faDirectory;
faReqReadOnly = faReadOnly * 256;
faReqHidden = faHidden * 256;
faReqSysFile = faSysFile * 256;
faReqVolumeID = faVolumeID * 256;
faReqDirectory = faDirectory* 256;
faReqArchive = faArchive * 256;
{ Volume attribute constants }
vaCaseSensitive = $0001; { Directory searches are case sensitive }
vaCasePreserve = $0002; { Preserves case in directory entries }
vaUnicodeChars = $0004; { Unicode chars used in file & dir names }
vaIsNetWorkDrive = $0100; { Volume is a network drive }
vaIsRemoveable = $0200; { Drive media is removable }
vaHasChangeLine = $0400; { Drive media supports ChangeLine mech }
vaNoDiskInDrive = $0800; { No disk in drive - other flags unknown }
vaDosLongNames = $4000; { Volume supports Long filename functions }
vaCompressed = $8000; { Volume is a compressed drive }
{ File path component flags - passed to FExpand and returned by FileSplit}
fcExtension = $0001;
fcFileName = $0002;
fcDirectory = $0004;
fcWildcards = $0008;
fcCasePreserve = $0100; { FExpand only - don't modify file case }
fcNetPath = $0200; { FExpand only - don't convert net paths }
{ Date format constants }
dfUsa = 0; { Month:Day:Year }
dfEurope = 1; { Day:Month:Year }
dfJapan = 2; { Year:Month:Day }
{ Time format constants }
tf12Hour = 0;
tf24Hour = 1;
{ Currency format constants }
cfPreFix = $00; { Currency symbol(s) preceed value: $4.00}
cfPostFix = $01; { Currency symbol(s) follows value: 4.00$}
cfHasSpace = $02; { Put a space between : $ 4.00}
cfDecPoint = $04; { Use symbol for decimal point : 4$00 }
{ Country codes }
ccUnitedStates = $001;
ccCanadianFrench = $002;
ccLatinAmerica = $003;
ccNetherlands = $01F;
ccBelgium = $020;
ccFrance = $021;
ccSpain = $022;
ccHungary = $024; { not supported by DR DOS 5.0 }
ccYugoslavia = $026; { not supported by DR DOS 5.0 }
ccItaly = $027;
ccSwitzerland = $029;
ccCzechoslovakia = $02A; { not supported by DR DOS 5.0 }
ccAustria = $02B; { DR DOS 5.0 }
ccUnitedKingdom = $02C;
ccDenmark = $02D;
ccSweden = $02E;
ccNorway = $02F;
ccPoland = $030; { not supported by DR DOS 5.0 }
ccGermany = $031;
ccBrazil = $037; { not supported by DR DOS 5.0 }
ccEnglish = $03D; { Australia in DR DOS 5.0 }
ccJapan = $051; { DR DOS 5.0, MS-DOS 5.0+ }
ccKorea = $052; { DR DOS 5.0 }
ccChina = $056; { MS-DOS 5.0+ }
ccTaiwan = $058; { MS-DOS 5.0+ }
ccTurkey = $05A; { MS-DOS 5.0+ }
ccPortugal = $15F;
ccIceland = $162;
ccFinland = $166;
ccMiddleEast = $311; { Saudi Arabia DR DOS 5.0,MS-DOS 5.0+ }
ccIsrael = $3CC; { DR DOS 5.0,MS-DOS 5.0+ }
{ Block Device Type numbers }
const
dtFloppy360 = 0; dtFloppy1200 = 1; dtFloppy720 = 2; dtBigFloppySD= 3;
dtBigFloppyDD= 4; dtFixedDisk = 5; dtTapeDriv = 6; dtFloppy1440 = 7;
dtFloppy2880 = 8; dtUnknown = 9; dtNet1 = 10; dtNet2 = 11;
dtCdRom = 12; dtRam = 13; dtError = 255;
type
TMediaLevel = (mcNo, mcUnknown, mcNotReady, mcYes); { Media changed states}
const
{ Dos Extended Errors }
deUnknownErr = -1;
deNoError = 0;
deInvalidfunc = 1;
deFileNotFound = 2;
dePathNotFound = 3;
deNoHandles = 4;
deAccessDenied = 5;
deInvalidHandle = 6;
deCtrlBlkKilled = 7;
deNotEnoughMem = 8;
deBadMemBlock = 9;
deBadEnvironment = 10;
deInvalidFormat = 11;
deBadAccessCode = 12;
deDataInvalid = 13;
deInvalidDrive = 15;
deDelCurrentDir = 16;
deNotSameDevice = 17;
deNoMoreFiles = 18;
deWriteProtected = 19;
deUnknownUnit = 20;
deDriveNotReady = 21;
deUnknownCommand = 22;
deCRC = 23;
deBadStrucLen = 24;
deSeek = 25;
deUnknownMedia = 26;
deSectorNotFound = 27;
deNoPaper = 28;
deWriteFault = 29;
deReadFault = 30;
deGeneralFailure = 31;
deShareViolation = 32;
deLockViolation = 33;
deBadDiskChange = 34;
deFCBUnavailable = 35;
deShareBuffer = 36;
deCodePage = 37;
deOutOfInput = 38;
deNoDiskSpace = 39;
{ 4x are extra error codes defined by the GDOS unit, not the O/S }
deInvalidPath = 40; { Invalid character[s] in file/directory path }
deInvalidName = 41; { Invalid character[s] in FILENAME.EXT }
deNameTooLong = 42; { FILENAME.EXT is too long }
deDirTooLong = 43; { Directory component of path is too long }
dePathTooLong = 43; { Path (as a whole) is too long }
deExtTooLong = 44; { Extension component of a path is too long }
deNoWildCards = 45; { Path cannot contain wildcards }
deNetNoSupport = 50;
deNetNoListen = 51;
deNetDupName = 52;
deNetNameNoFound = 53;
deNetBusy = 54;
deNetNoExist = 55;
deNetBiosCmdLim = 56;
deNetAdaptHard = 57;
deNetBadResponse = 58;
deNetUnexpected = 59;
dePrintQueFull = 60;
deQueNotFull = 61;
deNoPrintSpace = 62;
deNetNameDeleted = 64;
deNetNoAccess = 65;
deNetDeviceType = 66;
deNetNameNotFnd = 67;
deNetNameTooLong = 68;
deNetBiosLimit = 69;
deNetTempPause = 70;
deNetBadRequest = 71;
deNetPauseRedrct = 72;
deNetNoSoftware = 73;
deNetBadAccount = 74;
deNetBadPassword = 75;
deNetBadLogin = 76;
deNetDiskLimit = 77;
deNetNotLogged = 78;
deFileExists = 80;
deNoMakeDir = 82;
deInt24Fail = 83;
deRedirections = 84;
deDupRedirect = 85;
deBadPassword = 86;
deBadParameter = 87;
deNetWriteFault = 88;
deNetBadFunction = 89;
deNoSystemComp = 90;
deCdUnknown = 100;
deCdNotReady = 101;
deCdBadEMS = 102;
deCdBadFormat = 103;
deCdDoorOpen = 104;
{ Programmable Interrupt Timer types }
pitEmulated = 0; { PIT is faulty or emulated by the O/S }
pit8253 = 1; { PIT is an 8253 }
pit8254 = 2; { PIT is an 8254 }
pitTimer0 = $40; { 8254 Timer Chip port addresses }
pitTimer1 = $41;
pitTimer2 = $42;
pitCtrl = $43;
{ FileOpen/TStream access modes }
stCreate = $3C00; { Create new file }
stOpenRead = $3D00; { Read access only }
stOpenWrite = $3D01; { Write access only }
stOpen = $3D02; { Read and write access }
{ File sharing constants: These can be added to the above access modes }
stDenyAll = $10; { Deny any type of access to all others }
stDenyWrite = $20; { Deny write access by all others }
stDenyNone = $40; { Allow read and write access by others }
stDenyChild = $80; { Deny access by child process }
type
{ File Seek modes }
TFileSeek = (skStart, { = $00 }{ Seek relative to start of file }
skCurrent,{ = $01 }{ Seek relative to current file position }
skEnd); { = $02 }{ Seek relative to end of file }
DosPtr = Pointer; { Pointer to a DOS/BIOS (real mode) memory block }
PDosPtr = ^DosPtr;
{ Filename case conversion }
TFileCase = (fnPreserve, fnLowerCase, fnUpperCase, fnDosLower,
fnDos1stUpper);
{ FileGetSetAttr operations }
TAttrOp = (faGet, faSet);
type
{ Block Device information record }
PBlockDevInfo = ^TBlockDevInfo;
TBlockDevInfo = packed record
SpecialFunc: Byte;
DeviceType : Byte; { See Block Device Type Numbers above }
DeviceAttr : Word; { See DeviceAttr bit-fields above }
Cylinders : Word;
MediaType : Byte;
BytesSect : Word; { Number of bytes per sector (eg 512) }
SectClust : Byte; { Number of sectors per allocation unit }
ResvSect : Word; { No. reserved sectors at start of the disk }
NumFATs : Byte; { No. File Allocation Tables }
RootEntries: Word; { Max No. of entries in the root directory }
TotalSect : Word; { Total sectors or 0 if >32MB (see NumHuge) }
MediaID : Byte;
SectPerFAT : Word; { Number of sectors per FAT }
SectTrack : Word; { Number of sectors per track }
NumHeads : Word; { Number of drive heads }
NumHidden : Longint; { Number of hidden sectors }
NumHuge : Longint; { Actual Number of sectors if TotalSect = 0 }
Unused : array[0..6] of byte;
end;
{ GetDiskInfo record }
PDiskInfo = ^TDiskInfo;
TDiskInfo = packed record
SectsPerCluster: Word;
BytesPerSector : Word;
ClustersFree : DWord;
ClustersTotal : DWord;
end;
{ String types }
TComStr = String[127]; { Command line string }
TVolLabel = String[11]; { For holding a volume name }
TRootStr = String[2]; { For holding root dir name (eg "A:")}
TFileSysName = String[31]; { For file system name (eg 'FAT') }
TNetName = String[127]; { For local or network drive names. }
TMachineName = String[15]; { For holding a LAN machine name }
TDateStr = String[10]; { For holding formated date string }
TTimeStr = String[13]; { For holding a formated time string }
TDosPath = String[79]; { For holding a Dos 8.3 path }
TDosName = String[12]; { For holding a Dos 8.3 filename }
TDosExt = String[4]; { For holding a Dos .EXT component }
{$ifdef LongNames}
TPathStr = String; { LFN File pathname string }
TDirStr = String[246]; { LFN Drive and directory string }
TNameStr = String; { LFN File name string }
TExtStr = String; { LFN File extension string }
TNameExt = String; { For holding a name + extension }
TNetPath = String; { For network paths }
{$else LongNames}
TPathStr = String[79]; { DOS File pathname string }
TDirStr = String[67]; { DOS Drive and directory string }
TNameStr = String[8]; { DOS File name string }
TExtStr = String[4]; { DOS File extension string }
TNameExt = String[12]; { For holding a "filename.ext" }
TNetPath = String[127]; { For network paths/directories }
{$endif LongNames}
PComStr = ^TComStr;
PVolLabel = ^TVolLabel;
PRootStr = ^TRootStr;
PFileSysName = ^TFileSysName;
PNetName = ^TNetName;
PDosPath = ^TDosPath;
PDosName = ^TDosName;
PDosExt = ^TDosExt;
PMachineName = ^TMachineName;
PDateStr = ^TDateStr;
PTimeStr = ^TTimeStr;
PPathStr = ^TPathStr;
PDirStr = ^TDirStr;
PNameStr = ^TNameStr;
PExtStr = ^TExtStr;
PNameExt = ^TNameExt;
PNetPath = ^TNetPath;
{ Maximum file name component string lengths }
const
{$ifdef LongNames}
fsPathName = 259;
fsNetPath = fsPathName;
{$else LongNames}
fsPathName = High(TPathStr);
fsNetPath = High(TNetPath);
{$endif LongNames}
fsDirectory = High(TDirStr);
fsExtension = High(TExtStr);
fsFileName = High(TNameStr);
fsNetName = High(TNetName);
fsNameExt = High(TNameExt);
fsDosPath = High(TDosPath);
fsDosName = High(TDosName);
fsDosExt = High(TDosExt);
fsMachineName= High(TMachineName);
fsDosDir = fsDosPath - fsDosName;
fsVolLabel = High(TVolLabel);
type
PVolumeInfo = ^TVolumeInfo;
TVolumeInfo = packed record
VmtOffset : NearPtr; { For converting to an object }
Next : PVolumeInfo; { Pointer to next TVolumeInfo in list}
Attributes : Word; { vaXXXX Volume attributes }
MediaState : TMediaLevel; { Last known state of the drive media}
Reserved : Byte; { Reserved for future use }
DriveType : Byte; { dtXXXX Drive type }
DriveName : TRootStr; { Local drive name - eg 'A:' }
NetName : PNetName; { Network drive name - eg '//machine'}
MaxNameLen : Word; { Maximum file/dir name.ext length }
MaxExtLen : Word; { Maximum file extension length }
MaxPathLen : Word; { Maximum full path length }
FileSysName: TFileSysName; { File system used (FAT, CDFS, NTFS) }
VolumeLabel: TVolLabel; { Volume label }
SerialNum : DWord; { Volume serial number }
end;
{ Create Volume information record }
FCreateVolume = function(Drive: Char): PVolumeInfo;
{ Registers record used by Intr, IntrPM, MsDos and MsDosPM }
PRegisters = ^TRegisters;
TRegisters = packed record
case Integer of
0: (
EDI,ESI,EBP,EXX,EBX,EDX,ECX,EAX: DWord;
Flags,ES,DS,FS,GS,IP,CS,SP,SS: Word);
1: (
DI,DIH,SI,SIH,BP,BPH,XX,XXH: Word;
case Integer 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;
{ Structure used to allocate and access a DOS Memory block }
TDosBuf = packed record
{$ifdef DPMI}
case Integer of
0: (Buf : Pointer; { Application far pointer }
RealBuf: DosPtr; { Real mode far pointer }
Size : Word); { Size of mem allocation }
1: (Ofs : Word; { Protected offset (zero) }
Seg : Word; { Protected selector }
RealOfs: Word; { Real mode offset (zero) }
RealSeg: Word); { Real mode segment }
{$else DPMI}
case integer of
0: (Buf : Pointer; { Application far pointer }
Size: Word); { Size of mem allocation }
1: (RealBuf: DosPtr); { Same as AppPtr in RM }
2: (RealOfs: Word; { Real mode offset (zero) }
RealSeg: Word); { Real mode segment }
3: (Ofs: Word; { Application offset (zero)}
Seg: Word); { Application segment }
{$endif DPMI}
end;
{ DOS file handle type }
TFileHandle = Word;
{ XMS handle type }
{$ifdef MsDos}
TXmsHandle = Word;
{$endif MsDos}
{ Redefined typed and untyped-file record }
PFileRec = ^TFileRec;
TFileRec = packed record
Handle : TFileHandle; { O/S File handle }
Mode : Word; { File access and sharing modes }
RecSize : Word; { Size of each file record in bytes }
{$ifdef LongNames}
NameLen : Word; { No. characters in filename incl #0 }
Private : array[1..24] of Byte; { Not used - don't use it though }
UserData : array[1..16] of Byte; { 16 bytes available for any use }
Name : PChar; { Pointer to ASCIIZ filename }
NameBuf : array[1..80-SizeOf(PChar)] of Char; { For names of < 76 chars}
{$else LongNames}
Private : array[1..26] of Byte; { Not used - don't use it though }
UserData : array[1..16] of Byte; { 16 bytes available for any use }
Name : array[0..79] of Char; { ASCIIZ filename buffer }
{$endif LongNames}
end;
{ Redefined Textfile record - same as DOS.PAS declaration without LongNames}
PTextBuf = ^TTextBuf;
TTextBuf = array[0..127] of Char;
FOpenText = function(var T: Text): Integer;
FCloseText = function(var T: Text): Integer;
FInOutText = function(var T: Text): Integer;
FFlushText = function(var T: Text): Integer;
PTextRec = ^TTextRec;
TTextRec = record
Handle : TFileHandle; { O/S File handle }
Mode : Word; { File access and sharing modes }
BufSize : Word; { Size of file buffr - 128 by default}
{$ifdef LongNames}
NameLen : Word; { No. characters in filename incl #0 }
{$else LongNames}
Private : Word;
{$endif LongNames}
BufPos : Word; { Current buffer position }
BufEnd : Word; { Pos of last valid byte in buf +1 }
BufPtr : PTextBuf; { Pointer to start of file buffer }
OpenFunc : FOpenText; { Pointer to "open file" function }
InOutFunc: FInOutText; { Pointer to "read/write" function }
FlushFunc: FFlushText; { Pointer to "flush file" function }
CloseFunc: FCloseText; { Pointer to "close file" function }
UserData : array[1..16] of Byte; { 16 bytes available for any use }
{$ifdef LongNames}
Name : PChar; { Pointer to ASCIIZ filename }
NameBuf : array[1..80-SizeOf(PChar)] of Char; { For names of < 76 chars}
{$else LongNames}
Name : array[0..79] of Char;
{$endif LongNames}
Buffer : TTextBuf;
end;
{ Search record used by FindFirst, FindNext and FindClose }
PSearchRec = ^TSearchRec;
TSearchRec = packed record
Fill : packed array[1..21] of Byte;
Attr : Byte;
Time : Longint;
Size : Longint;
{$ifdef Windows}
Name : array[0..fsNameExt] of Char;
{$else Windows}
Name : TNameExt;
{$endif Windows}
VolAttribs: Word;
Handle : Word;
AttrMask : Byte;
Reserved : Byte;
UserData : array[0..7] of Byte;
end;
{ Date and time records used by PackTime and UnpackTime }
PDateTime = ^TDateTime;
TDateTime = packed record
Year,Month,Day,Hour,Min,Sec: Word;
end;
{ Country-specific information }
PDosCountry = ^TDosCountry;
TDosCountry = packed record { 34 byte Country Dependant Information block }
DateFormat : Word; { Date format - see dfXXXX }
CurrencyStr : String[4]; { Currency symbol(s). eg $ }
ThouSep : array[0..1] of Char; { Thousands separator: eg 1,000 }
DecSep : array[0..1] of Char; { Decimal point char: eg 1.23 }
DateSep : array[0..1] of Char; { Date separator: eg 01-01-80 }
TimeSep : array[0..1] of Char; { Time separator: eg 06:45:12 }
CurrencyFormat: Byte; { See cfXXXX constants }
CurrencyDigits: Byte; { No. signif currency places }
TimeFormat : Byte; { 12/24 hour - see tfXXXX }
UpCase : DosPtr; { Dos UpCase function-don't call}
DataListSep : array[0..1] of Char;
Reserved : array[0..4] of Word;
CountryCode : Word; { Country Code - see cnXXXX }
end;
{ Public constants }
const
maxFileBlock = 65535; { Max bytes in a single file transfer }
{$ifdef DPMI}
maxDosMemBlock= 65535;
{$else DPMI}
maxDosMemBlock= 65520;
{$endif DPMI}
maxNullStrLen = maxDosMemBlock;
{ Public variables }
var
StrError : Integer; { String error status }
DosError : Word absolute InOutRes; { Error status variable }
DosErrClass : Word; { Error class and suggested action }
DosErrLocus : Byte;
DosVersion : Word; { High byte = Major, Low byte = minor }
DosCountry : TDosCountry; { Country-specific information }
const
{$ifdef TurboDos}
DosBufSize : Word = 1024; { Default DosBuf mem allocation size }
{$else TurboDos}
DosBufSize : Word = 4096; { Used for file transfers too }
{$endif TurboDos}
DosBuf : TDosBuf = (Buf: nil); { DOS memory buffer }
const
MasterPicBase: Byte = $08; { Primary PIC interrupt offset }
SlavePicBase : Byte = $70; { Secondary PIC interrupt offset }
const
VFat : Boolean = false; { Operating System supports LFN's }
FileCase : TFileCase = fnDos1stUpper; { Filename case convertion rule }
const
ExeDir : TDirStr = ''; { Drive & Directory path of the .EXE }
ExeName : TNameStr = ''; { Filename without extension of .EXE }
ExeExt : TExtStr = ''; { Extension of the .EXE, ie ".EXE" }
const
VolumeList : PVolumeInfo = nil; { Linked list of valid TVolumeInfo's }
CreateVolume: FCreateVolume = nil; { Create new volume info record/object}
const
TempDir : PDirStr = nil;
TempPrefix : String[5] = 'TEMP-'; { Temporary filename prefix }
{$ifdef MsDos}
const { Extra error codes for OvrResult }
ovrNoXMSDriver = -7; { No XMS driver found }
ovrNoXMSMemory = -8; { XMS memory error, eg not enough }
const
XmsInstalled: Boolean = false; { If XMS present. Set by XMSInitHeap }
XmsOverlays : Boolean = false; { If overlays are being stored in XMS }
XmsVersion : Word = 0; { 2-digit BCD Xms version number }
XmsFunc : Pointer = nil; { The XMS driver's entry point }
{$endif MsDos}
(***************************************************************************
Case-conversion tables. NOTE : The second half, (from #128 to #255), of
both tables is overwritten during unit initialization due to the call to
InitCountry. This will use the MSDos case mapping function for characters
>= #128 to refill that portion of the array.
****************************************************************************)
const
LoToUpTbl: array[#0..#255] of Char =
(#00,#01,#02,#03,#04,#05,#06,#07,#08,#09,#10,#11,#12,#13,#14,#15,
#16,#17,#18,#19,#20,#21,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,
' ','!','"','#','$','%','&',#39,'(',')','*','+',',','-','.','/',
'0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?',
'@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
'P','Q','R','S','T','U','V','W','X','Y','Z','[','\',']','^','_',
'`','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
'P','Q','R','S','T','U','V','W','X','Y','Z','{','|','}','~',#127,
'Ç','Ü','É','A','Ä','A','Å','Ç','E','E','E','I','I','I','Ä','Å',
'É','Æ','Æ','O','Ö','O','U','U','Y','Ö','Ü','¢','£','¥','₧','ƒ',
'A','I','O','U','Ñ','Ñ','ª','º','¿','⌐','¬','½','¼','¡','«','»',
'░','▒','▓','│','┤','╡','╢','╖','╕','╣','║','╗','╝','╜','╛','┐',
'└','┴','┬','├','─','┼','╞','╟','╚','╔','╩','╦','╠','═','╬','╧',
'╨','╤','╥','╙','╘','╒','╓','╫','╪','┘','┌','█','▄','▌','▐','▀',
'α','ß','Γ','π','Σ','σ','µ','τ','Φ','Θ','Ω','δ','∞','φ','ε','∩',
'≡','±','≥','≤','⌠','⌡','÷','≈','°','∙','·','√','ⁿ','²','■',#255);
UpToLoTbl: array[#0..#255] of Char =
(#00,#01,#02,#03,#04,#05,#06,#07,#08,#09,#10,#11,#12,#13,#14,#15,
#16,#17,#18,#19,#20,#21,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,
' ','!','"','#','$','%','&',#39,'(',')','*','+',',','-','.','/',
'0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?',
'@','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
'p','q','r','s','t','u','v','w','x','y','z','[','\',']','^','_',
'`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
'p','q','r','s','t','u','v','w','x','y','z','{','|','}','~',#127,
'ç','ü','é','â','ä','à','å','ç','ê','ë','è','ï','î','ì','ä','å',
'é','æ','æ','ô','ö','ò','û','ù','ÿ','ö','ü','¢','£','¥','₧','ƒ',
'á','í','ó','ú','ñ','ñ','ª','º','¿','⌐','¬','½','¼','¡','«','»',
'░','▒','▓','│','┤','╡','╢','╖','╕','╣','║','╗','╝','╜','╛','┐',
'└','┴','┬','├','─','┼','╞','╟','╚','╔','╩','╦','╠','═','╬','╧',
'╨','╤','╥','╙','╘','╒','╓','╫','╪','┘','┌','█','▄','▌','▐','▀',
'α','ß','Γ','π','Σ','σ','µ','τ','Φ','Θ','Ω','δ','∞','φ','ε','∩',
'≡','±','≥','≤','⌠','⌡','÷','≈','°','∙','·','√','ⁿ','²','■',#255);
{*************************** Unit initialization ***************************}
{ Allocate DosBufSize bytes to the DosBuf App <-> O/S data buffer. }
function DosInit: Boolean;
{ DeAllocate the memory assigned to the DosBuf App <-> O/S data buffer. }
procedure DosDone;
{***************** Application <-> Operating system interface **************}
{ GetDosMem should be used to allocate a memory block suitable for passing }
{ to a DOS or BIOS interrupt. Using MemAlloc guarantees that the memory }
{ block has been allocated from the 1st MB of memory. }
function GetDosMem(var Buf: TDosBuf; Size: Word): Boolean;
{ FreeDosMem disposes of a DOS buffer previously allocated with GetDosMem. }
procedure FreeDosMem(var Buf: TDosBuf);
{ Create an application pointer that points to the given DOS/BIOS memory }
function MapDosPtr(RealPtr: DosPtr): Pointer;
{$ifdef MSDOS}
inline ($58/$5A); { pop ax dx }
{$endif MSDOS}
{ Free a pointer previously allocated by MapDosPtr }
procedure FreeDosPtr(P: Pointer); inline (
{$ifdef DPMI}
$5A/$5B/ { pop dx bx }
$B8/>$0001/ { mov ax,dpmiFreeSeg }
$CD/$31); { int intDPMI }
{$else}
$5A/$59); { pop dx cx }
{$endif DPMI}
{ Intr executes a specified software interrupt with a specified TRegisters }
{ package. Returns the value of Regs.AX (as set by the real mode interrupt).}
{ Intr always call the real-mode (O/S) version of the interrupt. }
function Intr(IntNo: Byte; var Regs: TRegisters): Word;
{$ifdef DPMI} inline (
$5F/ { pop di }
$07/ { pop es ES:DI = @Regs }
$B8/>$0300/ { mov ax,dpmiCallRealInt Simulate real-mode interrupt }
$5B/ { pop bx BL = IntNo }
$31/$C9/ { xor cx,cx No stack transfer }
$B7/$00/ { mov bh,0 BH must be zero }
$CD/$31/ { int intDPMI }
$26/$8B/$45/$1C); { mov ax,[es:di+TRegisters.AX] }
{$endif DPMI}
{ IntrApp calls protected-mode interrupt "IntNo". This differs from Intr }
{ in protected mode because Intr will call the real-mode version of the }
{ given interrupt number. IntrApp is the same as Intr for real-mode programs}
function IntrApp(IntNo: Byte; var Regs: TRegisters): Word;
{ MsDos invokes the DOS function call handler with a specified Registers }
{ package. Returns the value of Regs.AX (as set by the real mode interrupt).}
{ MsDos always calls the real mode version of interrupt $21. }
function MsDos(var Regs: TRegisters): Word;
{$ifdef DPMI} inline (
$BB/>$0021/ { mov bx,21h Dos interrupt 21h (BH must be 0)}
$5F/ { pop di }
$31/$C9/ { xor cx,cx No stack transfer }
$07/ { pop es ES:DI = @Regs }
$B8/>$0300/ { mov ax,dpmiCallRealInt Call real-mode interrupt in BL}
$CD/$31/ { int intDPMI }
$26/$8B/$45/$1C); { mov ax,[es:di+TRegisters.AX] }
{ MsDosPM is only avaiable in protected mode. It invokes the DPMI server's }
{ Int $21 DOS function dispatch emulator. This should only be called while }
{ converting existing applications. }
function MsDosPM(var Regs: TRegisters): Word;
{$endif DPMI}
{ Call a real mode function with far call (16:16) return stack frame. The }
{ real mode address of the function to be called must be in Regs.CS and }
{ Regs.IP. Returns the value of Regs.AX (as set by the real mode function). }
function DosFarCall(var Regs: TRegisters): Word;
{$ifdef DPMI} inline (
$31/$DB/ { xor bx,bx BH must be zero }
$5F/ { pop di }
$B8/>$0301/ { mov ax,dpmiCallRealFar }
$07/ { pop es ES:DI = @Regs }
$31/$C9/ { xor cx,cx No stack transfer }
$CD/$31/ { int intDPMI Call real mode far procedure }
$26/$8B/$45/$1C); { mov ax,[es:di+TRegisters.AX] }
{$endif DPMI}
{ Call a real mode function with an IRET return stack frame. The real mode }
{ address of the function to be called must be in Regs.CS and Regs.IP. }
{ Returns the value of Regs.AX (as set by the real mode function). This is }
{ used to chain an original DOS/BIOS software interrupt (eg Video BIOS }
{ interrupt $10) from inside a user-installed interrupt service routine. }
function DosSoftIntr(var Regs: TRegisters): Word;
{$ifdef DPMI} inline (
$31/$DB/ { xor bx,bx BH must be zero }
$5F/ { pop di }
$B8/>$0302/ { mov ax,dpmiCalliret }
$07/ { pop es ES:DI = @Regs }
$31/$C9/ { xor cx,cx No stack transfer }
$CD/$31/ { int intDPMI Call real-mode IRET procedure }
$26/$8B/$45/$1C); { mov ax,[es:di+TRegisters.AX] }
{$endif DPMI}
{ Call a DOS/BIOS real mode function with an IRET return stack frame. This }
{ is used to call hardware interrupts (eg system clock interrupt $08) from }
{ inside a user-installed hardware interrupt service routine. }
procedure DosHardIntr(RealAddr: DosPtr); inline (
{$ifdef DPMI}
$58/ { pop ax Pop interrupt addr into DX:AX }
$5A/ { pop dx }
$55/ { push bp Save stack frame pointer }
$83/$EC/$32/ { sub sp,type TRegisters Make space on stack for Regs }
$31/$DB/ { xor bx,bx BH must be zero for Int 31h }
$89/$E5/ { mov bp,sp }
$89/$5E/$26/ { mov [bp+TRegisters.&FS],bx Set Regs.FS & GS to zero. }
$8C/$D1/ { mov cx,ss Set ES:DI to index pseudo }
$89/$5E/$28/ { mov [bp+TRegisters.&GS],bx Regs on the stack (@SS:BP)}
$89/$EF/ { mov di,bp Set Regs.CS:IP to IntrAdr }
$89/$46/$2A/ { mov [bp+TRegisters.&IP],ax }
$8E/$C1/ { mov es,cx }
$89/$56/$2C/ { mov [bp+TRegisters.&CS],dx }
$B8/$02/$03/ { mov ax,dpmiCalliret Call real-mode IRET procedure }
$31/$C9/ { xor cx,cx No stack transfer }
$CD/$31/ { int intDPMI Call the interrupt function }
$83/$C4/$32/ { add sp,type TRegisters Restore stack and stack frame }
$5D); { pop bp pointer }
{$else DPMI}
$55/ { push bp Save stack frame pointer }
$89/$E5/ { mov bp,sp Set bp to @RealAddr }
$9C/ { pushf Push Flags for the IRET return }
$FF/$5E/$02/ { call far ptr [bp+2] Call the interrupt function }
$5D/ { pop bp Restore stack frame pointer }
$83/$C4/$04); { add sp,(type Pointer) Pop the call address off stack }
{$endif DPMI}
{ Call an application side function with an IRET return stack frame. This }
{ is used to call software interrupts (eg video interrupt $10) from inside }
{ the user-installed software interrupt service routine that replaced it. }
function SoftIntr(var Regs: TRegisters): Word;
{ Call an application side function with an IRET return stack frame. This }
{ is used to call hardware interrupts (eg clock interrupt $08) from inside }
{ the user-installed hardware interrupt service routine that replaced it. }
procedure HardIntr(ISR: Pointer); inline (
$55/ { push bp Save stack frame pointer }
$89/$E5/ { mov bp,sp Set bp to @ISR }
$9C/ { pushf Push Flags for the IRET return }
$FF/$5E/$02/ { call far ptr [bp+2] Call the interrupt function }
$5D/ { pop bp Restore stack frame pointer }
$83/$C4/$04); { add sp,(type Pointer) Pop the call address off stack }
{ ClearRegs sets all register values to 0. This should be called before you }
{ set specific Regs fields prior a call to Intr, IntrApp, MsDos, MsDosPM etc}
procedure ClearRegs(var Regs: TRegisters); inline (
$5F/$07/ { pop di es }
$FC/ { cld }
$31/$C0/ { xor ax,ax }
$B9/>$19/ { mov cx,type TRegisters/2 }
$F3/$AB); { rep stosw }
{ PushAllRegs pushes DS, ES, EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI }
procedure PushAllRegs; inline (
$1E/$06/ { push ds es }
$66/$60); { pushad }
{ PopAllRegs pops EDI, ESI, EBP, EBX, EDX, ECX, EAX, ES, DS }
procedure PopAllRegs; inline (
$66/$61/ { popad }
$07/$1F); { pop es ds }
{ GetIntVec returns the address of the given application mode interrupt. }
{ This function returns the protected-mode interrupt vector in DMPI. }
function GetIntVec(IntNo: Byte): Pointer; inline (
{$ifdef DPMI}
$B8/>$0204/ { mov ax,dpmiGetProtInt Use DPMI sever to return the }
$5B/ { pop bx address of the protected mode int }
$CD/$31/ { int intDPMI }
$89/$D0/ { mov ax,dx }
$89/$CA); { mov dx,cx }
{$else DPMI}
$58/ { pop al [IntNo] Use DOS to return the real mode }
$B4/$35/ { mov ah,35h address of a real mode interrupt }
$CD/$21/ { int 21h vector. }
$8C/$C2/ { mov dx,es }
$89/$D8); { mov ax,bx }
{$endif DPMI}
{ GetDosIntVec returns a real-mode (DOS/BIOS) vector. This function is the }
{ same as GetIntVec in DOS real mode programs. }
function GetDosIntVec(IntNo: Byte): DosPtr; inline (
{$ifdef DPMI}
$B8/>$0200/ { mov ax,dpmiGetRealInt Use the DPMI sever to return the }
$5B/ { pop bx [IntNo] real mode address of a real mode }
$CD/$31/ { int intDPMI interupt vector. }
$89/$D0/ { mov ax,dx }
$89/$CA); { mov dx,cx }
{$else DPMI}
$58/ { pop al Use DOS to return the real mode }
$B4/$35/ { mov ah,35h address of a real mode interrupt }
$CD/$21/ { int 21h vector. }
$8C/$C2/ { mov dx,es }
$89/$D8); { mov ax,bx }
{$endif DPMI}
{ SetIntVec sets the address in the RM or PM interrupt vector table to the }
{ the specified application Vector. Sets PM interrupt vector in DPMI apps. }
function SetIntVec(IntNo: Byte; Vector: Pointer): Boolean; inline (
{$ifdef DPMI}
$5A/ { pop dx [word Vector] }
$B8/>$0205/ { mov ax,dpmiSetProtInt Use the DPMI server to set a }
$59/ { pop cx [word Vector+2] protected mode intrpt vector }
$5B/ { pop bx [IntNo] to a protected mode address. }
$CD/$31); { int intDPMI }
{$else DPMI}
$5A/ { pop dx pop Vector into DX:CX }
$59/ { pop cx }
$58/ { pop ax AL = IntNo }
$1E/ { push ds Save global DS }
$8E/$D9/ { mov ds,cx DS:DX = Vector }
$B4/$25/ { mov ah,25h Use DOS to set a real mode }
$CD/$21/ { int 21h interrupt vector to a real }
$1F); { pop ds mode address. }
{$endif DPMI}
{ SetDosIntVec sets the given real-mode (DOS/BIOS) mode vector to the given }
{ real-mode mode address. Same as SetIntVec in real-mode programs. }
function SetDosIntVec(IntNo: Byte; Vector: DosPtr): Boolean; inline (
{$ifdef DPMI}
$5A/ { pop dx [word Vector] Use the DPMI server to set a }
$B8/>$0201/ { mov ax,dpmiSetRealInt real mode interrupt vector to }
$59/ { pop cx [word Vector+2] a real mode address. }
$5B/ { pop bx [IntNo] BL = IntNo CX:DX = Vector }
$CD/$31); { int intDPMI }
{$else DPMI}
$5A/ { pop dx Pop Vector into DX:CX }
$59/ { pop cx }
$58/ { pop ax AL = IntNo }
$1E/ { push ds Save global DS }
$8E/$D9/ { mov ds,cx DS:DX = Vector }
$B4/$25/ { mov ah,25h Use DOS to set a real mode }
$CD/$21/ { int 21h interrupt vector to a real }
$1F); { pop ds mode address. }
{$endif DPMI}
{ Replace a standard DOS/BIOS interrupt vector with a user-installed }
{ interrupt service routine. Returns true if the function was successful. }
function HookDosIntr(IntNum: Byte; ISR: Pointer): Boolean;
{ Unhook a DOS/BIOS interrupt vector previously hooked by the application. }
function UnHookDosIntr(IntNum: Byte): Boolean;
{ Replace an application-side interrupt service routine. Returns True if }
{ the function was successful. }
function HookIntr(IntNum: Byte; ISR: Pointer): Boolean;
{ Unhook a previously hooked application interrupt. }
function UnHookIntr(IntNum: Byte): Boolean;
{ Allocate a real mode callback. CallBackProc is the procedure to be called,}
{ HookProc must point to its install procedure, UnHookProc to its uninstall }
{ procedure. ID is set on return and is passed to DoneCallBack to indentify }
{ which callback to uninstall. }
function InitCallBack(CallBackProc, HookProc, UnHookProc: Pointer;
var ID: Word): Boolean;
{ Deallocate a real mode callback. ID is the callback idenfier (as returned }
{ by the corresponing call to InitCallBack) you wish to uninstall }
function DoneCallBack(ID: Word): Boolean;
{ Unhook all interrupts and callbacks installed by HookIntr, HookDosIntr & }
{ InitCallBack. Called automatically by the program's termination code. }
procedure UnHookAll;
{ Enable hardware interrupts }
procedure EnableInterrupts; inline (
$FB); { sti }
{ Disable hardware interrupts }
procedure DisableInterrupts; inline (
$FA); { cli }
{ Interrupt Service Routine entry code. a "call" to this procedure MUST be }
{ the first statement of any Pascal based application-side ISR you define }
{ and install with the HookISR function. }
procedure EnterISR; inline ( { Same as real-mode EnterDosISR }
$29/$E5/ { sub bp,sp ; BP = SizeOf(Locals) }
$01/$EC/ { add sp,bp ; "Pop" Locals off the stack }
{ Flags }
{ CS }
{ IP }
{ SP & BPo -> BP }
$16/ { push ss ; Push register arguments }
$54/ { push sp }
$68/>$C5C5/ { push $C5C5 ; OldVec.CS Self-modifying code to }
$68/>$1919/ { push $1919 ; OldVec.IP be replaced at run-time}
$0F/$A8/ { push gs }
$0F/$A0/ { push fs }
$1E/ { push ds }
$06/ { push es }
$9C/ { pushf }
$66/$60/ { pushad }
$16/ { push ss ; Stack.Seg (Dummy Return CS) }
$54/ { push sp ; Stack.Ofs (Dummy Return IP) }
$55/ { push bp ; Save SizeOf(Locals) }
$89/$E5/ { mov bp,sp ; Set current stack frame }
$83/$46/52/4/ { add [bp+52],4 ; Correct pushed Regs.SP (BP & SS) }
$2B/$66/$00/ { sub sp,[bp] ; Make room for Locals }
$8B/$46/56/ { mov ax,[bp+56]; Correct the pushed BP argument }
$68/>$A157/ { push $A157 ; AppISR signature (Global DataSeg)}
$89/$46/14/ { mov [bp+14],ax; Regs.BP is now correct. }
$1F); { pop ds }
{ Flags }
{ CS }
{ IP }
{ BP }
{ Regs }
{ CS dummy (SS) }
{ IP dummy (SP) }
{ BP -> BP = SizeOf(Locals) }
{ SP -> Locals (if any) }
procedure ExitISR; inline ( { Same as real mode ExitDosISR }
$89/$EC/ { mov sp,bp ; Pop Locals }
$83/$C4/$06/ { add sp,6 ; Pop dummy BP,IP and CS }
$66/$61/ { popad ; Pop registers }
$9D/ { popf }
$07/ { pop es }
$1F/ { pop ds }
$0F/$A1/ { pop fs }
$0F/$A9/ { pop gs }
$83/$C4/10/ { add sp,10 ; "Pop" IP,CS,SP,SS,BP }
$CF); { iret ; Pop IP, CS, Flags }
{ EnterCallBack must be the first statement of a DOS -> application callback}
{ you install with the InitDosCallBack function. }
procedure EnterCallBack; inline (
{$ifdef DPMI}
{ At this point, the stack frame contains the following data: }
{ }
{ Return Flags -----+ Pushed by the }
{ Return CS | Real mode callback }
{ Return IP -----+ }
{ BP -> Parent Stack frame (BP) -----+ Pushed by entry code }
{ Local variable stack space (if any ) -----+ Reserved by entry code}
{ SP -> Last byte of Local variables (if any) -----+ }
{ Make room on the DPMI stack for the dummy return address and registers }
$8B/$46/$00/ { mov ax,[bp] Return BP needs moving so save in AX }
$83/$ED/$36/ { sub bp,type Pointer + type TRegisters }
$83/$EC/$36/ { sub sp,type Pointer + type TRegisters }
{ Store the dummy return address and parent stack frame }
$89/$46/$00/ { mov [bp],ax ; Store relocated return BP }
$89/$76/$02/ { mov [bp+2],si ; Store pointer to real-mode stack }
$8C/$5E/$04/ { mov [bp+4],ds ; in the dummy return address space }
$06/ { push es ; Save address of TRegisters for }
$57/ { push di ; use by the exit code. }
{ Copy TRegisters from DPMI TRegisters (@ES:DI) into the stack arguments }
$06/ { push es }
$89/$FE/ { mov si,di }
$1F/ { pop ds ; DS:SI = @TRegisters }
$B9/>25/ { mov cx,type TRegisters/2 }
$16/ { push ss }
$89/$EF/ { mov di,bp }
$07/ { pop es ; ES:DI = SS:BP }
$83/$C7/$06/ { add di,type Word * 3 ; ES:DI = @EDI }
$FC/ { cld }
$F3/$A5/ { rep movsw }
{ Set DS to the global Pascal Data Segment }
$8E/$5C/$CC); { mov ds,[si-(SizeOf(TRegisters)+SizeOf(Word))] }
{ Now the stack frame contains the following data: }
{ }
{ Return Flags -----+ Pushed by the DPMI }
{ Return CS | Real mode callback }
{ Return IP -----+ }
{ Regs.SS -----+ }
{ Regs.SP | Registers copied from }
{ ... | TRegisters structure }
{ BP+6 Regs.EDI -----+ }
{ BP+4 Seg(Real-Mode-Stack) -----+ PM pointer to top of }
{ BP+2 Ofs(Real-Mode-Stack) -----+ real mode stack }
{ BP -> Parent Stack frame (BP) -----+ Pushed by entry code }
{ Local variable stack space (if any) -----+ Reserved by entry code }
{ Seg(TIntrInfo) -----+ Used by the the exit }
{ SP -> Ofs(TIntrInfo) -----+ code }
{$else DPMI} { Almost the same as EnterISR in real mode }
$29/$E5/ { sub bp,sp ; BP = SizeOf(Locals) }
$01/$EC/ { add sp,bp ; "Pop" Locals off the stack }
{ CS }
{ IP }
{ SP & BPo -> BP }
$16/ { push ss ; Push register arguments }
$54/ { push sp }
$50/ { push ax ; Dummy CS }
$50/ { push ax ; Dummy IP }
$0F/$A8/ { push gs }
$0F/$A0/ { push fs }
$1E/ { push ds }
$06/ { push es }
$9C/ { pushf }
$66/$60/ { pushad }
$16/ { push ss ; Stack.Seg (Dummy Return CS) }
$54/ { push sp ; Stack.Ofs (Dummy Return IP) }
$55/ { push bp ; Save SizeOf(Locals) }
$89/$E5/ { mov bp,sp ; Set current stack frame }
$83/$46/52/4/ { add [bp+52],4 ; Correct pushed Regs.SP (BP & SS) }
$2B/$66/$00/ { sub sp,[bp] ; Make room for Locals }
$8B/$46/56/ { mov ax,[bp+56]; Correct the pushed BP argument }
$68/>$DCA1/ { push $DCA1 ; Callback sig (Global DataSeg) }
$89/$46/14/ { mov [bp+14],ax; Regs.BP is now correct. }
$1F); { pop ds }
{ CS }
{ IP }
{ BP }
{ Regs }
{ CS dummy (SS) }
{ IP dummy (SP) }
{ BP -> BP = SizeOf(Locals) }
{ SP -> Locals (if any) }
{$endif DPMI}
{ ExitCallBack must be the last statement of a DOS -> application callback }
{ you install with the InitDosCallBack function. }
procedure ExitCallBack; inline (
{$ifdef DPMI}
$5F/ { pop di ; Restore ES:DI so they point to the }
$07/ { pop es ; RMCB TRegisters set by the DPMI }
$16/ { push ss ; Copy values of the register args }
$89/$EE/ { mov si,bp ; from the stack into the TRegisters }
$1F/ { pop ds ; DPMI structure. }
$83/$C6/$06/ { add si,6 ; DS:SI = @EDI }
$FC/ { cld }
$B9/>25/ { mov cx,type TRegisters/2 }
$F3/$A5/ { rep movsw }
$83/$EF/50/ { sub di,type TRegisters }
$C9/ { leave ; Pop locals, restore callers BP }
$5E/ { pop si ; Pop dummy return address (pointer }
$1F/ { pop ds ; to top of the real-mode stack). }
$83/$C4/50/ { add sp,type TRegisters ; "Pop" register arguments }
{ Set the callback return address CS:IP to the return address as is }
{ stored on the top of the realmode stack and "pop" the r/m IP and CS }
$8B/$14/ { mov dx,[si] ; DX = RM return IP }
$8B/$44/$02/ { mov ax,[si+2] ; AX = RM return CS }
$26/$89/$55/$2A/ { mov es:[di+TRegisters.&IP],dx; Set RM return Addr }
$26/$89/$45/$2C/ { mov es:[di+TRegisters.&CS],ax }
$26/$83/$45/$2E/$04/ { add es:[di+TRegisters.&SP],4 ; Pop return address }
$CF); { iret IRET is always used with RMCB's }
{$else}
$89/$EC/ { mov sp,bp ; Pop Locals }
$83/$C4/$06/ { add sp,6 ; Pop dummy BP,IP and CS }
$66/$61/ { popad ; Pop registers }
$9D/ { popf }
$07/ { pop es }
$1F/ { pop ds }
$0F/$A1/ { pop fs }
$0F/$A9/ { pop gs }
$83/$C4/10/ { add sp,10 ; "Pop" IP,CS,SP,SS,BP }
$CB); { retf ; Pop IP, CS }
{$endif dpmi}
{ EnterDosISR must be the first statement of hooked DOS/BIOS ISR you install}
{ with the HookDosISR function. }
procedure EnterDosISR; inline (
{ At this point, the stack frame contains the following data: }
{ }
{ Return Flags -----+ Pushed by the }
{ Return CS | Real mode callback }
{ Return IP -----+ }
{ BP -> Parent Stack frame (BP) -----+ Pushed by entry code }
{ Local variable stack space (if any ) -----+ Reserved by entry code}
{ SP -> Last byte of Local variables (if any) -----+ }
{$ifdef DPMI}
{ Make room on the DPMI stack for the dummy return address and registers }
$8B/$46/$00/ { mov ax,[bp] Return BP needs moving so save in AX }
$83/$ED/$36/ { sub bp,type Pointer + type TRegisters }
$83/$EC/$36/ { sub sp,type Pointer + type TRegisters }
{ Store the dummy return address and parent stack frame }
$89/$46/$00/ { mov [bp],ax ; Store relocated return BP }
$89/$76/$02/ { mov [bp+2],si ; Store pointer to real-mode stack }
$8C/$5E/$04/ { mov [bp+4],ds ; in the dummy return address space }
$06/ { push es ; Save address of TRegisters for }
$57/ { push di ; use by the exit code. }
{ Copy TRegisters from DPMI TRegisters (@ES:DI) into the stack arguments }
$06/ { push es }
$89/$FE/ { mov si,di }
$1F/ { pop ds ; DS:SI = @TRegisters }
$B9/>25/ { mov cx,type TRegisters/2 }
$16/ { push ss }
$89/$EF/ { mov di,bp }
$07/ { pop es ; ES:DI = SS:BP }
$83/$C7/$06/ { add di,type Word * 3 ; ES:DI = @EDI }
$FC/ { cld }
$66/$8B/$44/<-12/ { mov eax,[si-12] ; (EAX = TIntrInfo.OldVec) }
$F3/$A5/ { rep movsw }
{ (Set the CS:IP arguments to the address of the original DOS ISR) }
$66/$89/$46/$30/ { mov dword ptr [Registers.IP],eax }
{ Set DS to the global Pascal Data Segment }
$8E/$5C/$CC); { mov ds,[si-(SizeOf(TRegisters)+SizeOf(Word))] }
{ Now the stack frame contains the following data: }
{ }
{ Return Flags -----+ Pushed by the DPMI }
{ Return CS | Real mode callback }
{ Return IP -----+ }
{ Regs.SS -----+ Registers copied from }
{ Regs.SP | TRegisters structure }
{ Regs.CS (=OldVec.Seg) | (except CS & IP) }
{ Regs.IP (=OldVec.Ofs) | }
{ ... | }
{ BP+6 Regs.EDI -----+ }
{ BP+4 Seg(Real-Mode-Stack) -----+ PM pointer to top of }
{ BP+2 Ofs(Real-Mode-Stack) -----+ real mode stack }
{ BP -> Parent Stack frame (BP) -----+ Pushed by entry code }
{ Local variable stack space (if any) -----+ Reserved by entry code }
{ Seg(TIntrInfo) -----+ Used by the the exit }
{ SP -> Ofs(TIntrInfo) -----+ code }
{$else DPMI}
$29/$E5/ { sub bp,sp ; BP = SizeOf(Locals) }
$01/$EC/ { add sp,bp ; "Pop" Locals off the stack }
{ Flags }
{ CS }
{ IP }
{ SP & BPo -> BP }
$16/ { push ss ; Push register arguments }
$54/ { push sp }
$68/>$C5C5/ { push $C5C5 ; OldVec.CS Self-modifying code to }
$68/>$1919/ { push $1919 ; OldVec.IP be replaced at run-time}
$0F/$A8/ { push gs }
$0F/$A0/ { push fs }
$1E/ { push ds }
$06/ { push es }
$9C/ { pushf }
$66/$60/ { pushad }
$16/ { push ss ; Stack.Seg (Dummy Return CS) }
$54/ { push sp ; Stack.Ofs (Dummy Return IP) }
$55/ { push bp ; Save SizeOf(Locals) }
$89/$E5/ { mov bp,sp ; Set current stack frame }
$83/$46/52/4/ { add [bp+52],4 ; Correct the pushed Regs.SP }
$2B/$66/$00/ { sub sp,[bp] ; Make room for Locals }
$83/$46/$02/$36/ { add [bp+2],54 ; Dummy Return = @Return_IP }
$8B/$46/56/ { mov ax,[bp+56]; Correct the pushed BP argument }
$68/>$D150/ { push $D150 ; DosISR signature (Global DataSeg)}
$89/$46/14/ { mov [bp+14],ax; Regs.BP is now correct. }
$1F); { pop ds }
{ Flags }
{ CS }
{ IP }
{ BP }
{ Regs }
{ CS dummy (SS) }
{ IP dummy (SP) }
{ BP -> BP = SizeOf(Locals) }
{ SP -> Locals (if any) }
{$endif not DPMI}
{ ExitDosISR must be the last statement of hooked DOS/BIOS ISR you install }
{ with the HookDosISR function. }
procedure ExitDosISR; inline (
{$ifdef DPMI}
$5F/ { pop di ; Restore ES:DI so they point to the }
$07/ { pop es ; RMCB TRegisters set by the DPMI }
$16/ { push ss ; Copy values of the register args }
$89/$EE/ { mov si,bp ; from the stack into the TRegisters }
$1F/ { pop ds ; DPMI structure. }
$83/$C6/$06/ { add si,6 ; DS:SI = @EDI }
$FC/ { cld }
$B9/>25/ { mov cx,type TRegisters/2 }
$F3/$A5/ { rep movsw }
$83/$EF/50/ { sub di,type TRegisters }
$C9/ { leave ; Pop locals, restore callers BP }
$5E/ { pop si ; Pop dummy return address (pointer }
$1F/ { pop ds ; to top of the real-mode stack). }
$83/$C4/50/ { add sp,type TRegisters ; "Pop" register arguments }
{ Set the callback return address CS:IP to the return address as is }
{ stored on the top of the realmode stack and "pop" the r/m IP,CS & Flags }
$8B/$14/ { mov dx,[si] DX = RM return IP }
$8B/$44/$02/ { mov ax,[si+2] AX = RM return CS }
$26/$89/$55/$2A/ { mov es:[di+TRegisters.&IP],dx Set RM return Addr }
$26/$89/$45/$2C/ { mov es:[di+TRegisters.&CS],ax }
$26/$83/$45/$2E/$06/ { add es:[di+TRegisters.&SP],6 Pop rtn adr & flags}
$CF); { iret IRET is always used with RMCB's }
{$else DPMI} { Same as ExitISR in real mode }
$89/$EC/ { mov sp,bp ; Pop Locals }
$83/$C4/$06/ { add sp,6 ; Pop dummy BP,IP and CS }
$66/$61/ { popad ; Pop registers }
$9D/ { popf }
$07/ { pop es }
$1F/ { pop ds }
$0F/$A1/ { pop fs }
$0F/$A9/ { pop gs }
$83/$C4/10/ { add sp,10 ; "Pop" IP,CS,SP,SS,BP }
$CF); { iret ; Pop IP, CS, Flags }
{$endif dpmi}
{******************* Programmable Interrupt Timer functions ****************}
function GetPit0Count: Word; { Read value of channel 0 (clock) }
function GetPit1Count: Word; { Read value of channel 1 (ram refresh) }
function GetPit2Count: Word; { Read value of channel 2 (speaker) }
procedure SetPit0Mode(Mode: Word; Value: Word);
function GetPit0Mode: Word; { Only possible with the 8254 ! }
function GetPitType: Word; { Reprograms timer 0 !! }
{ Translate a given IRQ number to its corresponding interrupt vector }
function IRQtoIntVec(IRQ: Byte): Word;
{*********************** Date/Time related functions ***********************}
{ GetDate returns 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). }
procedure GetDate(var Year,Month,Day,DayOfWeek: Word);
{ SetDate sets the current date in the operating system. Valid parameter }
{ ranges are: Year 1980-2099, Month 1-12 and Day 1-31. If the date is not }
{ valid, the function call is ignored. }
procedure SetDate(Year,Month,Day: Word);
{ GetTime 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. }
procedure GetTime(var Hour,Minute,Second,Sec100: Word);
{ SetTime sets the time in the operating system. Valid parameter ranges are:}
{ Hour 0-23, Minute 0-59, Second 0-59 & Sec100 (hundredths of seconds) 0-99.}
{ If the time is not valid, the function call is ignored. }
procedure SetTime(Hour,Minute,Second,Sec100: Word);
{ Formats a date according to local custom - month:day:year day:month:year }
{ or year:month:day. DosCountry.DateSep character used as separator. }
function FormatDate(Year,Month,Day: Word): TDateStr;
{ Formats a time according to local custom - DosCountry.TimeSep character }
{ used as separator, 12 or 24 hour clock used (DosCountry.TimeFormat) }
function FormatTime(Hour,Minute,Second: Word): TTimeStr;
{ Formats a time (including 100ths of seconds) according to local custom - }
{ DosCountry.TimeSep character used as separator, 12 or 24 hour clock used. }
function FormatTime100(Hour,Minute,Second,Sec100: Word): TTimeStr;
{*********************** Disk/Drive related functions **********************}
{ GetVerify 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. }
function GetVerify: Boolean;
{ SetVerify sets the state of the verify flag in DOS. }
procedure SetVerify(Verify: Boolean);
{ DiskInfo returns information on the given logical drive. Returns false if }
{ the drive number is invalid. }
function GetDiskInfo(Drive: Byte; var DiskInfo: TDiskInfo): Boolean;
{ DiskFree returns the number of free bytes on the specified drive number }
{ (0=Default,1=A,2=B,..). DiskFree returns -1 if drive number is invalid. }
{ MaxLongint (2,147,483,647) is returned on drives with more than 2GB of }
{ free disk space. }
function DiskFree(Drive: Byte): DWord;
{ DiskSize returns the size in bytes of the specified drive number }
{ (0=Default,1=A,2=B,..). DiskSize returns -1 if the drive number is invalid}
{ MaxLongint (2,147,483,647) is returned on drives larger than 2GB. }
function DiskSize(Drive: Byte): DWord;
{ Return the current drive }
function GetCurDrive: Char;
{ Returns a list of valid system drives. eg: a return string of 'ABCE' means}
{ drives A: B: C: and E: are valid on this machine. Drive B is not included }
{ on systems with a single floppy drive. }
function GetDrives: String;
{ Return true if the Drive is valid on the system. False is returned if }
{ Drive is not an upper or lowercase letter between "A" and "Z" inclusive, }
{ or the drive does not exist. }
function DriveValid(Drive: Char): Boolean;
{ Returns true if the drive has removable media. eg if it's a floppy disk, }
{ CD-Rom etc. False if fixed disk or invalid drive. Drive must be an upper }
{ or lowercase letter between "A" and "Z" }
function DriveRemove(Drive: Char): Boolean;
{ Uses the DOS IOCTL functions to return information about a block device. }
{ Fills in the passed TBlockDevInfo structure and returns the device type. }
{ A return of 255 indicates an error. (deprecated - use GetVolumeInfo) }
function GetDriveInfo(Drive: Char; var Info: TBlockDevInfo): Byte;
{ Validate and return drive type given a drive letter (deprecated - ditto) }
function GetDriveType(Drive: Char;
var IsRemoveable, HasChangeLine: Boolean): Byte;
{ Return pointer to the drive volume information record of a given drive. }
function GetVolumeInfo(Drive: Char): PVolumeInfo;
{ Determines the volume from the given a path. Path can contain an absolute,}
{ relative or network path. Returns nil and sets a DosError if a valid drive}
{ cannot be resolved from the given Path. }
function GetVolumeOf(const Path: TNetPath): PVolumeInfo;
function GetVolumeOfStr(Path: PChar): PVolumeInfo;
{ Add V to List of defined volumes. Called by overridden an CreateVolume. }
procedure InsertVolume(V: PVolumeInfo);
{ Check if media on a removable-media drive has been changed }
function CheckDrvMedia(V: PVolumeInfo): TMediaLevel;
{ Get the volume label of the given drive }
function GetVolumeLabel(Drive: Char): TVolLabel;
function GetVolumeLabelStr(VolLabel: PChar; Drive: Char): PChar;
{ Set the volume label of the given drive. Don't include the "." in the name}
function SetVolumeLabel(Drive: Char; VolLabel: TVolLabel): Boolean;
function SetVolumeLabelStr(VolLabel: PChar; Drive: Char): Boolean;
{************************** File related functions *************************}
{ GetFAttr returns the attributes of a file. F must be a file variable }
{ (typed, untyped or textfile) which has been assigned a name. The }
{ attributes are examined by ANDing with the attribute masks defined as }
{ faXXXX constants above. Errors are reported in DosError. }
procedure GetFAttr(var F; var Attr: Word);
{ SetFAttr sets the attributes of a file. F must be a file variable (typed, }
{ untyped or textfile) which has been assigned a name. The attribute value }
{ is formed by adding (or ORing) the appropriate attribute masks defined as }
{ faXXXX constants above. Errors are reported in DosError. }
procedure SetFAttr(var F; Attr: Word);
{ GetFTime returns the date and time a file was last written. F must be a }
{ file variable (typed, untyped or textfile) which has been assigned a name.}
{ The file can be open or closed. The Time parameter may be unpacked through}
{ a call to UnpackTime. Errors are reported in DosError. }
procedure GetFTime(var F; var Time: Longint);
{ SetFTime sets the date and time a file was last written. F must be a file }
{ variable (typed, untyped or textfile) which has been assigned and opened. }
{ The Time parameter may be created through a call to PackTime. Errors are }
{ reported in DosError. }
procedure SetFTime(var F; Time: Longint);
{ GetFSize returns the size in bytes of the file assigned to F. F must be }
{ assigned, but can be open or closed. }
procedure GetFSize(var F; var Size: Longint);
{ UnAssign disassociates an external file with its File or Text variable. }
{ Closes an open file before disassociating if the file is still open. Every}
{ variable of type "File" or type "Text" that is assigned must eventually }
{ have a corresponding call to Unassign, or memory leaks might occur. }
procedure UnAssign(var F);
{ Returns the name of an assigned File or Text variable }
function GetFName(var F): TPathStr;
function GetFileName(var F): PChar;
{----------------------- Directory search functions ------------------------}
{ FindFirst searches the specified (or current) directory for the first }
{ entry that matches the specified filename and attributes. The result is }
{ returned in the specified search record. Errors (and no files found) are }
{ reported in DosError. The Low byte of Attr contains the "can have" file }
{ attributes. The High byte of Attr contains the "must have" file attributes}
{$ifdef Windows}
function FindFirst(Path: PChar; Attr: Word; var SR: TSearchRec): Boolean;
{$else Windows}
function FindFirst(Path: TPathStr; Attr: Word; var SR: TSearchRec): Boolean;
function FindFirstStr(Path: PChar; Attr: Word; var SR: TSearchRec): Boolean;
{$endif Windows}
{ FindNext 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. Errors (and no more files) are reported in DosError. }
function FindNext(var SR: TSearchRec): Boolean;
{ FindClose terminates a directory search. Does nothing unless long names }
{ are supported. FindFirst and FindNext call FindClose automatically }
{ whenever a DOS error occurs (such as deNoMoreFiles), so FindClose only }
{ needs calling when your code wants a file search to be ended prematurely. }
procedure FindClose(var SR: TSearchRec);
{$ifndef LongNames}
inline($58/$5A); { pop ax dx ; do nothing, just pop ^SR }
{$endif LongNames}
{ UnpackTime converts a 4-byte packed date/time returned by FindFirst, }
{ FindNext or GetFTime into a DateTime record. }
procedure UnpackTime(Time: Longint; var DT: TDateTime);
{ PackTime converts a DateTime record into a 4-byte packed date/time used by}
{ SetFTime. }
procedure PackTime(const DT: TDateTime; var Time: LongInt);
{------------------- FileName/FilePath name based functions ----------------}
{ FSearch searches for the file given by Path in the list of directories }
{ given by DirList. The directory paths in DirList must be separated by }
{ semicolons. Add ';' to start of DirList to start the search in the current}
{ directory of the current drive. The returned value is the fully qualified }
{ path if the Path, or an empty string if the file could not be located. }
function FSearch(const Path: String; DirList: String): TPathStr;
function FileSearch(Dest, Path, DirList: PChar): PChar;
{ FExpand expands the file name in Path into a fully qualified file name. }
{ The resulting name consists of a drive letter, a colon, a root relative }
{ directory path, and a file name. Embedded '.' and '..' directory }
{ references are removed. Wilcards in the name and/or extension are allowed }
{ if the fcWildCard flag is set. Returns ptr to TVolumeInfo for that path. }
function FExpand(const Path: String; Flags: Word): TPathStr;
function FileExpand(Dest, Name: PChar; Flags: Word): PVolumeInfo;
{ FDosExpand returns the DOS 8.3 equivalent of the long file/path name given}
{ by LongPath. Wildcards are not allowed and Path/Name must exist. Used for }
{ passing filename path arguments to child processes. }
function FDosExpand(const Path: TPathStr): TDosPath;
function FileDosExpand(DosPath, LongPath: PChar): PChar;
{ FContract is the inverse of FExpand. It takes a fully-expanded path and }
{ tries to convert it to a shorter, current directory relative path. }
function FContract(const Path: TPathStr): TPathStr;
function FileContract(Dest, Name: PChar): PChar;
{ FDosContract is similar to FContract except it returns a DOS 8.3 path. }
{ Wildcards are not allowed and Name must exist. Use it only for passing }
{ path arguments to child processes spawned with Exec. }
function FDosContract(const Name: TPathStr): TDosPath;
function FileDosContract(Dest, Name: PChar): PChar;
{ FSplit 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 dot. Each of the component strings may possibly be empty,}
{ if Path contains no such component. }
procedure FSplit(const Path: TPathStr; var Dir: TDirStr; var Name: TNameStr;
var Ext: TExtStr);
function FileSplit(Path, Dir, Name, Ext: PChar): Word;
{ Compares 2 path strings. Returns +1 if Name1 > Name2, -1 if Name1 < Name2}
{ and 0 if the file/path names are equivalent. Takes file system case }
{ sensitiviy into account unless IgnoreCase is true. }
function FCompare(Name1, Name2: String): Integer;
function FileCompare(Name1, Name2: PChar): Integer;
{ Delete a given file }
procedure FErase(const FileName: String);
procedure FileErase(FileName: PChar);
{ Rename a given file to a new name. Can be renamed accross directories but }
{ not accross drives. Errors returned in DosError }
procedure FRename(const OldName, NewName: String);
procedure FileRename(OldName, NewName: PChar);
{ Get or Set the attributes of a named file }
function FileGetSetAttr(PathName: PChar; Attr: Word; Op: TAttrOp): Word;
{ Add and remove trailing backslashes from a directory string }
procedure DelDirSep(var Dir: TDirStr);
procedure DelDirSepStr(Dir: PChar);
procedure AddDirSep(var Dir: TDirStr);
procedure AddDirSepStr(Dir: PChar);
{ Returns true if S is a directory }
function IsDirectory(S: TPathStr): Boolean;
function IsDirectoryStr(S: PChar): Boolean;
{ Returns true if S is a root directory ("X:" or "X:\") }
function IsRootDir(const S: TPathStr): Boolean;
function IsRootDirStr(S: PChar): Boolean;
{ Return a unique file name and path, either using the directory in the }
{ 'TMP' or 'TEMP' environment variables if they exist, or the current }
{ directory if they don't. Returned file extension is always '.TMP' }
{ Temp file will be erased on program termination when AutoErase is true. }
procedure GetTempFile(var TempName: TPathStr; AutoErase: Boolean);
procedure GetTempFileStr(TempName: PChar; AutoErase: Boolean);
{ Erases a temporary file whose name was created by GetTempFile }
procedure EraseTempFile(const TempName: TPathStr);
procedure EraseTempFileStr(TempName: PChar);
{ Create a new subdirectory }
procedure CreateDir(Dir: PChar);
{ Remove an empty directory }
procedure RemoveDir(Dir: PChar);
{*********************** Handle based file functions ***********************}
{ FileOpen opens or creates a file. Mode should be a combination of stXXXX }
{ file open/create and sharing mode constants. Returns a valid file handle }
{ if successful, or $FFFF if not. Errors are reported in DosError. }
function FileOpen(const Name: String; Mode: Word): TFileHandle;
function FileOpenStr(Name: PChar; Mode: Word): TFileHandle;
{ Close a file previously opened with FileOpen. }
procedure FileClose(Handle: Word);
{ Return the current file position of a file }
function FilePosition(Handle: TFileHandle): Longint;
{ Seek to given position relative to start of file }
function FileSeek(Handle: TFileHandle; Pos: Longint; SeekType: TFileSeek): Longint;
{ Return file size of a file }
function FileSize(Handle: TFileHandle): Longint;
{ Read Count bytes into Buf from a file. Returns actual number of bytes read}
{ Errors are returned in DosError. }
function FileRead(Handle: TFileHandle; var Buf; Count: Word): Word;
{ Write Count bytes from Buf into a file. Returns actual number of bytes }
{ written. Errors are returned in DosError. }
function FileWrite(Handle: TFileHandle; const Buf; Count: Word): Word;
{ Truncate file at current file position }
procedure FileTruncate(Handle: TFileHandle);
{ FileGetTime returns the date and time a file was last written. Handle must}
{ be a file handle which has been assigned and opened. The Time parameter }
{ may be unpacked through a call to UnpackTime. Errors reported in DosError.}
function FileGetTime(Handle: TFileHandle): Longint;
{ FileSetTime sets the date and time a file was last written. Handle must be}
{ a file handle which has been assigned & opened. The Time parameter may be }
{ created through a call to PackTime. Errors are reported in DosError. }
procedure FileSetTime(Handle: TFileHandle; Time: Longint);
{****************** Environment & Process handling functions ***************}
{ EnvCount returns the number of strings contained in the DOS environment. }
function EnvCount: Integer;
{ EnvStr returns a specified environment string. The returned string is of }
{ the form "VAR=VALUE". The index of the first string is one. If Index is }
{ less than one or greater than EnvCount, EnvStr returns an empty string. }
function EnvStr(Index: Integer): String;
{ GetEnv returns the value of a specified environment variable. The variable}
{ name can be in upper or lowercase, but it must not include the '=' charctr}
{ If the specified environment variable does not exist, GetEnv returns an }
{ empty string. }
function GetEnv(EnvVar: String): String;
{ SwapVectors swaps the contents of the SaveIntXX pointers in the System }
{ unit with the current contents of the interrupt vectors. SwapVectors is }
{ typically called just before and just after a call to Exec. This insures }
{ that the Exec'd process does not use any interrupt handlers installed by }
{ the current process, and vice versa. }
{$ifndef Windows}
procedure SwapVectors;
{ Keep (or Terminate Stay Resident) terminates the program and makes it stay}
{ in memory. The entire program stays in memory, including data segment, }
{ stack segment, and heap. The ExitCode corresponds to the one passed to the}
{ Halt standard procedure. }
procedure Keep(ExitCode: Byte);
{ Exec executes another program. The program is specified by the Path }
{ parameter, and the command line is specified by the CmdLine parameter. To }
{ execute a DOS internal command, run COMMAND.COM, e.g. }
{ "Exec('\COMMAND.COM','/C DIR *.PAS');". Note the /C in front of the }
{ command. Errors are reported in DosError. When compiling a program that }
{ uses Exec, be sure to specify a maximum heap size as there will otherwise }
{ not be enough memory to execute the child process. }
procedure Exec(const Path: String; const CmdLine: TComStr);
{ DosExitCode returns the exit code of a sub-process. The low byte is the }
{ code sent by the terminating process. The high byte is zero for normal }
{ termination, 1 if terminated by Ctrl-C, 2 if terminated due to a device }
{ error, or 3 if terminated by the Keep procedure (function call 31 hex). }
function DosExitCode: Word;
{$endif Windows}
{*********************** Case-conversion functions *************************}
{ DosUpCase returns the uppercase equivalent of character C, or C if C is }
{ not a lowercase character. }
function DosUpCase(C: Char): Char; inline (
$5B/ { pop bx }
$B7/$00/ { mov bh,0 }
$8A/$87/>LoToUpTbl); { mov al,[bx+offset LoToUpTbl] }
{ DosLoCase returns the lowercase equivalent of C, or C if C is }
{ not an uppercase character. }
function DosLoCase(C: Char): Char; inline (
$5B/ { pop bx }
$B7/$00/ { mov bh,0 }
$8A/$87/>UpToLoTbl); { mov al,[bx+offset LoToUpTbl] }
{ DosUpperCase converts all lowercase characters in S to their }
{ lowercase equivalents. }
procedure DosUpperCase(var S: String);
{ DosLowerCase converts all uppercase characters in S to their }
{ lowercase equivalents. }
procedure DosLowerCase(var S: String);
{ DosCompare performs a case insensitive compare of 2 strings }
function DosCompare(S1, S2: String): Integer;
{************************* System unit replacements ************************}
{ "Bug Fixed" version of System.ChDir. This function allows strings like }
{ "A:", "A:\SOMEDIR", "A:SOMEDIR\" etc. System.ChDir only allows "A:\" and }
{ "A:\SOMEDIR". LFN and network directory paths are supported too of course }
procedure ChDir(Dir: String);
procedure ChangeDir(Dir: PChar);
{ Returns the current directory of the specified drive in S. Note that all }
{ sub-directories are terminated with a backslash, unlike the System unit }
{ version where only the root directory is terminated with a backslash. }
{ The case of the path and filenames are converted according to the FileCase}
{ flags, even when compiled without long filename support. }
procedure GetDir(Drive: Byte; var S: String);
function GetCurDir(S: PChar; Drive: Byte): PChar;
{************************ Miscellaneous functions **************************}
{ Return the network name of the local machine }
function GetLocalName: TMachineName;
{ GetCBreak returns the state of Ctrl-Break checking in DOS. When off }
{ (False), DOS only checks for Ctrl-Break during I/O to console, printer, or}
{ communication devices. When on (True) checks are made at every system call}
procedure GetCBreak(var Break: Boolean);
{$ifdef TurboDos}
inline (
$B8/>$3300/ { mov ax,3300h }
$CD/$21/ { int $21 }
$5F/ { pop di }
$07/ { pop es }
$26/$88/$15);{ mov byte [es:di],dl }
{$endif TurboDos}
{ SetCBreak sets the state of Ctrl-Break checking in DOS. }
procedure SetCBreak(Break: Boolean);
{$ifdef TurboDos}
inline (
$5A/ { pop dx }
$B8/>$3301/ { mov ax,3301h }
$CD/$21); { int $21 }
{$endif TurboDos}
const
rdtsc = $310F;
function FrdtscW: Word; inline (>rdtsc); { Clock to edx:eax }
function FrdtscL: Longint; inline (
>rdtsc/ { Clock to edx:eax }
$66/$89/$C2/ { mov edx,eax }
$66/$C1/$EA/$10); { shr dx,16 }
function FrdtscC: Comp; inline (
>rdtsc/ { Clock to edx:eax }
$89/$E3/ { mov bx,sp }
$66/$52/ { push edx }
$66/$50/ { push eax }
$36/$DF/$6F/$F8/ { fild qword ptr ss:[bx-8] }
$83/$C4/$08); { add sp,8 }
{$ifdef MsDos}
{************************ XMS Device driver interface **********************}
{ OvrInitXMS loads the overlay file into XMS, if possible. }
procedure OvrInitXMS;
{------------------- Low-Level XMS API wrapper functions -------------------}
{ XmsAvail returns total amount of available XMS memory }
function XmsAvail: Longint;
{ MaxXmsAvail returns the largest available XMS block }
function MaxXmsAvail: Longint;
{ GetXms allocates a block of XMS memory, and returns a handle to it }
function GetXms(var Handle: TXmsHandle; var Size: Longint): Boolean;
{ FreeXms deallocates a block of XMS memory }
function FreeXms(Handle: TXmsHandle): Boolean; inline (
$5A/ { pop dx }
$B4/$0A/ { mov ah,xmsFreeEMB }
$FF/$1E/>XmsFunc); { call [XmsFunc] }{ Call XmsFreeEMB function }
{ MoveXms copies data to/from XMS extended memory blocks }
function MoveXms(Dest: Pointer; DestHandle: TXmsHandle; Srce: Pointer;
SrceHandle: TXmsHandle; Size: Longint): Boolean;
{ ReAllocXms tries to resize a block of XMS extended memory }
function ReAllocXms(Handle: TXmsHandle; var Size: Longint): Boolean;
{$endif MsDos}
{***************************** String functions ****************************}
{ Output a string using DOS function 40h }
procedure PrintStr(const S: String);
{ Allocate a dynamic string on the heap }
function NewStr(const S: String): PString;
{ Exchange the values of 2 strings }
procedure SwapString(var S1, S2: String);
procedure StrSwap(S1, S2: PChar);
{ Dispose of a string on the heap }
function DisposeStr(P: PString): Pointer;
{ Dispose of a C-string on the heap }
procedure StrDispose(Str: PChar);
{ Allocate a C-string on the heap }
function StrNew(Str: PChar): PChar;
{ Convert a C-string to a Pascal-style string. No length checking performed }
function StrPas(Str: PChar): String;
{ Copy a string from a C-String. Copies at most MaxLen characters from Str }
{ to the resulting pascal string. DosError is set to dePathTooLong if }
{ trancation occurs. }
function StrLPas(Str: PChar; MaxLen: Word): String;
{ Copy a Pascal-style string to a null-terminated string }
function StrPCopy(Dest: PChar; const Source: String): PChar;
{ Convert pascal string to C-String. Copies at most MaxLen chars}
function StrPLCopy(P: PChar; const PasStr: String; MaxLen: Word): Word;
{ StrScan returns a pointer to the first occurrence of Chr in Str. If Chr }
{ does not occur in Str, StrScan returns NIL. The null terminator is }
{ considered to be part of the string. }
function StrScan(Str: PChar; Chr: Char): PChar;
{ StrRScan returns a pointer to the last occurrence of Chr in Str. If Chr }
{ does not occur in Str, StrRScan returns NIL. The null terminator is }
{ considered to be part of the string. }
function StrRScan(Str: PChar; Chr: Char): PChar;
{ StrCount returns the number of occurences of a given character}
{ in the given string }
function StrCount(Str: PChar; Chr: Char): Word;
{ StrArrayCount returns the number of occurences of a given character }
{ in the given array of char }
function StrArrayCount(Str: PChar; Chr: Char; Count: Integer): Word;
{ StrPos returns a pointer to the first occurrence of Str2 in Str1. If Str2 }
{ does not occur in Str1, StrPos returns NIL. }
function StrPos(Str1, Str2: PChar): PChar;
{ StrUpper converts Str to upper case and returns Str.}
function StrUpper(Str: PChar): PChar;
{ StrLower converts Str to lower case and returns Str.}
function StrLower(Str: PChar): PChar;
{ Compare two C-strings }
function StrComp(Str1, Str2: PChar): Integer;
{ Compare two C-strings without case sensitivity }
function StrIComp(Str1, Str2: PChar): Integer;
{ Compare two C-strings, up to a maximum length }
function StrLComp(Str1, Str2: PChar; MaxLen: Word): Integer;
{ StrLIComp compares Str1 to Str2, for a maximum length of MaxLen characters}
{ without case sensitivity. The return value is the same as StrComp. }
function StrLIComp(Str1, Str2: PChar; MaxLen: Word): Integer;
{ StrCat appends a copy of Source to the end of Dest and returns Dest. }
function StrCat(Dest, Source: PChar): PChar;
{ StrLCat appends at most MaxLen - StrLen(Dest) characters from Source to }
{ the end of Dest, and returns Dest. DosError is set to dePathTooLong if }
{ trancation occurs. }
function StrLCat(Dest, Source: PChar; MaxLen: Word): PChar;
{ StrEnd returns a pointer to the null character that terminates Str. }
function StrEnd(Str: PChar): PChar;
{ Copy characters from one string to another }
function StrCopy(Dest, Source: PChar): PChar;
{ Copy characters from one string to another. Returns pointer to the end of }
{ the resulting string. }
function StrECopy(Dest, Source: PChar): PChar;
{ Copy at most MaxLen characters from Source to Dest. DosError is set to }
{ dePathTooLong if trancation occurs. }
function StrLCopy(Dest, Source: PChar; MaxLen: Word): Word;
{ Copies characters from one C-string to another. }
function StrMove(Dest, Source: PChar; Count: Word): PChar;
{ Returns the number of characters in Str, excluding the null terminator. }
function StrLen(P: PChar): Word;
{---------------------------------------------------------------------------}
{---------------------------------------------------------------------------}
implementation
{$ifdef MsDos}
uses Overlay;
{$endif MsDos}
const
{ DeviceAttr bit-fields }
bdaNotRemoveable = $0001; { 1 = Media is not removable }
bdaHasChangeLine = $0002; { Device supports a change-line }
type
PIntrInfo = ^TIntrInfo;
TIntrInfo = packed record { Used to register App and O/S interrupts }
Next : PIntrInfo; { Pointer to next TIntrInfo record }
OldVec : DosPtr; { Original vector - read only }
UnHook : Pointer; { Pointer to the UnhookInt function. }
IntNo : Word; { Interrupt vector number - read only }
{$ifdef DPMI}
DataSeg: Word; { Global Pascal Data Segment - read only }
Regs : TRegisters; { Pseudo registers in a DPMI callback }
{$endif DPMI}
end;
PCallInfo = ^TCallInfo;
TCallInfo = packed record { Used to register real mode callbacks }
Next : PIntrInfo; { Pointer to next TIntrInfo record }
RMCB : DosPtr; { Pointer to real mode callback }
UnHook : Pointer; { Pointer to the UnhookInt function. }
CallNo : Word; { Callback identifier number - read only }
AppKill: Pointer; { Pointer to user defined Unhook function }
AppHook: Pointer; { Pointer to user defined Hook function }
{$ifdef DPMI}
DataSeg: Word; { Global Pascal Data Segment - read only }
Regs : TRegisters; { TRegisters structure used by the RMCB }
{$endif DPMI}
end;
const
itDosInt = 0; { Dos (real-mode) interrupt tag }
{$ifdef DPMI}
itAppInt = 4; { Application (Protected mode) intrpt tag }
{$else DPMI}
itAppInt = itDosInt; { Application & O/S interrupt tables are same in r/m }
{$endif DPMI}
itCallBack= 6; { Realmode callback }
const
DriveList : String[26] = ''; { List of valid disk drives }
const
IntrRegs: PIntrInfo = nil;
const
SaveDTA: DosPtr = nil; { Previous address of Dos Transfer Buffer }
const
TempNameCnt: Integer = 0; { Temporary filename number }
const
CallNum : Word = itCallBack * 256; { Used to uniquely identify callbacks }
const
TempNums: Set of Byte = []; { For erasing temporary files on bad exit }
const
DosChars: TCharSet = ['$','%','-','_','@','{','}','~','`','!','#','&','.',
'^','(',')',' ','0'..'9','A'..'Z']; {Valid 8.3 chars}
function LongMake(Hi, Lo: Word): Longint; inline (
$58/ { pop ax }
$5A); { pop dx }
function Offset(P: Pointer): Word; inline(
$58/ { pop ax }
$5A); { pop dx }
{$ifndef MsDos}
function GlobalDosAlloc(Bytes: Longint): Longint; far;
external 'KERNEL' index 184;
function GlobalDosFree(Selector: Word): Word; far;
external 'KERNEL' index 185;
{$endif !MsDos}
function GetDosMem(var Buf: TDosBuf; Size: Word): Boolean;
{$ifdef MsDos}
var
P,T: Pointer;
begin
Size := (Size + 7) and $FFF8;
GetMem(P, Size + 8);
if P <> nil then
begin
if PtrRec(P).Ofs = 0
then begin
PtrRec(T).Ofs := Size and 15;
PtrRec(T).Seg := PtrRec(P).Seg + Size shr 4;
end
else begin
T := P;
PtrRec(P).Ofs := 0;
Inc(PtrRec(P).Seg);
end;
FreeMem(T, 8);
end;
Buf.Buf := P;
{$else MsDos}
begin
Longint(Buf.RealBuf) := GlobalDosAlloc(Size);
Buf.Seg := Buf.RealOfs;
Buf.RealOfs:= 0;
Buf.Ofs := 0;
{$endif MsDos}
Buf.Size := Size;
GetDosMem := Buf.Seg <> 0;
end;
procedure FreeDosMem(var Buf: TDosBuf);
begin
if Buf.Seg = 0
then Exit;
{$ifdef MsDos}
FreeMem(Buf.Buf, Buf.Size);
Buf.Seg := 0;
{$else MsDos}
Buf.Seg := GlobalDosFree(Buf.Seg);
{$endif MsDos}
Buf.Size:= 0;
end;
{$ifdef DPMI}
{$L SOFTINT.OBP}
function SoftIntr(var Regs: TRegisters): Word; external; { SOFTINT.OBP }
{$else DPMI}
{$L SOFTINT.OBJ}
function SoftIntr(var Regs: TRegisters): Word; external; { SOFTINT.OBJ }
function DosSoftIntr(var Regs: TRegisters): Word; external; { SOFTINT.OBJ }
function DosFarCall(var Regs: TRegisters): Word; external; { SOFTINT.OBJ }
{$endif DPMI}
{------------------------- Hook[Dos]Int/InitCallBack -----------------------}
function UnHookDosI(P: PIntrInfo): Boolean; far;
begin
{$ifdef DPMI}
asm
les di,[P] { Use the DPMI sever to return the }
mov ax,dpmiGetRealInt { real mode address of a real mode }
mov bx,[es:di+TIntrInfo.IntNo] { interupt vector (RMCB) }
int intDPMI
mov ax,dpmiFreeRMCB { CX:DX = RMCB }
int intDPMI { Release RMCB }
end;
{$endif DPMI}
UnHookDosI := SetDosIntVec(P^.IntNo, P^.OldVec);
Dispose(P);
end;
function UnHookI(P: PIntrInfo): Boolean; far;
begin
UnHookI := SetIntVec(P^.IntNo, P^.OldVec);
Dispose(P);
end;
function UnHookCall(P: PCallInfo): Boolean; far;
begin
asm
les di,[P] { Call user-supplied unhook function }
push [es:di].TCallInfo.CallNo
call [es:di].TCallInfo.AppKill
mov [@Result],al
{$ifdef DPMI}
les di,[P]
mov dx,[es:di].TCallInfo.RMCB.Word[0]
mov cx,[es:di].TCallInfo.RMCB.Word[2]
mov ax,dpmiFreeRMCB { CX:DX = RMCB }
int intDPMI { Release RMCB }
{$endif DPMI}
end;
Dispose(P);
end;
{ Add an Interrupt, Dos Interrupt or real mode callback to the linked-list. }
{ Duplicate entries are not allowed - ie an interrupt vector that has }
{ already been hooked by the application cannot be hooked again. }
function RegisterIntr(P: PIntrInfo): Boolean; assembler;
asm
push ds
db $66; mov cx,[IntrRegs].Word[0] { Save value of IntrRegs ptr }
les di,[P] { P must not be a local variable }
lds si,[IntrRegs] { DS:DI = @1'st registration record }
mov ax,[es:di].TIntrInfo.IntNo
jmp @@3
@@1: cmp ax,[si].TIntrInfo.IntNo { Make sure the IntNo has not }
jne @@2 { been hooked already. }
pop ds
mov al,false
jmp @@Exit
@@2: lds si,[si].TIntrInfo.Next
@@3: mov dx,ds
or dx,si
jne @@1
pop ds
db $66; mov [es:di].TIntrInfo.Next.Word[0],cx { P^.Next := IntrRegs }
mov [IntrRegs].Word[0],di { IntrRegs := @P.Next }
mov [IntrRegs].Word[2],es
mov al,true
@@Exit:
end;
{ Unhook the specified Interrupt, Dos Interrupt or real mode callback from }
{ the system and remove it from the linked-list. }
function UnHookIt: Boolean; far; assembler; { AX = interrupt identifier }
var
SaveDS: Word;
asm
mov [SaveDS],ds
mov si,offset IntrRegs { DS:SI = address of prev^.Next}
les di,[IntrRegs] { ES:DI = address of TIntrInfo }
jmp @@2
@@1: cmp ax,[es:di].TIntrInfo.IntNo { DS:SI = address of prev^.Next}
je @@FoundIt { ES:DI = address of TIntrInfo }
mov cx,es { prev = ThisOne^.Next }
mov si,di
mov ds,cx { DS:SI = @ThisOne^.Next }
les di,[es:di].TIntrInfo.Next { ES:DI = @Next TIntrInfo }
@@2: mov cx,es { Make sure we haven't reached }
or cx,di { the end of the linked-list }
jne @@1
xor ax,ax { Return false }
jmp @@Exit
@@FoundIt:
db $66; mov ax,word ptr [es:di].TIntrInfo.Next
push es
db $66; mov word ptr [si],ax { Previous^.Next := This^.Next }
push di
mov ds,[SaveDS]
call [es:di].TIntrInfo.UnHook { Unhook the ISR/Callback }
mov al,true
@@Exit: mov ds,[SaveDS]
end;
procedure UnHookAll; assembler; { Unhook all interrupts and callbacks }
asm { from the system and the linked-list }
@@Next: les di,[IntrRegs]
mov ax,es
or ax,di
jz @@Done
db $66; mov ax,word ptr [es:di].TIntrInfo.Next
push es
db $66; mov word ptr [IntrRegs],ax
push di
call [es:di].TIntrInfo.UnHook
jmp @@Next
@@Done: mov ah,itCallBack
mov [CallNum],ax
end;
{$ifndef Windows}
procedure SwapVectors; assembler;
var
Count: Word;
asm
jmp @@Start
@@Callback: pusha { Unhook or Hook a callback }
push es { ES:DI = @TCallInfo }
cmp ah,itCallBack { AX = Callback identifier }
jne @@HookCall
inc [es:di].TCallInfo.CallNo.Byte[1] { Next time it's a hook }
push ax { Identifier argument }
call [es:di].TCallInfo.AppKill { Unhook the callback }
pop es
popa
retn
@@HookCall: dec [es:di].TCallInfo.CallNo.Byte[1] { Next time it's unhook }
dec ah
db $66; push word ptr [es:di].TCallInfo.RMCB
push ax { Identifier argument }
call [es:di].TCallInfo.AppHook { Re-Hook the callback }
pop ds
pop es
popa
retn
{$ifdef DPMI}
@@VecTable: db $00,2 { DPMI Exception handler 00 }
db $02,4 { DPMI Interrupt vector 02 }
db $0C,2 { DPMI Exception handler 0C }
db $0D,2 { DPMI Exception handler 0D }
db $1B,4 { DPMI Interrupt vector 1B }
db $21,4 { DPMI Interrupt vector 21 }
db $23,0 { DOS Interrupt vector 23 }
db $24,0 { DOS Interrupt vector 24 }
db $34,4 { DPMI Interrupt vector 34 }
db $35,4 { DPMI Interrupt vector 35 }
db $36,4 { DPMI Interrupt vector 36 }
db $37,4 { DPMI Interrupt vector 37 }
db $38,4 { DPMI Interrupt vector 38 }
db $39,4 { DPMI Interrupt vector 39 }
db $3A,4 { DPMI Interrupt vector 3A }
db $3B,4 { DPMI Interrupt vector 3B }
db $3C,4 { DPMI Interrupt vector 3C }
db $3D,4 { DPMI Interrupt vector 3D }
db $3E,4 { DPMI Interrupt vector 3E }
db $3F,4 { DPMI Interrupt vector 3F }
db $75,4 { DPMI Interrupt vector 75 }
@@Start: les di,[IntrRegs]
mov si,offset @@VecTable
@@NextUser: mov ax,es
or ax,di
jz @@DoneUser
mov bx,[es:di].TIntrInfo.IntNo { BL = interrupt Number }
cmp bh,itCallBack { AH = interrupt/callback type}
jb @@IntCheck { Not Hook or UnHook callback }
call @@CallBack { Hook/UnHook callback }
jmp @@NoSwap
@@IntCheck: mov si,offset @@VecTable { Don't swap vectors if a user}
mov cx,21 { has hooked a std BP interupt}
@@NxtCheck: cmp bx,[si] { Is it same as a std BP hook?}
je @@NoSwap { Yes, so allow std TP swap to}
add si,type Word { perform the unhooking. }
loop @@NxtCheck { Check all 21 std hooks }
mov ax,dpmiGetRealInt { Unhook the user interrupt }
add al,bh
int intDPMI { Get current interrupt vector}
xchg dx,[es:di].TIntrInfo.OldVec.Word[0]
xchg cx,[es:di].TIntrInfo.OldVec.Word[2]
inc ax { dmpiGetXXint -> dpmiSetXXint}
int intDPMI { Set the interrupt to OldVec }
@@NoSwap: les di,[es:di].TIntrInfo.Next
jmp @@NextUser
@@DoneUser: mov si,offset @@VecTable { Restore BP7 hooked intrpts }
mov di,offset SaveInt00 { DS:DI = @SaveInt00 }
mov [Count],21 { There are 21 hooked intrpts }
@@NextTP: mov bx,[cs:si] { BL = interrupt Number }
mov ax,dpmiGetRealInt
add al,bh { BH = interrupt/callback type}
int intDPMI { CX:DX = current vector }
xchg [di],dx { Save current interrupt vect }
xchg [di+2],cx { in SaveIntXX, CX:DX }
inc al { dmpiGetXXint -> dpmiSetXXint}
int intDPMI { Set previous interrupt vec }
add si,type Word { CS:SI = @next VecTable entry}
add di,type Pointer { ES:DI = @next SaveIntX var }
dec [Count]
jne @@NextTP
{$else DPMI}
@@VecTable: db $00,$02,$1B,$21,$23,$24,$34,$35,$36,$37
db $38,$39,$3A,$3B,$3C,$3D,$3E,$3F,$75
@@Start: les di,[IntrRegs]
mov si,offset @@VecTable
@@NextUser: mov ax,es
or ax,di
jz @@DoneUser
mov ax,[es:di].TIntrInfo.IntNo { AL = interrupt Number }
cmp ah,itCallBack { AH = interrupt/callback type}
jae @@IntCheck
call @@CallBack { Hook/UnHook callback }
jmp @@NoSwap
@@IntCheck: mov si,offset @@VecTable { Don't swap vectors if a user}
mov cx,19 { has hooked a std BP interupt}
@@NxtCheck: cmp bx,[si] { Is it same as a std BP hook?}
je @@NoSwap { Yes, so allow std TP swap to}
add si,type Word { perform the unhooking. }
loop @@NxtCheck { Check all 19 std hooks }
mov ah,$35 { Unhook the user interrupt }
push es
int intDos { ES:BX = current int vector }
mov dx,bx
mov bx,es { BX:DX = current int vector }
pop es
xchg dx,[es:di].TIntrInfo.OldVec.Word[0]
mov ah,$25 { DOS - Set interupt vector }
push ds
xchg bx,[es:di].TIntrInfo.OldVec.Word[2]
mov ds,bx
int intDos
pop ds
@@NoSwap: les di,[es:di].TIntrInfo.Next
jmp @@NextUser
@@DoneUser: mov si,offset @@VecTable { Restore BP7 hooked intrpts }
mov di,offset SaveInt00 { DS:DI = @SaveInt00 }
mov cx,19
cld
@@NextTP: mov ah,$35 { DOS - Get Interrupt vector }
segcs lodsb { AL = interrupt number }
int intDos { ES:BX = current int vector }
push es
push bx
mov dx,[di] { DS:DX = [SaveIntXX] }
push ds
mov ds,[di+2]
mov ah,$25 { DOS - Set interrupt vector }
pop ds
db $66; pop dx { EDX = Previous int vector }
db $66; mov [di],dx
add di,type Pointer
loop @@NextTP
{$endif DPMI}
end;
{$endif Windows}
function HookDosIntr(IntNum: Byte; ISR: Pointer): Boolean;
var
P: PIntrInfo;
V: DosPtr;
R: Boolean;
X: Boolean;
begin
R := False;
V := GetDosIntVec(IntNum);
New(P);
with P^ do
begin
OldVec:= V; { Assign the P^.OldVec, }
IntNo := (itDosInt * 256) + IntNum;{ P^.IntNo and the }
UnHook:= @UnHookDosI; { UnHook procedure fields. }
end;
if RegisterIntr(P) then { Check the interrupt has not already}
asm { been hooked, and add to list. }
push ds
{$ifdef DPMI} { Allocate an RMCB }
les di,[P]
mov [es:di].TIntrInfo.DataSeg,ds
lds si,[ISR] { DS:SI = Address of PM ISR }
add di,type TIntrInfo - type TRegisters { ES:DI = @P^.Regs }
mov ax,dpmiAllocRMCB { Allocate real mode callback }
int intDPMI { CX:DX = real mode address of RMCB }
jc @@Exit { function failed }
mov bl,[IntNum] { Use the DPMI server to set the }
mov ax,dpmiSetRealInt { real mode interrupt vector to the }
int intDPMI { real mode address of the RMCB. }
jnc @@Good
mov ax,dpmiFreeRMCB { Falied to set r/m interrupt vector }
int intDPMI { o deallocate the allocated RMCB }
jmp @@Exit
{$else DPMI}
les di,[ISR] { Look for the position in the ISR }
mov al,$68 { (Opcode for push immediate word) }
mov cx,3 { of the EnterDosISR macro, so we }
add di,43 { (minumum offset of push from start)}
cld { can self-modify the DS place holder}
repne scasb { of the ISR code. }
mov ax,ds
lds dx,[ISR] { DS:DX = address of ISR }
jne @@Exit { push immediate opcode not found }
cmp word ptr [di],$D150
jnz @@Exit { EnterDosISR Signature not found }
mov [di],ax { Store the Data Segment in ISR code }
mov ax,[V].Word[2] { Store the original Interrupt vector}
mov [di-34],ax { into the EnterDosISR modified code.}
mov ax,[V].Word[0] { This means the the CS:IP arguments }
mov [di-31],ax { of the ISR point to original ISR. }
mov al,[IntNum] { AL = interrupt number }
mov ah,25h { Use DOS to set a real mode interrpt}
int intDos { vector to a real mode address. }
{$endif DPMI}
@@Good: inc [R]
@@Exit: pop ds
end;
if not R then
begin
IntrRegs := P^.Next; { Unhook P from interrupt registratn }
Dispose(P); { list, then dispose of P. }
end;
HookDosIntr := R;
end;
function HookIntr(IntNum: Byte; ISR: Pointer): Boolean;
var
P: PIntrInfo;
V: Pointer;
R: Boolean;
X: Boolean;
begin
R := False;
V := GetIntVec(IntNum);
New(P);
with P^ do
begin
OldVec:= V; { Assign the P^.OldVec, }
IntNo := IntNum; { P^.IntNo and the }
UnHook:= @UnHookI; { UnHook procedure fields. }
end;
if RegisterIntr(P) then { Check the interrupt has not already}
asm { been hooked, and add to list. }
{$ifdef DPMI}
mov bx,word ptr [ISR+2] { Save original ISR code selector }
mov ax,[SelectorInc] { Convert the ISR's code segment }
add word ptr [ISR+2],ax { selector to a R/W data selector. }
{$endif DPMI}
push ds
les di,[ISR] { Look for the position in the ISR }
mov al,$68 { (Opcode for push immediate word) }
mov cx,3 { of the EnterISR macro, so we can }
add di,39 { (minumum offset of push from start)}
cld { self-modify the DS place holder of }
repne scasb { the ISR code. }
mov ax,ds
lds dx,[ISR] { DS:DX = address of ISR }
jne @@Exit { push immediate opcode not found }
cmp word ptr [di],$A157
jnz @@Exit { EnterISR Signature not found }
mov [di],ax { Store the Data Segment in ISR code }
mov ax,[V].Word[2] { Store the original Interrupt vector}
mov [di-30],ax { into the EnterISR modified code. }
mov ax,[V].Word[0] { This means the the CS:IP arguments }
mov [di-27],ax { of the ISR point to original ISR. }
{$ifdef DPMI}
mov cx,bx { CX:DX = address of ISR }
mov ax,dpmiSetProtInt { Use the DPMI server to set a }
mov bl,[IntNum] { protected mode interrupt vector }
int intDPMI { to a protected mode address. }
jc @@Exit
{$else DPMI}
mov al,[IntNum] { AL = interrupt number }
mov ah,25h { Use DOS to set a real mode }
int 21h { interrupt vector real mode address.}
{$endif DPMI}
mov [R],1
@@Exit: pop ds
end;
if not R then
begin
IntrRegs := P^.Next; { Unhook P from interrupt registration}
Dispose(P); { list, then dispose of P. }
end;
HookIntr := R;
end;
function InitCallBack(CallBackProc, HookProc, UnHookProc: Pointer;
var ID: Word): Boolean;
var
P : PCallInfo;
R : DosPtr;
Result: Boolean;
Padder: Boolean;
begin
Result := false;
R := nil;
New(P);
with P^ do
begin
{$ifndef DPMI}
RMCB := CallBackProc; { Assign the P^.OldVec (not used) }
{$endif !DPMI}
CallNo := CallNum; { P^.CallNo and the }
UnHook := @UnHookCall; { UnHook procedure fields. }
AppHook:= HookProc;
AppKill:= UnHookProc;
end;
ID := CallNum;
Inc(CallNum);
asm
{$ifdef DPMI}
les di,[P]
push ds
mov [es:di].TCallInfo.DataSeg,ds
add di,offset TCallInfo.Regs { ES:DI = Address of TRegisters }
lds si,[CallBackProc] { DS:SI = p/m address of callback }
mov ax,dpmiAllocRMCB { Allocate real mode callback }
int intDPMI { CX:DX = r/m addr of DPMI callback }
pop ds
jc @@Exit { function failed }
sub di,offset TCallInfo.Regs { Store RMCB addr }
mov PtrRec([es:di.TCallInfo.RMCB]).&Ofs,dx { in P.RMCB }
mov PtrRec([es:di.TCallInfo.RMCB]).&Seg,cx
push cx { CallBackAddr argument to user hook }
push dx
{$else DPMI}
les di,[CallBackProc] { Look for the position in CallBack }
mov al,$68 { (Opcode for push immediate word) }
mov cx,3 { of the EnterCallBack macro, so we }
add di,35 { (minumum offset of push from start)}
cld { can self-modify the DS place holder}
repne scasb { of the callback code. }
mov ax,ds
jne @@Exit { push immediate opcode not found }
cmp word ptr [es:di],$DCA1
je @@1 { EnterCallBack Signature not found }
pop dx { so release the allocated RMCB and }
pop cx { fail }
jmp @@Exit
@@1: mov [es:di],ax { Store the Data Segment in callback }
mov di,PtrRec(CallBackProc).Ofs
push es { CallBackAddr argument to HookProc }
push di
les di,[P]
{$endif DPMI} { Call user-supplied Hook function }
push [es:di].TCallInfo.CallNo
call [es:di].TCallInfo.AppHook
mov [Result],al
cmp al,false
jne @@Ok { Callback was installed succesfully }
{$ifdef DPMI}
les di,[P]
mov dx,PtrRec([es:di.TCallInfo.RMCB]).&Ofs
mov cx,PtrRec([es:di.TCallInfo.RMCB]).&Seg
mov ax,dpmiFreeRMCB { CX:DX = RMCB }
int intDPMI { Release RMCB }
{$endif DPMI}
jmp @@Exit
@@Ok: db $66; push word ptr [P]
call RegisterIntr { Add callback to the linked-list }
mov [Result],true
@@Exit:
end;
if not Result then
begin
Dispose(P);
Dec(CallNum);
end;
InitCallBack := Result;
end;
function UnHookDosIntr(IntNum: Byte): Boolean; assembler;
asm
mov al,[IntNum] { UnHookDosIntr := UnhookIt(itDos or IntNum); }
mov ah,itDosInt
push cs
call near ptr UnhookIt
end;
function UnHookIntr(IntNum: Byte): Boolean; assembler;
asm
mov al,[IntNum] { UnHookIntr := UnhookIt(IntNum); }
mov ah,itAppInt
push cs
call near ptr UnhookIt
end;
function DoneCallBack(ID: Word): Boolean; assembler;
asm
mov ax,[ID] { DoneCallBack := UnhookIt(ID); }
push cs
call near ptr UnhookIt
end;
{ Translate a given IRQ number to its corresponding interrupt vector }
function IRQtoIntVec(IRQ: Byte): Word;
begin
if IRQ < 8
then IRQtoIntVec := IRQ + MasterPicBase
else if IRQ < 16
then IRQtoIntVec := IRQ + SlavePicBase
else IRQtoIntVec := Word(-1);
end;
{$ifdef MsDos}
{---------------------------- XMS based routines ---------------------------}
const
xmsGetVersion = $00; { Get XMS driver version number }
xmsFreeEMB = $0A; { Dispose a block of extended memory }
xmsMoveEMB = $0B; { XMS move function }
{$ifdef XMS30}
xmsGetFreeEMB = $88; { Query Free Extended Memory }
xmsAllocEMB = $89; { Allocate a block of extended memory }
xmsReAllocEMB = $8F; { Resize a block of extended memory }
function XmsAvail: Longint; assembler;
asm
db $66; xor ax,ax
mov ah,xmsGetFreeEMB
call [XmsFunc]
db $66; mov ax,dx
@@1:
db $66; mov dx,1024; dw 0
db $66; mul dx
db $66,$0F,$A4,$C2,$10; { shld edx,eax,16 }
end;
{ MaxXmsAvail returns the largest available XMS block }
function MaxXmsAvail: Longint; assembler;
asm
db $66; xor ax,ax
mov ah,xmsGetFreeEMB
call [XmsFunc]
@@1:
db $66; mov dx,1024; dw 0
db $66; mul dx
db $66,$0F,$A4,$C2,$10; { shld edx,eax,16 }
end;
{$else XMS30}
xmsGetFreeEMB = $88; { Query Free Extended Memory }
xmsAllocEMB = $89; { Allocate a block of extended memory }
xmsReAllocEMB = $8F; { Resize a block of extended memory }
function XmsAvail: Longint; assembler;
asm
xor ax,ax
mov ah,xmsGetFreeEMB
call [XmsFunc]
mov ax,dx
mov dx,1024;
mul dx
end;
{ MaxXmsAvail returns the largest available XMS block }
function MaxXmsAvail: Longint; assembler;
asm
xor ax,ax
mov ah,xmsGetFreeEMB
call [XmsFunc]
mov dx,1024;
mul dx
end;
{$endif XMS30}
function GetXms(var Handle: TXmsHandle; var Size: Longint): Boolean; assembler;
asm
les di,[Size]
db $66; xor ax,ax
db $66; mov dx,es:[di] { Convert Size to kilobytes }
db $66; add dx,1023; dw 0;
db $66; shr dx,10 { E|DX = requested size in kilobytes }
db $66; mov cx,dx { ECX = Requested size in KB }
mov ah,xmsAllocEMB { Call XmsAllocEMB function }
call [XmsFunc]
db $66; shl cx,10 { ECX = KB-rounded Size in bytes }
or ax,ax { Memory allocated ok? }
jnz @@2 { Yes }
@@1: mov dx,ax { Return null handle }
db $66; mov cx,ax { Return 0 Size }
@@2:
db $66; mov es:[di],cx { Return kB-rounded Size in bytes }
les di,[Handle]
mov es:[di],dx { Return Handle }
end;
function MoveXms(Dest: Pointer; DestHandle: TXmsHandle; Srce: Pointer;
SrceHandle: TXmsHandle; Size: Longint): Boolean; assembler;
type
TXmsMove = record
Length : Longint; { The arguments are set up as this }
SrceHandle: Word; { structure to match what the Xms }
Srce : Pointer; { API function expects. This type }
DestHandle: Word; { defined only for debugging. }
Dest : Pointer;
end;
var
M: TXmsMove absolute Size;
asm
xor ax,ax
mov dx,ds { Save Turbo's global DS }
mov ax,ss
lea si,Size { Get the address of Size }
mov ds,ax { Set ds equal to ss }
mov es,dx { Set es to Turbo's ds }
mov ah,xmsMoveEMB { Call XMS move function }
call es:[XmsFunc]
mov ds,dx { Restore global DS }
@@Exit: {add sp,type TXmsMove}
end;
function ReAllocXms(Handle: TXmsHandle; var Size: Longint): Boolean; assembler;
asm
les di,[Size]
xor ax,ax
db $66; mov bx,es:[di] { EBX = Size in bytes }
mov ah,xmsReAllocEMB { Call XmsReAllocEMB function }
db $66; add bx,1023; dw 0; { Round Size up to next KB }
mov dx,[Handle]
db $66; shr bx,10 { EBX = Size in kilobytes }
db $66; mov cx,bx { ECX = Size in kilobytes }
call [XmsFunc]
db $66; shl cx,10 { Convert alloc size to bytes }
cmp ax,false { Succesfull call? }
jne @@1 { Yes }
db $66; xor cx,cx { Set Size to 0 (al = false) }
@@1:
db $66; mov es:[di],cx { Size = actual bytes allocated }
end;
type
POvrCodeBlock = ^TOvrCodeBlock;
TOvrCodeBlock = record
Int3F : Word; { INT 3F instruction - $CD/$3F }
RetOfs : Word; { Offset of Return }
FilePos : Longint; { Location in overlay file }
CodeSize : Word; { Bytes of code in file }
FixupSize: Word; { Bytes of relocation data in file }
Entries : Word; { Number of entry points }
NextBlock: Word; { Next block location (offset from PrefixSeg) }
BufSeg : Word; { Segment location in overlay buffer. 0-not loaded}
Retries : Word; { Called whilst on probation if 1 }
NextSeg : Word; { Segment of next loaded code }
EmsPage : Word; { Unused by Xms overlays }
EmsOffset: Word;
Unused : Word;
XmsPos : Longint; { Location (offset) in XMS memory Block }
end;
var
OvrXmsHandle: TXmsHandle; { XMS handle used by overlays }
{= XmsReadBuf ==========================================================}
{ Replacement overlay read routine: gets the overlay code block from }
{ XMS memory. }
{=======================================================================}
function XmsReadBuf(OvrSeg: Word): Integer; far;
var
CodeBlock: POvrCodeBlock;
HeapBlock: PWord;
begin
CodeBlock := Ptr(OvrSeg, 0);
HeapBlock := Ptr(CodeBlock^.BufSeg, 0);
XmsReadBuf:= Ord(MoveXms(HeapBlock, 0, { Destination }
Pointer(CodeBlock^.XmsPos), OvrXmsHandle, { Source }
CodeBlock^.CodeSize)) -1; { Size }
end;
{= OvrInitXMS ==========================================================}
{ If XMS is present, copy all the code segments into XMS memory. Set up }
{ the OvrReadBuf routine to read them back when required. Close the }
{ overlay file. }
{=======================================================================}
procedure OvrInitXMS;
var
CodeBlock: POvrCodeBlock; { Ptr to current overlay code block }
OvrBuffer: PWord; { Overlay buffer on heap }
OvrTotal : Longint; { Xms required for all the overlays }
XmsError : Boolean;
Padder : Boolean;
begin
{ Exit if no XMS memory or driver }
if not XMSinstalled then
begin
OvrResult := ovrNoXMSDriver;
Exit;
end;
{ Exit if the user hasn't called OvrInit }
if OvrHeapOrg = 0 then
begin
OvrResult := ovrError;
Exit;
end;
OvrBuffer := Ptr(OvrHeapOrg, 0); { Get ptr to overlay buffer on heap }
PtrRec(CodeBlock).Seg := OvrCodeList;{ Walk the overlay code block chain }
PtrRec(CodeBlock).Ofs := 0;
OvrTotal := 0;
while PtrRec(CodeBlock).Seg <> 0 do
begin
Inc(PtrRec(CodeBlock).Seg, PrefixSeg + $10);
Inc(OvrTotal, (CodeBlock^.CodeSize + 3) and (not 3)); { Round up 2 DWord}
PtrRec(CodeBlock).Seg := CodeBlock^.NextBlock; { Next overlay block link}
end;
{ Try to allocate the required amount of XMS memory }
if not GetXms(OvrXmsHandle, OvrTotal) then
begin
OvrResult := ovrNoXMSMemory; { There was an XMS error }
Exit;
end;
OvrBuffer := Ptr(OvrHeapOrg, 0); { Get ptr to overlay buffer on heap }
PtrRec(CodeBlock).Seg := OvrCodeList;{ Walk the overlay code block chain }
OvrTotal := 0; { Now it's a running offset }
XmsError := false;
while (PtrRec(CodeBlock).Seg <> 0) and (OvrResult = 0) do
begin
Inc(PtrRec(CodeBlock).Seg, PrefixSeg + $10);
with CodeBlock^ do
begin
BufSeg := OvrHeapOrg; { Set block load addr to OvrHeapOrg }
OvrResult:= OvrReadBuf(PtrRec(CodeBlock).Seg); { Load code into memory}
BufSeg := 0; { Mark this code block as unloaded }
XmsPos := OvrTotal; { Mark the pos in XMS of this overlay}
CodeSize := (CodeSize + 3) and (not 3); { Round up to to DWord }
if not MoveXms(Pointer(OvrTotal), OvrXmsHandle, { Destination }
Ptr(OvrHeapOrg, 0), 0, { Source }
CodeSize) { Size }
then OvrResult := ovrNoXmsMemory;
Inc(OvrTotal, CodeSize); { = XMS position of next code block }
end;
PtrRec(CodeBlock).Seg := CodeBlock^.NextBlock; { Next overlay block link}
end;
if XmsError
then begin { There was an XMS error }
FreeXms(OvrXmsHandle); { Release the XMS memory block }
OvrXmsHandle := 0; { Set the handle to zero }
OvrResult := ovrNoXMSMemory;
end
else begin
asm { Close the overlay file, zero the handle}
mov ah,$3E
mov bx,[OvrDOSHandle]
int $21
end;
OvrDOSHandle:= 0;
OvrReadBuf := XmsReadBuf;{ Point the overlay read routine at ours }
OvrResult := 0;
XmsOverlays:= true; { XMS is being used for code overlays }
end;
end;
{$endif MsDos}
{--------------------------- Date and time routines ------------------------}
procedure GetDate(var Year, Month, Day, DayOfWeek: Word);
{$ifdef TurboDos} assembler;
asm
mov ah,$2A { DOS - Get System Date }
int intDos
les di,[DayOfWeek]
cbw
cld
stosw
les di,[Day]
mov al,dl
stosw
les di,[Month]
mov al,dh
stosw
les di,[Year]
mov [es:di],cx
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AH := $2A; { DOS - Get System Date }
DayOfWeek := Lo(MsDos(Regs));
Year := Regs.CX;
Month:= Regs.DH;
Day := Regs.DL;
end;
{$endif TurboDos}
procedure SetDate(Year, Month, Day: Word);
{$ifdef TurboDos} assembler;
asm
mov dl,[Day].Byte[0]
mov ah,$2B { DOS - Set System Date }
mov dh,[Month].Byte[0]
mov cx,[Year]
int intDos
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AH := $2B; { DOS - Set System Date }
Regs.CX := Year;
Regs.DH := Month;
Regs.DL := Day;
MsDos(Regs);
end;
{$endif TurboDos}
procedure GetTime(var Hour,Minute,Second,Sec100: Word);
{$ifdef TurboDos} assembler;
asm
mov ah,$2C { DOS - Get System Time }
int intDos
les di,[Hour]
xor ax,ax
mov al,ch
stosw
les di,[Minute]
mov al,cl
stosw
les di,[Second]
mov al,dh
stosb
les di,[Sec100]
mov al,dl
stosw
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AH := $2C; { DOS - Get System Time }
MsDos(Regs);
Hour := Regs.CH;
Minute:= Regs.CL;
Second:= Regs.DH;
Sec100:= Regs.DL;
end;
{$endif TurboDos}
procedure SetTime(Hour, Minute, Second, Sec100: Word);
{$ifdef TurboDos} assembler;
asm
mov ch,[Hour].Byte[0]
mov cl,[Minute].Byte[0]
mov dh,[Second].Byte[0]
mov dl,[Sec100].Byte[0]
mov ah,$2D
int intDos
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AH := $2D; { DOS - Set System Time }
Regs.CH := Hour;
Regs.CL := Minute;
Regs.DH := Second;
Regs.DL := Sec100;
MsDos(Regs);
end;
{$endif TurboDos}
procedure ZeroPad(Value: Word; var S: OpenString; Len: Word);
var
j: Word;
begin
Str(Value:0, S);
for j := 1 to Len - Length(S) do
S := '0' + S;
end;
function FormatDate(Year,Month,Day: Word): TDateStr;
var
Y: String[4];
M: String[2];
D: String[2];
begin
ZeroPad(Year, Y, 4);
ZeroPad(Month, M, 2);
ZeroPad(Day, D, 2);
case DosCountry.DateFormat of
dfUsa:
FormatDate := M + DosCountry.DateSep[0] + D + DosCountry.DateSep[0] + Y;
dfEurope:
FormatDate := D + DosCountry.DateSep[0] + M + DosCountry.DateSep[0] + Y;
else { dfJapan }
FormatDate := Y + DosCountry.DateSep[0] + M + DosCountry.DateSep[0] + D;
end;
end;
function FormatTime100(Hour,Minute,Second,Sec100: Word): TTimeStr;
var
H,M,S,S100,AP: String[2];
begin
AP := '';
if DosCountry.TimeFormat = tf12Hour then
begin
AP := 'am';
if Hour >= 12 then
begin
AP[1] := 'p';
if Hour > 12
then Dec(Hour, 12);
end;
end;
ZeroPad(Hour, H, 2);
ZeroPad(Minute, M, 2);
ZeroPad(Second, S, 2);
ZeroPad(Sec100, S100, 2);
FormatTime100 := H + DosCountry.TimeSep[0] + M + DosCountry.TimeSep[0] +
S + DosCountry.TimeSep[0] + S100 + AP;
end;
function FormatTime(Hour,Minute,Second: Word): TTimeStr;
var
H,M,S,AP: String[2];
begin
AP := '';
if DosCountry.TimeFormat = tf12Hour then
begin
AP := 'am';
if Hour >= 12 then
begin
AP[1] := 'p';
if Hour > 12
then Dec(Hour, 12);
end;
end;
ZeroPad(Hour, H, 2);
ZeroPad(Minute, M, 2);
ZeroPad(Second, S, 2);
FormatTime := H + DosCountry.TimeSep[0] + M + DosCountry.TimeSep[0] + S +
AP;
end;
{$ifdef DPMI}
{$L INTR.OBP} { Software interrupt routines }
{$else DPMI}
{$L INTR.OBJ} { Software interrupt routines }
{$endif DPMI}
{$ifndef MSDOS}
function AllocDStoCSAlias(Selector: Word): Word; far; external 'KERNEL' index 171;
function AllocSelector(Selector: Word): Word; far; external 'KERNEL' index 175;
function FreeSelector(Selector: Word): Word; far; external 'KERNEL' index 176;
function ChangeSelector(SourceSelector,
DestSelector: Word): Word; far; external 'KERNEL' index 177;
function SetSelectorBase(Selector: Word;
Base: Longint): Word; far; external 'KERNEL' index 187;
function GetSelectorLimit(Selector: Word): Longint;far; external 'KERNEL' index 188;
function SetSelectorLimit(Selector: Word;
Limit: Longint): Word; far; external 'KERNEL' index 189;
function MapDosPtr(RealPtr: DosPtr): Pointer;
var
Selector: Word; { Set up a pointer to point to RealPtr memory }
Base : LongInt;
begin
MapDosPtr := nil;
Selector := AllocSelector(0);
if Selector = 0
then Exit;
ChangeSelector(CSeg, Selector); { Ensure a read/write selector }
Base := (LongInt(PtrRec(RealPtr).Seg) shl 4);
if SetSelectorBase(Selector, Base) = 0 then
begin
FreeSelector(Selector);
Exit;
end;
SetSelectorLimit(Selector, $FFFF);
MapDosPtr := Ptr(Selector, PtrRec(RealPtr).Ofs);
end;
{$endif !MSDOS}
{$ifdef DPMI}
function IntrApp(IntNo: Byte; var Regs: TRegisters): Word; external {INTR};
function MsDosPM(var Regs: TRegisters): Word; external {INTR};
{$else DPMI}
function Intr(IntNo: Byte; var Regs: TRegisters): Word; external {INTR};
function IntrApp(IntNo: Byte; var Regs: TRegisters): Word; external {INTR};
function MsDos(var Regs: TRegisters): Word; external {INTR};
{$endif DPMI}
{$ifdef Windows}
procedure AnsiToOem(Dest, Source: PChar); far; external 'KEYBOARD' index $0005;
procedure OemToAnsi(Dest, Source: PChar); far; external 'KEYBOARD' index $0006;
{$endif Windows}
{-------------- General purpose and string conversion functions ------------}
function Min(A, B: Integer): Integer; inline (
$58/ {pop ax }
$5B/ {pop bx }
$3B/$C3/ {cmp ax,bx}
$7E/$01/ {jle @@1 }
$93); {xchg ax,bx}
{@@1: }
function MaxWord(A, B: Word): Word; inline (
$58/ {pop ax }
$5B/ {pop bx }
$3B/$C3/ {cmp ax,bx}
$73/$01/ {jae @@1 }
$93); {xchg ax,bx}
{@@1: }
function MinWord(A, B: Word): Word; inline (
$58/ {pop ax }
$5B/ {pop bx }
$3B/$C3/ {cmp ax,bx}
$76/$01/ {jbe @@1 }
$93); {xchg ax,bx}
{@@1: }
function MaxLong(A, B: Longint): Longint; inline (
$66/$58/ { pop eax }
$66/$5B/ { pop ebx }
$66/$3B/$C3/ { cmp eax,ebx }
$7F/$02/ { jg @@1 }
$66/$93/ { xchg eax,ebx }
{@@1: }
$66/$0F/$A4/$C2/$10);{ shld edx,eax,16 }
function LongMul(X, Y: Integer): Longint; inline (
$5A/ { pop dx }
$58/ { pop ax }
$F7/$EA); { imul dx }
function LongMulW(X, Y: Word): Longint; inline (
$5A/ { pop dx }
$58/ { pop ax }
$F7/$E2); { mul dx }
procedure PrintStr(const S: String);
begin
FileWrite(1, S[1], Length(S)); { Write S to standard output }
end;
function NewStr(const S: String): PString;
var
P: PString;
begin
NewStr := nil;
if S <> '' then
begin
GetMem(P, Length(S) + 1);
P^ := S;
NewStr := P;
end;
end;
function DisposeStr(P: PString): Pointer;
begin
if P <> nil
then FreeMem(P, Length(P^) + 1);
DisposeStr := nil;
end;
procedure SwapString(var S1, S2: String); assembler;
asm
push ds
les di,[S2]
xor cx,cx
lds si,[S1]
xor dx,dx
mov al,es:[di] { AL = Length(S2) }
mov dl,[si] { DX = Length(S1) }
mov cl,al { CX = Length(S2) }
cmp dx,cx
jle @@1
xchg dx,cx
@@1: inc cx
@@2: xchg al,[si]
inc si
stosb
mov al,es:[di]
loop @@2
pop ds
end;
procedure StrSwap(S1, S2: PChar); assembler;
asm
push ds
db $66; push word ptr [S2]
push cs
call near ptr StrLen
push ax { AX = Length(S2) }
db $66; push word ptr [S1]
push cs
call near ptr StrLen
les di,[S2] { AX = Length(S1) }
pop cx { CX = Length(S2) }
lds si,[S1]
cmp ax,cx
jle @@1
xchg ax,cx
@@1: inc cx
@@2: mov al,es:[di]
xchg al,[si]
inc si
stosb
loop @@2
pop ds
end;
function PasToNull(const S: String; P: PChar): Word; assembler;
asm
push ds
lds si,[S] { DS:SI = @Pascal source string }
xor ax,ax
les di,[P] { ES:DI = @Null target string }
cld
lodsb { AX = Length(String) }
mov cx,ax { ES:DI = @1st character of source }
jcxz @@1
rep movsb { copy CX chars from DS:SI to ES:DI }
@@1: mov [es:di],cl { Store the null terminator at end }
pop ds
end;
function StrPCopy(Dest: PChar; const Source: String): PChar; assembler;
asm
push ds
lds si,[Source]
les di,[Dest]
cld
mov bx,di
xor ax,ax
mov dx,es
lodsb
xchg ax,cx
rep movsb
xor ax,ax
stosb
mov ax,bx
pop ds
end;
function StrPLCopy(P: PChar; const PasStr: String;
MaxLen: Word): Word; assembler;
asm
xor dx,dx
push ds
lds si,[PasStr] { DS:SI = @Pascal source string }
xor cx,cx
les di,[P] { ES:DI = @Null target string }
mov cl,[si] { CX = Length(String) }
cld
cmp cx,[MaxLen]
jbe @@1
mov cx,[MaxLen]
mov dx,dePathTooLong
@@1: mov ax,cx { Return length of string in AX }
inc si { ES:DI = @1st character of source }
rep movsb { copy CX chars from DS:SI to ES:DI }
mov [es:di],cl { Store the null terminator at end }
pop ds
mov [StrError],dx
end;
function NullToPas(P: PChar): String; assembler;
asm
push ds
les di,[P]
cld
mov cx,-1
xor ax,ax
repne scasb
not cx
lds si,[P]
dec cx
les di,@Result
mov al,cl
stosb
rep movsb
pop ds
end;
function StrLen(P: PChar): Word; assembler;
asm
les di,[P]
mov cx,-1
cld
xor ax,ax
repne scasb
mov ax,-2
sub ax,cx
end;
function StrMove(Dest, Source: PChar; Count: Word): PChar; assembler;
asm
push ds
lds si,[Source]
les di,[Dest]
mov ax,di
mov dx,es
cld
mov cx,[Count]
cmp si,di
jae @@1
std
add si,cx
add di,cx
dec si
dec di
@@1: rep movsb
cld
pop ds
end;
function StrPas(Str: PChar): String; assembler;
asm
push ds
cld
les di,[Str]
mov cx,$FFFF
xor ax,ax
repne scasb
not cx
lds si,[Str]
dec cx
les di,[@Result]
mov ax,cx
stosb
rep movsb
pop ds
end;
function StrLPas(Str: PChar; MaxLen: Word): String; assembler;
asm
push ds
cld
les di,[Str]
xor ax,ax
mov cx,[MaxLen]
mov dx,ax
repne scasb
jz @@1 { Max length not exceeded (found null) }
dec cx { Didn't find null, so add 1 to length }
mov dx,dePathTooLong
@@1: not cx
add cx,[MaxLen]
les di,[@Result]
lds si,[Str]
mov ax,cx
stosb
rep movsb
pop ds
mov [StrError],dx
end;
function StrCopy(Dest, Source: PChar): PChar; assembler;
asm
les di,[Source]
cld
push ds
mov cx,-1
xor ax,ax
repne scasb
les di,[Dest]
not cx
lds si,[Source]
mov ax,es
mov dx,di
rep movsb
pop ds
end;
function StrECopy(Dest, Source: PChar): PChar; assembler;
asm
les di,[Source]
push ds
mov cx,-1
xor ax,ax
cld
repne scasb
not cx
lds si,[Source]
les di,[Dest]
rep movsb
mov ax,di
mov dx,es
pop ds
dec ax
end;
function StrLCopy(Dest, Source: PChar; MaxLen: Word): Word; assembler;
asm
les di,[Source]
push ds
mov cx,[MaxLen]
mov dx,cx
cld
inc cx
xor ax,ax
repne scasb
jz @@1
mov ax,dePathTooLong
@@1: mov [StrError],ax
lds si,[Source]
sub dx,cx
les di,[Dest]
mov cx,dx
rep movsb
stosb
mov ax,dx
pop ds
end;
{ StrEnd returns a pointer to the null character that }
{ terminates Str. }
function StrEnd(Str: PChar): PChar; assembler;
asm
les di,[Str]
cld
mov cx,-1
xor ax,ax
repne scasb
mov ax,di
mov dx,es
dec ax
end;
function StrCat(Dest, Source: PChar): PChar; assembler;
asm
db $66; push [Dest].Word[0]
push cs
call near ptr StrEnd
push dx
push ax
db $66; push [Source].Word[0]
push cs
call near ptr StrCopy
mov ax,[Dest].Word[0]
mov dx,[Dest].Word[2]
end;
{ StrLCat appends at most MaxLen - StrLen(Dest) characters from }
{ Source to the end of Dest, and returns Dest. StrError is set }
{ to dePathTooLong if trancation occurs }
function StrLCat(Dest, Source: PChar; MaxLen: Word): PChar; assembler;
asm
db $66; push [Dest].Word[0]
push cs
call near ptr StrEnd { DS:AX = @Dest[Length] }
mov cx,[Dest].Word[0]
add cx,MaxLen { CX = Highest Dest offset }
sub cx,ax { - Ofs(Dest[Len)) }
jae @@1 { Dest is <= MaxLen }
mov es,dx { Dest is already > MaxLen }
mov di,cx
mov byte ptr [es:di],0 { Truncate Dest }
mov [StrError],dePathTooLong
jmp @@3
@@1: mov [StrError],deNoError
push dx { Append target address }
push ax
db $66; push [Source].Word[0] { Append source address }
push cx { Max no. chars to append }
db $66; push [Source].Word[0]
push cs
call near ptr StrLen { AX = Length(Source) }
pop cx { CX = MaxCopyChars }
cmp ax,cx { if Len(Source) > MaxChars}
jbe @@2 { Dest + Source <= MaxLen }
mov [StrError],dePathTooLong { StrError := dePathTooLong}
mov ax,cx
@@2: push ax
push cs
call near ptr StrLCopy { Append Source to Dest }
@@3: mov ax,[Dest].Word[0]
mov dx,[Dest].Word[2]
end;
{ StrScan returns a pointer to the first occurrence of Chr in }
{ Str. If Chr does not occur in Str, StrScan returns NIL. The }
{ null terminator is considered to be part of the string. }
function StrScan(Str: PChar; Chr: Char): PChar; assembler;
asm
les di,[Str]
cld
push di
mov cx,-1
xor ax,ax
repne scasb
not cx
pop di
mov al,[Chr]
repne scasb
mov al,0
cwd
jne @@Exit
dec di
mov dx,es
mov ax,di
@@Exit:
end;
function StrRScan(Str: PChar; Chr: Char): PChar; assembler;
asm
les di,[Str]
cld
mov cx,-1
xor ax,ax
repne scasb
not cx
std
dec di
mov al,[Chr]
repne scasb
mov al,0
cwd
jne @@Exit
inc di
mov dx,es
mov ax,di
@@Exit:
end;
{ StrCount returns the number of occurences of a given character}
{ in the given string }
function StrCount(Str: PChar; Chr: Char): Word; assembler;
asm
les di,[Str]
xor cx,cx
cld
dec cx
xor ax,ax
repne scasb
mov ax,-2
sub ax,cx
mov cx,ax
xor dx,dx
mov al,[Chr]
@@1: jcxz @@2
repne scasb
jne @@2
inc dx
jmp @@1
@@2: mov ax,dx
end;
{ StrCharCount returns the number of occurences of a given character}
{ in the given array of char }
function StrArrayCount(Str: PChar; Chr: Char; Count: Integer): Word; assembler;
asm
les di,[Str]
mov cx,[Count]
cld
xor dx,dx
mov al,[Chr]
@@1: jcxz @@2
repne scasb
jne @@2
inc dx
jmp @@1
@@2: mov ax,dx
end;
{ StrPos returns a pointer to the first occurrence of Str2 in }
{ Str1. If Str2 does not occur in Str1, StrPos returns NIL. }
function StrPos(Str1, Str2: PChar): PChar; assembler;
asm
les di,[Str2]
push ds
cld
xor ax,ax
mov cx,-1
repne scasb
not cx
dec cx
je @@2
mov dx,cx
mov bx,es
mov ds,dx
les di,[Str1]
mov bx,di
mov cx,-1
repne scasb
not cx
sub cx,dx
jbe @@2
mov di,bx
@@1: mov si,[Str2].Word[0]
lodsb
repne scasb
jne @@2
mov ax,cx
mov bx,di
mov cx,dx
dec cx
repe cmpsb
mov cx,ax
mov di,bx
jne @@1
mov ax,di
mov dx,es
dec ax
jmp @@Exit
@@2: xor ax,ax
xor dx,dx
@@Exit: pop ds
end;
function StrComp(Str1, Str2: PChar): Integer; assembler;
asm
les di,[Str2]
push ds
cld
mov si,di
mov cx,-1
xor ax,ax
cwd
repne scasb
not cx
mov di,si
lds si,[Str1]
repe cmpsb
mov al,[si-1]
mov dl,es:[di-1]
pop ds
sub ax,dx
end;
function StrLComp(Str1, Str2: PChar; MaxLen: Word): Integer; assembler;
asm
les di,[Str2]
push ds
cld
mov si,di
mov ax,[MaxLen]
mov cx,ax
jcxz @@Exit
mov bx,ax
xor ax,ax
xor dx,dx
repne scasb
mov di,si
sub bx,cx
lds si,[Str1]
mov cx,bx
repe cmpsb
mov al,[si-1]
mov dl,es:[di-1]
sub ax,dx
@@Exit: pop ds
end;
function StrLIComp(Str1, Str2: PChar; MaxLen: Word): Integer; assembler;
var
SaveDS: Word;
asm
les di,[Str2]
mov [SaveDS],ds
cld
mov ax,[MaxLen]
mov si,di { Save Str2.Ofs in SI }
mov cx,ax { Count := MaxLen }
jcxz @@Exit { MaxLen = 0, so StrLIComp = 0 }
mov bx,ax { Save MaxLen in BX }
xor ax,ax
xor dx,dx
repne scasb { Look for Str2 null terminator }
sub bx,cx { BX = Min(MaxLen, StrLen(Str2))}
mov cx,bx
mov di,si { ES:DI = addr(Str2) }
lds si,[Str1] { DS:SI = addr(Str1) }
xor bx,bx { BH = 0 }
@@1: repe cmpsb
je @@Exit
push ds
mov bl,[si-1]
mov ds,[SaveDS]
mov al,[bx+offset LoToUpTbl] { AL = DosUpCase[AL] }
mov bl,[es:di-1]
mov bl,[bx+offset LoToUpTbl] { BL = DosUpCase[BL] }
sub ax,bx
pop ds
jz @@1
@@Exit: mov ds,[SaveDS]
end;
function Compare(const Arg1, Arg2; Length: Word): Integer; assembler;
asm
les di,[Arg1]
push ds
lds si,[Arg2]
cld
mov cx,[Length]
xor ax,ax
repe cmpsb
je @@Exit
adc al,0
jne @@Exit
not ax
@@Exit: pop ds
end;
function DosCompare(S1, S2: String): Integer;
var
Result: Integer;
L1,L2 : Word;
begin
DosUppercase(S1);
DosUppercase(S2);
L1 := Length(S1);
L2 := Length(S2);
Result := Compare(S1[1], S2[1], Min(Length(S1), Length(S2)));
if (Result = 0) and (L1 <> L2)
then Result := -1 + (Ord(L1 > L2) shl 1);
DosCompare := Result;
end;
function StrIComp(Str1, Str2: PChar): Integer;
var
Result: Integer;
L1,L2 : Word;
J : Word;
C1,C2 : Char;
begin
L1 := StrLen(Str1);
L2 := StrLen(Str2);
for J := MinWord(L1, L2)-1 downto 0 do
begin
C1 := DosUpCase(Str1^);
C2 := DosUpCase(Str1^);
if (C1 <> C2)
then Break;
Inc(Str1);
Inc(Str2);
end;
Result := 0;
if C1 < C2
then Dec(Result)
else if C1 > C2
then Inc(Result)
else if L1 <> L2
then Result := -1 + (Ord(L1 > L2) shl 1);
StrIComp := Result;
end;
function StrNew(Str: PChar): PChar;
var
L: Word;
P: PChar;
begin
StrNew := nil;
if (Str <> nil) and (Str^ <> #0) then
begin
L := StrLen(Str) + 1;
GetMem(P, L);
if P <> nil
then StrNew := StrMove(P, Str, L);
end;
end;
procedure StrDispose(Str: PChar);
begin
if Str <> nil
then FreeMem(Str, StrLen(Str) + 1);
end;
procedure RunErr(ErrCode: Word); far;
begin
RunError(ErrCode);
end;
procedure CheckDosBuf(var SaveBuf; MinBufSize: Word); assembler;
asm
cmp [DosBuf.Size],0
jne @@1
push cs
call near ptr DosInit
@@1: mov cx,[MinBufSize]
cmp cx,[DosBuf.Size]
jbe @@2
pop ds
push deBadMemBlock
call RunErr
@@2: les di,[SaveBuf]
push ds
lds si,[DosBuf.Buf]
cld
shr cx,1
rep movsw
jnc @@3
movsb
@@3: pop ds
end;
procedure RestoreDosBuf(const SaveBuf; BufSize: Word); assembler;
asm
push ds
les di,[DosBuf.Buf]
lds si,[SaveBuf]
mov cx,[BufSize]
cld
shr cx,1
rep movsw
jnc @@1
movsb
@@1: pop ds
end;
{------------------------- Environment string handling ---------------------}
procedure IndexEnvStr; assembler;
asm
mov es,[PrefixSeg]
xor di,di
mov es,[es:02Ch] { Load ES with environment seg }
cld
xor ax,ax
@@1: cmp al,[es:di] { If 1st byte null, or double null }
je @@Exit { then last environment string done }
dec dx
jz @@Exit
mov cx,-1 { Find next null terminator }
repne scasb
jmp @@1
@@Exit: or dx,dx
end;
function EnvCount: Integer; assembler;
asm
xor dx,dx
call IndexEnvStr
neg dx
mov ax,dx
end;
function EnvStr(Index: Integer): String; assembler;
asm
les si,[@Result]
mov dx,[Index]
mov [byte ptr es:si],0
dec dx
mov bx,es { Save Result segment }
js @@Exit { Invalid index }
inc dx
call IndexEnvStr
jnz @@Exit { Invalid index }
push bx { BX:SI = @Result }
push si
push es { ES:@DI = @EnvStr[Index] }
push di
call NullToPas { Convert to Pascal style string }
@@Exit:
end;
function GetEnv(EnvVar: String): String;
var
S,E : String;
i,j : Integer;
begin
DosUpperCase(EnvVar);
GetEnv := '';
i := EnvCount;
while i > 0 do
begin
S := EnvStr(i);
j := Pos('=', S);
E := Copy(S, 1, j-1);
DosUpperCase(E);
if E = EnvVar then
begin
GetEnv := Copy(S, j+1, 255);
Break;
end;
Dec(i);
end;
end;
{----------------------- Replacement System functions ----------------------}
{$ifdef MsDos}
function GetExtError: Word; assembler; { Return extended DOS err in AX & BX }
asm
mov ax,seg @Data
push ds
push bp
push es
push di
mov ah,$59
mov ds,ax
push ds
int intDos
pop di
pop es
pop ds
pop bp
@@1: mov [DosError],ax
mov bx,ax
pop ds
end;
{$else MsDos}
function GetExtError: Word; { Return extended DOS err in AX & BX }
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AH := $59; { DOS - Get extended error code }
DosError := MsDos(Regs);
DosErrClass:= Regs.BX;
DosErrLocus:= Regs.CH;
GetExtError:= Regs.AX;
asm
mov ax,[DosError]
mov bx,ax
end;
end;
{$endif MsDos}
type
TPathName = packed array[0..fsPathName] of Char;
TPathNet = packed array[0..fsNetPath] of Char;
TShortPath = packed array[0..fsDosPath] of Char;
TShortName = packed array[0..fsDosName] of Char;
TShortDir = packed array[0..fsDosDir] of Char;
PRename = ^TRename; { Structure used by Rename and FRename }
TRename = packed record
Old: TPathNet;
New: TPathNet;
end;
PNetNet = ^TNetNet; { For converting a short or net path to a }
TNetNet = record { long path. }
LongPath: TPathNet; { used by DOS "GetTrueName" so }
NetPath : TPathNet; { result (LongPath) cound be net }
end;
procedure SlashConvert(Length: Word; var Path); assembler;
asm
les di,[Path]
mov cx,[Length]
mov al,'/'
cld
@@1: jcxz @@Exit
repne scasb
jnz @@Exit
mov byte ptr [es:di-1],'\'
jmp @@1
mov di,[Path].Word[0] { @Path in ES:DI }
@@Exit: mov cx,[Length] { Returns with Length in CX and }
end;
function IsDosName(P: PChar): Boolean; { True if P^ is a valid 8.3 DOS name }
type { lowercase chars treated as invalid }
TCharSet = Set of Char;
var
DotCnt : Integer;
CharCnt: Integer;
C : Char;
Padder : Char;
begin { IsDosName }
IsDosName := true;
if not VFat
then Exit;
IsDosName := false;
DotCnt := 0;
CharCnt:= 8+1; { Maximum of 8 characters in the filename }
if P^ = ' '
then Exit; { Dos files names cannot start with space }
while P^ <> #0 do
begin
C := P^;
if (CharCnt = 0) or not (C in DosChars)
then Exit;
if P^ = '.' then
if DotCnt = 0 { Dos filenames can only contain 1 "." }
then begin
Inc(DotCnt);
if (CharCnt = 9) or { No null names or ending in a space char }
(PChar(Ptr(PtrRec(P).Seg, PtrRec(P).Ofs-1))^ = ' ')
then Exit;
CharCnt := 4; { Maximum of .+3 characters for extension }
end
else Exit; { Only one '.' allowed in a file/dir name }
Dec(CharCnt);
Inc(P);
end;
Dec(P);
IsDosName := P^ <> ' '; { Dos file names cannot end with a space }
end;
procedure ConvertNameCase(P: PChar); near;
begin
if (P = nil) or (P^ = #0)
then Exit;
case FileCase of
fnLowerCase:
StrLower(P);
fnUpperCase:
StrUpper(P);
else
{$ifdef LongNames}
if IsDosName(P) then { Must be fnDosLower or fnDos1stUpper }
{$endif LongNames}
begin
if FileCase = fnDos1stUpper
then Inc(P);
StrLower(P);
end;
end;
end;
procedure ConvertPathCase(P: PChar; V: PVolumeInfo); near;
var
N : PChar;
L,C: Word;
begin
if (P = nil) or (P^ = #0) or (FileCase = fnPreserve) {$ifdef LongNames} or
(V^.Attributes and vaCaseSensitive <> 0) {$endif LongNames} then Exit;
C := 0;
L := StrLen(P);
N := P + L;
while PtrRec(N).Ofs <> PtrRec(P).Ofs do
begin
Dec(N);
if N^ = '\' then
begin
Inc(N);
ConvertNameCase(N);
Dec(N);
N^ := #0;
Inc(C);
end;
end;
while C > 0 do
begin
if P^ = #0 then
begin
P^ := '\';
Dec(C);
end;
Inc(P);
Dec(L);
end;
end;
{ Standard Close Text (File or Device) - called by Close(F: Text) }
function TextClose(var T: Text): Integer; far;
var
TR: TTextRec absolute T;
begin
DosError := 0;
if TR.Handle > 4 { Don't close the standard devices }
then FileClose(TR.Handle);
TR.Mode := fmClosed;
TextClose := DosError;
end;
{ Standard Read Text (File or Device) - called by Read/ReadLn(F: Text) }
function TextRead(var T: Text): Integer; far;
var
TR: TTextRec absolute T;
begin
TR.BufEnd := FileRead(TR.Handle, TR.BufPtr^, TR.BufSize);
if DosError <> 0
then TR.BufEnd := 0;
TR.BufPos := 0;
TextRead := DosError;
end;
{ Standard Write to Text (TextFile) - called by Write/WriteLn(F: Text) }
function TextWriteFile(var T: Text): Integer; far;
var
TR: TTextRec absolute T;
begin
if FileWrite(TR.Handle, TR.BufPtr^, TR.BufPos) = TR.BufPos
then TextWriteFile := DosError
else TextWriteFile := 101; { Write Error }
TR.BufPos := 0;
end;
{ Standard Write to Text (TextDevice) - called by Write/WriteLn(F: Text) }
function TextWriteDevice(var T: Text): Integer; far;
var
TR: TTextRec absolute T;
begin
FileWrite(TR.Handle, TR.BufPtr^, TR.BufPos);
TR.BufPos := 0;
TextWriteDevice := DosError;
end;
{ Standard Open text - called by Reset/Rewrite/Append(var F:Text) }
function LfnOpenText(var TR: TTextRec): Integer; far;
var
T : Text absolute TR;
Count : Word;
Inx : Word;
OpenMode: Word;
Pos : Longint;
Regs : TRegisters;
begin
TR.Handle := TFileHandle(-1);
InOutRes := 0;
case TR.Mode of
fmInput:
begin
OpenMode := stOpenRead; { Open file for reading (Reset) }
TR.Handle := 0;
end; { F.Handle = 0 (std i/p) }
fmOutput:
begin
OpenMode := stCreate; { Open file for writing (Rewrite) }
TR.Handle := 1;
end;
fmInOut:
begin
OpenMode := stOpen; { Open file for read/write access }
TR.Handle := 1;
end;
else
begin
LfnOpenText := deInvalidFunc;
Exit;
end;
end;
{$ifdef LongNames}
if TR.Name^ <> #0 then { If not StdIn/StdOut then open file }
{$else LongNames}
if TR.Name[0] <> #0 then
{$endif LongNames}
begin { Add file sharing flags of FileMode }
TR.Handle := FileOpenStr(TR.Name, OpenMode or (System.FileMode and $F0));
if InOutRes <> 0 then
begin
TR.Mode := fmClosed;
LfnOpenText := InOutRes;
Exit;
end;
end;
@TR.CloseFunc := @TextClose;
@TR.InOutFunc := @TextRead;
@TR.FlushFunc := nil;
if TR.Mode <> fmInput then
begin
@TR.InOutFunc := @TextWriteDevice;
@TR.FlushFunc := @TextWriteDevice;
ClearRegs(Regs);
Regs.BX := TR.Handle;
Regs.AX := $4400; { DOS - IOCTRL Get device information }
MsDos(Regs);
if Regs.DL and $80 = 0 then { File, Not device }
begin
@TR.InOutFunc := @TextWriteFile;
@TR.FlushFunc := @TextWriteFile;
if TR.Mode = fmInOut then { File Append }
begin
Pos := MaxLong(FileSeek(TR.Handle, -TR.Bufsize, skEnd), 0);
Count := FileRead(TR.Handle, TR.BufPtr^, TR.BufSize);
Inx := 0;
while Count <> Inx do
begin
if TR.BufPtr^[Inx] = asEOF
then FileSeek(TR.Handle, Inx - Count, skEnd);
Inc(Inx);
end;
TR.Mode := fmOutput;
FileSeek(TR.Handle, Count - Inx, skEnd);
end;
end;
end;
LfnOpenText := InOutRes;
end;
{$ifndef TurboDos}
{ Standard BlockWrite procedure }
procedure BlockWrite(var F: TFileRec; var Buf; Count: Word;
var Result: Word); far;
var
R: Word;
C: Longint;
begin
R := 0;
case F.Mode of
fmClosed:
InOutRes := 103; { File not not open }
fmInput:
InOutRes := 105; { File not open for output }
fmOutput, fmInOut:
InOutRes := 0;
else
InOutRes := 102; { File not assigned }
end;
if InOutRes <> 0
then Exit;
C := LongMulW(Count, F.RecSize);
if C <= MaxFileBlock
then R := FileWrite(F.Handle, Buf, C) div F.RecSize
else InOutRes := 215; { Arithmetic overflow error }
if @Result <> nil
then Result := R;
if (InOutRes = 0) and (R <> Count)
then InOutRes := 101; { Disk write error }
end;
{ Standard BlockRead procedure }
procedure BlockRead(var F: TFileRec; var Buf; Count: Word;
var Result: Word); far;
var
R: Word;
C: Longint;
begin
R := 0;
case F.Mode of
fmClosed:
InOutRes := 103; { File not not open }
fmOutPut:
InOutRes := 104; { File not open for input }
fmInput, fmInOut:
InOutRes := 0;
else
InOutRes := 102; { File not assigned }
end;
if InOutRes <> 0
then Exit;
C := LongMulW(Count, F.RecSize);
if C <= MaxFileBlock
then R := FileRead(F.Handle, Buf, LongRec(C).Lo) div F.RecSize
else InOutRes := 215; { Arithmetic overflow error }
if @Result <> nil
then Result := R;
if (InOutRes = 0) and (R <> Count)
then InOutRes := 100; { Disk read error }
end;
{ Standard typed file read }
procedure LfnFileRead(var F: TFileRec; var Buf); far; assembler;
asm
db $66; push [F].Word[0] { BlockRead(F, Buf, 1, nil); }
db $66; xor ax,ax
db $66; push [Buf].Word[0]
push 1
db $66; push ax
push cs
call near ptr BlockRead
pop bp { Must leave F on the stack! }
retf 4
end;
{ Standard typed file write }
procedure LfnFileWrite(var F: TFileRec; var Buf); far; assembler;
asm
db $66; push [F].Word[0] { BlockWrite(F, Buf, 1, nil); }
db $66; xor ax,ax
db $66; push [Buf].Word[0]
push 1
db $66; push ax
push cs
call near ptr BlockWrite
pop bp { Must leave F on the stack! }
retf 4
end;
{ Standard Seek procedure }
procedure SeekFile(F: TFileRec; Pos: Longint); far;
begin
case F.Mode of
fmClosed:
InOutRes := 103; { File not not open }
fmInput, fmOutPut, fmInOut:
InOutRes := 0;
else
InOutRes := 102; { File not assigned }
end;
FileSeek(F.Handle, Pos * F.RecSize, skStart);
end;
{ Standard FilePos function }
function FilePos(var F: TFileRec): Longint;
begin
case F.Mode of
fmClosed:
InOutRes := 103; { File not not open }
fmInput, fmOutPut, fmInOut:
InOutRes := 0;
else
InOutRes := 102; { File not assigned }
end;
if InOutRes <> 0
then FilePos := -1
else FilePos := FilePosition(F.Handle);
end;
{$endif !TurboDos}
{ Close(F) - replacement System.Close(var F: File) procedure }
procedure LfnCloseFile(var F: TFileRec); far;
begin
if (F.Mode > fmClosed) and (F.Mode <= fmInOut) then
begin
if F.Handle > 4
then FileClose(F.Handle);
F.Mode := fmClosed;
end;
end;
{ Called by System.Reset(var F:File) and System.Rewrite(var F: File) }
function LfnOpenFile(var F: TFileRec; RecSize: Word): Word; far;
var
Mode : Word;
InOut: Word;
begin
asm
mov [Mode],ax { AX = file open mode }
mov [InOut],dx { DX = Function (0=Reset, 1=Rewrite) }
end;
case F.Mode of { Check current status. }
fmInOut, fmOutPut, fmInput: { If file open (any mode), then close}
begin
FileClose(F.Handle);
F.Mode := fmClosed;
end;
fmClosed:
;
else
begin
InOutRes := 102; { File has not been assigned so error}
Exit;
end;
end;
F.Handle := InOut; { Handle = 0 (Std I/P or 1 (Std O/P) }
{$ifdef LongNames}
if (F.Name <> nil) and (F.Name^ <> #0) then{ nul name means StdIn or StdOt}
{$else LongNames}
if F.Name <> #0 then { nul name means StdIn or StdOt}
{$endif LongNames}
begin
F.Handle := FileOpenStr(F.Name, Mode); { Try to open the file in the }
if DosError = 0 then { given mode. }
begin
F.Mode := fmInOut;
F.RecSize := RecSize;
end;
end;
end;
{ Erase(F) - replacement System.Erase(var F: File/var T: Text) procedure }
procedure LfnErase(var F: TFileRec); far;
begin
case F.Mode of
fmClosed:
FileErase(F.Name);
fmInput..fmInOut:
InOutRes := 5; { File Access Denied }
else
InOutRes := 102; { File not assigned }
end;
end;
procedure SystemFreeMem(P: Pointer; Size: Word); near;
begin
FreeMem(P, Size);
end;
{$ifdef LongNames}
{ Inputs: ES:DI @TFileRec that is being assigned or destroyed }
procedure UnAssignName; near; assembler;
asm
cmp [es:di].TFileRec.Mode,fmClosed { Make sure F has been }
jb @@Exit { previously assigned. }
cmp [es:di].TFileRec.Mode,fmInOut
ja @@Exit
cmp [es:di].TfileRec.NameLen,type TFileRec.NameBuf
jbe @@Exit { If the length of the name}
mov [es:di].TFileRec.Mode,0 { record is unasssigned }
pusha { is > SizeOf internal name}
push es { name buffer, its on heap }
db $66; push [es:di].TFileRec.Name.Word[0]
push [es:di].TfileRec.NameLen
call SystemFreeMem
pop es
popa
@@Exit:
end;
{$endif LongNames}
{ UnAssign(F) - unlinks File or Text variable from its external file/device }
procedure UnAssign(var F); assembler;
asm
les di,[F]
mov ax,[es:di].TFileRec.Mode
cmp ax,fmClosed
jb @@Done { File/Text not assigned }
cmp ax,fmOutput
jb @@DoClose { Not open for writing }
cmp ax,fmInOut
ja @@Done { File/Text not assigned }
db $66; cmp [es:di].TTextRec.InOutFunc.Word[0],0
je @@DoClose
push es { Flush the Text file }
push di
call [es:di].TTextRec.InOutFunc
@@DoClose: les di,[F]
cmp [es:di].TTextRec.Handle,4
jbe @@NoClose
db $66; cmp [es:di].TTextRec.CloseFunc.Word[0],0
jne @@CloseTxt
@@CloseFle: push es { Use LfnCloseFile to close }
push di
push cs
call near ptr LfnCloseFile
jmp @@NoClose
@@CloseTxt: push es { Use CloseFunc to close }
push di { the Text/File }
call [es:di].TTextRec.CloseFunc
or ax,ax { Was it sucessful? }
jne @@Done
@@NoClose: les di,[F] { No, so don't unassign it }
mov [es:di].TTextRec.Mode,fmClosed
@@UnAssign:
{$ifdef LongNames}
call UnAssignName { Unallocate long filename }
{$else LongNames} { and mark it as unassigned }
mov [es:di].TFileRec.Mode,0 { Mark it as unassigned }
{$endif LongNames}
@@Done:
end;
{ Inputs: ES:DI = @TFileRec that is being assigned or destroyed }
{ DS:SI = @FileName argument }
{ DX = Filename type (1 -> PChar 0 -> PString) }
procedure AssignName; near; assembler;
var
TempName: TPathName;
Len : Word;
asm
add di,Offset(TFileRec.Name) { ES:DI = @TFileRec.Name }
push di
push es { Save pointer to TFileRec.Name }
cld
push ss
pop es
lea di,[TempName] { ES:DI = @TempName }
{$ifdef Windows}
push es { Push arguments for AnsiToOem call }
push di
push es
push di
{$endif Windows}
push es { Push argument to StrNew (@TempName)}
push di
push es { Push argument to StrLen (@TempName)}
push di
push es { Push arg to FileExpand(@TempName) }
push di
push es { Push arg to FileExpand(@TempName) }
push di
mov cx,type TempName { CX = Max filename length }
or dx,dx { PChar or String argument? }
jne @@2 { PChar }
lodsb { AL = AX = Filename String length }
{ cmp cx,ax }{ Name too long? }
{ jbe @@1 }{ No }
mov cx,ax { Limit length to SizeOf(TempName) }
xor bx,bx
jcxz @@3 { Null name passed }
@@2: lodsb { Get next filename character }
or al,al { Null Terminator? }
je @@3 { Yes }
stosb { Store character in TempName }
inc bx
loop @@2 { Until all characters copied }
@@3: xor ax,ax { Store null-terminator }
stosb
or bx,bx
mov ax,seg @Data
mov ds,ax
jnz @@4
{$ifdef Windows}
add sp,20 { Pop FileExpand, StrLen OemToAnsii }
{$else Windows}
add sp,12 { Pop FileExpand & StrLen arguments }
{$endif Windows}
jmp @@NoAlloc
@@4: push fcDirectory + fcCasePreserve{ Filename doesn't have to }
push cs { exist, but its path does. }
call near ptr FileExpand { Expand filename into fully-qualif'd}
call StrLen { filename, get qualified length }
inc ax { Include null terminator in length }
mov [Len],ax
{$ifdef Windows}
call AnsiToOem { Convert TempName to OEM string }
{$endif Windows}
mov cx,[Len] { Check & store filename length }
{$ifdef LongNames}
cmp cx,type TFileRec.NameBuf { Is Filename short enough to be}
jbe @@NoAlloc { stored in the TTextRec? (Yes) }
call StrNew { Store filename on the heap }
cld
pop es { ES:DI = @FileRec.Name }
pop di
mov cx,[Len]
mov [es:di+Offset(TFileRec.NameLen)-Offset(TFileRec.Name)],cx
stosw { FileRec.Name = @HeapName }
mov ax,dx
stosw
jmp @@Exit
{$endif LongNames}
@@NoAlloc: pop si { DS:SI = @F.Name }
pop ds
pop es { ES:DI = @TempName }
pop di
{$ifdef LongNames}
mov ax,di { F.Name = @F.NameBuf }
mov [es:di+Offset(TFileRec.NameLen)-Offset(TFileRec.Name)],cx
add ax,type TFileRec.Name
stosw
mov ax,es
stosw
{$endif LongNames}
rep movsb { Store filename in F.NameBuf }
@@Exit:
end;
{ Rename(F) replacement System.Rename(var F: File/var T: Text) procedure }
procedure LfnRename(var F: TFileRec; NewName: PChar); far; assembler;
var
New: TPathName;
asm
les di,[F]
mov [InOutRes],102 { File not assigned }
cmp [es:di].TFileRec.Mode,fmInOut
ja @@Exit
cmp [es:di].TFileRec.Mode,fmClosed
jb @@Exit
je @@1
mov [DosError],deAccessDenied { File access denied }
jmp @@Exit
@@1: push dx { Save PChar/PString flag }
{$ifdef Longnames}
db $66; push [es:di].TFileRec.Name.Word[0] { FileRename arguments }
{$else Longnames}
add di,TFileRec.Name
push es
push di
{$endif Longnames}
or dx,dx { PChar or String NewName? }
db $66; push NewName.Word[0] { Push PasToNull arguments if String }
jnz @@PChar1 { or FileRename arguments if PChar }
lea di,New
push ss
push di
call PasToNull
lea di,New
push ss { Push FileRename arguments }
push di
@@PChar1: call FileRename { Try to rename the file }
cmp [DosError],deNoError
jne @@Exit
les di,[F]
{$ifdef LongNames}
call UnAssignName { Dispose of possibly old long name }
{$endif LongNames}
pop dx { Get PChar/PString flag }
push ds
lds si,[NewName]
call AssignName
pop ds
@@Exit:
end;
{ Assign(F) - System.Assign(var F:File) standard procedure (String or PChar)}
procedure LfnAssignFile(var F: TFileRec; FileName: PChar); far; assembler;
asm { SS:BX+8 = @TFileRec }
{ XOR DX,DX ; DX = 0 = String arg. DX = 1 = PChar}
{ MOV BX,SP ; Why not use BP ? }
{ System code is hooked here }
{ ES:DI = @TFileRec }
les di,[F]
push ds
push dx
cld
xor ax,ax
stosw { F.Handle = 0 }
mov ax,fmClosed
stosw { F.Mode = fmClosed }
xor ax,ax
mov cx,(offset(TFileRec.Name)-offset(TFileRec.RecSize))/2
rep stosw { Set all other TFileRec fields to 0 }
pop dx { DX = PChar/PString flag }
sub di,offset(TFileRec.Name) { ES:DI = @F }
lds si,[FileName] { DS:SI = @FileName }
call AssignName
pop ds
end;
{ Assign(T) - System.Assign(var T:Text) standard procedure (String or PChar)}
procedure LfnAssignText(var T: TTextRec; FileName: PChar); far;
var
F: TFileRec absolute T;
begin
LfnAssignFile(F, Filename);
@T.OpenFunc := @LfnOpenText;
T.BufPtr := @T.Buffer;
T.BufSize:= SizeOf(TTextBuf);
end;
{-------------------------- Miscellaneous functions ------------------------}
{$ifndef TurboDos}
procedure GetCBreak(var Break: Boolean);
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AX := $3300;
MsDos(Regs);
Break := Boolean(Regs.DL);
end;
procedure SetCBreak(Break: Boolean);
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AX := $3301;
Regs.DL := Byte(Break);
MsDos(Regs);
end;
{$endif !TurboDos}
type
PFcb = ^TFcb;
TFcb = record { DOS standard file control block }
Drive : Byte;
Name : array[0..7] of Char;
Ext : array[0..2] of Char;
BlockNum : Word;
RecSize : Word;
FileSize : DWord;
WriteDate: Word;
WriteTime: Word;
Reserved : array[0..7] of Byte;
CurRecNum: Byte;
RndRecNum: DWord;
end;
TPsp = array[0..127] of Byte;
TExecParamBlock = record { DOS "Exec" parameter block }
EnvSeg : Word;
CmdTail: PString;
FcB1 : PFcb;
Fcb2 : PFcb;
SS_SP : DosPtr;
CS_IP : DosPtr;
end;
PExec = ^TExec;
TExec = record { Structure of data stored in DosBuf }
{$ifdef DPMI}
Prefix : TPsp;
{$endif DPMI}
ExePath: TShortPath;
Block : TExecParamBlock;
case integer of
0: (CmdLine : TComStr;
Fcb1 : TFcb;
Fcb2 : TFcb);
1: (LongPath: TPathName);
end;
{.$ifdef TurboDos}
procedure Exec(const Path: String; const CmdLine: TComStr); assembler;
var
ExecRec : TExecParamBlock;
FileBlock1: TFcb;
FileBlock2: TFcb;
PathBuf : TShortPath;
CmdLineBuf: TComStr;
Regs : TRegisters absolute CmdLineBuf;
const
SaveSP : Word = 0; { Use typed contants so they go in }
SaveSS : Word = 0; { the data segment, not on stack }
asm
lea di,PathBuf { PasToNull(FDosExpand(Path), @PathBuf); }
push ss { PasToNull String argument }
push di
push ss { PasToNull PChar argument }
push di
push ss { FDosExpand return String }
push di
db $66; push word ptr [Path] { FDosExpand String argument }
push cs
call near ptr FDosExpand
call PasToNull
{ mov [SaveSP],sp } { MS-DOS 2.x trashes all regs, incl}
{ mov [SaveSS],ss } { SS and SP, so save them in DS }
mov ds,[PrefixSeg] { ExecData.EnvSeg = @PrefixSeg }
mov ax,word ptr ds:[$2C]
mov [ExecRec.EnvSeg],ax
lds si,[CmdLine] { Convert command line to ASCIIZ }
lea di,[CmdLineBuf] { and store in CmdLine buffer }
lodsb
cmp al,126
jb @@2
mov al,126
@@2: stosb { Store command line length byte }
cbw
xchg ax,cx
rep movsb
mov al,$0D { Store terminating carriage retrn }
stosb
push ss
lea si,[CmdLineBuf]
pop ds
mov [ExecRec.CmdTail].Word[0],si { Store ptr to command line }
mov [ExecRec.CmdTail].Word[2],ds
inc si { DS:SI = @CommandLine[1] }
lea di,[FileBlock1] { ExecData.Fcb1] = @FileBlock1 }
mov [ExecRec.Fcb1].Word[0],di
mov [ExecRec.Fcb1].Word[2],es
mov ax,$2901 { Parse 1st command arg into Fcb1^}
int intDos { Use exsisting Drive number in }
lea di,[FileBlock2] { FCB if none specified }
mov [ExecRec.Fcb2].Word[0],di { ExecData.Fcb1] = @FileBlock1 }
mov [ExecRec.Fcb2].Word[2],es
mov ax,$2901 { Parse 2nd command arg into Fcb1^}
int intDos
lea dx,[PathBuf]
lea bx,[ExecRec] { ES:DI = @ExecData }
mov ax,$4B00 { DOS - Load and Execute }
int intDos
jc @@3
xor ax,ax
@@3: mov dx,seg @Data { Restore DS to global data seg }
cld
mov ds,dx
{ mov ss,[SaveSS] } { Restore stack pointer }
{ mov sp,[SaveSP] }
mov [DosError],ax
{$ifdef MsDos}
push dx
mov ah,$1A { MsDos - Set Disk Transfer Addr }
lds dx,[DosBuf.RealBuf] { DTA = DosBuf.RealBuf }
int intDos
pop ds
{$else MsDos}
push ss
lea di,Regs
pop es
mov cx,type TRegisters / 2
xor ax,ax
rep stosw
sub di,type TRegisters
mov ax,[DosBuf.RealSeg]
mov es:[di].TRegisters.&AH,$1A
mov es:[di].TRegisters.&DS,ax
mov bx,intDos
mov ax,dpmiCallRealInt
int intDPMI
{$endif MsDos}
end;
(*
{$else TurboDos}
{ The following Pascal code works, except when the DOS - Exec interrupt is }
{ called it reports error 8 - Memory Control Block Destroyed. I suspect this}
{ is because the created Psp has not been allocated by DOS ? }
procedure Exec(const Path: String; const CmdLine: TComStr);
var
Regs : TRegisters;
CmdLen : Word;
Buf : TDosBuf;
ExecRec: PExec absolute Buf;
begin
if not GetDosMem(Buf, SizeOf(TExec)) then
begin
DosError := deNotEnoughMem;
Exit;
end;
ClearRegs(Regs);
Regs.ES := Buf.RealSeg; { ES:DI @ShortPath (ExePath) }
Regs.DS := Regs.ES; { DS:DX @LongPath }
{$ifdef LongNames}
if VFat
then begin { Exec does not support LFN's, so }
Regs.SI := Ofs(ExecRec^.LongPath); { convert long path and put in }
StrPLCopy(@ExecRec^.LongPath, Path, Buf.Size); { DosBuf }
Regs.DI := Ofs(ExecRec^.ExePath); { ES:DI = @ShortName }
Regs.CX := $8001; { Get short path, use subst drive }
Regs.AX := $7160; { LFN - Get short filename }
DosError := MsDos(Regs); { we must convert the EXE filename}
if Regs.Flags and fCarry <> 0 { to its short path equivalent. }
then Exit;
end
else
{$endif LongNames}
StrPLCopy(@ExecRec^.ExePath, Path, High(TPathStr));{ Put path in Buf.Path }
CmdLen := Min(Length(CmdLine), 126); { Cmdline -> DosBuf }
Move(CmdLine[1], ExecRec^.CmdLine[1], CmdLen);
ExecRec^.CmdLine[0] := Char(CmdLen);
ExecRec^.CmdLine[CmdLen+1] := #13;
PtrRec(ExecRec^.Block.CmdTail).Seg := Regs.DS;
PtrRec(ExecRec^.Block.Fcb1).Seg := Regs.DS;
PtrRec(ExecRec^.Block.Fcb2).Seg := Regs.DS;
PtrRec(ExecRec^.Block.CmdTail).Ofs := Offset(@ExecRec^.CmdLine);
PtrRec(ExecRec^.Block.Fcb1).Ofs := Offset(@ExecRec^.Fcb1);
PtrRec(ExecRec^.Block.Fcb2).Ofs := Offset(@ExecRec^.Fcb2);
{$ifdef DPMI}
Move(Ptr(PWord(Ptr(PrefixSeg, $002C))^, 0)^, ExecRec^.Prefix, SizeOf(TPsp));
ExecRec^.Block.EnvSeg := 0 {Regs.DS};
{$else DPMI}
ExecRec^.Block.EnvSeg := PWord(Ptr(PrefixSeg, $002C))^;
{$endif DPMI}
Regs.SI := Offset(@ExecRec^.CmdLine[1]);{ DS:SI = @CommandLine[1] }
Regs.DI := Offset(@ExecRec^.Fcb1); { ES:DI = @Fcb1 }
Regs.AX := $2901; { DOS - Parse Filname into FCB }
DosError := MsDos(Regs);
if Regs.Flags and fCarry <> 0
then Exit;
Regs.AX := $2901; { DOS - Parse Filname into FCB }
Regs.DI := Offset(@ExecRec^.Fcb2); { ES:DI = @Fcb2 }
DosError := MsDos(Regs);
if Regs.Flags and fCarry <> 0
then Exit;
Regs.DX := Offset(@ExecRec^.ExePath); { DS:DX = @ExecPath }
Regs.BX := Offset(@ExecRec^.Block); { ES:BX = @ExecParameterBlock }
Regs.AX := $4B00; { DOS - Load and Execute }
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0;
FreeDosMem(Buf);
end;
{$endif TurboDos}
*)
function DosExitCode: Word;
{$ifdef TurboDos} assembler;
asm
mov ah,$4D
int intDos
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AH := $4D;
DosExitCode := MsDos(Regs);
end;
{$endif TurboDos}
procedure Keep(ExitCode: Byte); assembler;
asm
mov es,[PrefixSeg]
mov ax,es
mov dx,word ptr [es:$0002]
sub dx,ax
mov al,[ExitCode]
mov ah,$31
int intDos
end;
function GetLocalName: TMachineName;
{$ifdef TurboDos}
assembler;
asm
push ds
lds di,[@Result]
xor ax,ax
mov dx,di
mov [di],ax
inc dx { DS:DX @Result[1] }
mov ax,$5E00; { DOS Network - Get Machine name }
int intDos
mov bx,ax
jc @@Exit
cmp ch,0
je @@Exit
mov cx,high(TMachineName)
add di,cx
mov al,' '
@@1: cmp al,[di]
jne @@2
dec di
loop @@1
@@2: sub di,cx
mov [di],cl
xor bx,bx
@@Exit: pop ds
mov [DosError],bx
end;
{$else TurboDos}
var
Name : PMachineName absolute DosBuf;
Regs : TRegisters;
SaveBuf: TMachineName;
Result : TMachineName;
j : Word;
begin
Result := '';
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
ClearRegs(Regs);
Regs.DS := DosBuf.RealSeg;
Regs.AH := $5E; { DOS Network - Get Machine name }
DosError:= MsDos(Regs);
if (Regs.Flags and fCarry = 0) and (Regs.CH <> 0) then
begin
DosError := deNoError;
Move(Name^, Result[1], High(TMachineName));
j := High(TMachineName);
while Result[j] = ' ' do { Remove trailing padding spaces }
Dec(j);
Result[0] := Chr(j);
end;
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
GetLocalName := Result;
end;
{$endif TurboDos}
procedure GetTempFileStr(TempName: PChar; AutoErase: Boolean);
var
Temp: PPathStr absolute TempName;
begin
Temp^ := NullToPas(TempName);
GetTempFile(Temp^, AutoErase);
PasToNull(Temp^, TempName);
end;
procedure GetTempFile(var TempName: TPathStr; AutoErase: Boolean);
var
Suffix: String[3];
j : Byte;
SR : TSearchRec;
Temp : TDirStr;
begin
{ Get temp directory name from environment TMP or TEMP variable or use }
{ the application's directory if there's no valid TMP or TEMP directory }
if TempDir = nil then
begin
Temp := GetEnv('TEMP');
if (Temp = '')
then Temp := GetEnv('TMP');
{$V-}AddDirSep(Temp); {$V+} { Append backslash if not in variable }
if Temp = '\' { Verify the existance of the temp dir}
then Temp := ExeDir
else begin
Temp := FExpand(Temp, fcCasePreserve + fcFileName);
AddDirSep(Temp);
if DosError <> deNoError
then Temp := ExeDir;
end;
TempDir := PDirStr(NewStr(Temp));
end;
{ Keep generating file names until we find one that's not in use }
TempNameCnt := 0;
while true do
begin { Append a suitable file name }
repeat
if not (TempNameCnt in TempNums)
then Break;
Inc(TempNameCnt); { Advance counter }
until TempNameCnt = 256;
if TempNameCnt = 256 then
begin
TempName := '';
DosError := deFileExists;
Exit;
end;
Str(TempNameCnt, Suffix); { Create numeric suffix }
for j := Length(Suffix)+1 to High(Suffix) do
Suffix := '0' + Suffix;
TempName := FExpand(TempDir^ + TempPrefix + Suffix + '.TMP',
fcCasePreserve + fcFileName);
{ See if file name is already in use }
if (DosError = deFileNotFound) or (DosError = dePathNotFound)
then Break;
Inc(TempNameCnt); { Current number is in }
end; { use, so incriment it. }
if AutoErase
then Include(TempNums, TempNameCnt);
DosError := deNoError;
end;
procedure EraseTempFile(const TempName: TPathStr);
var
J,E: Integer;
begin
FErase(TempName);
Val(Copy(TempName, Pos('.', TempName)-3, 3), J,E);
if E = 0
then Exclude(TempNums, J);
end;
procedure EraseTempFileStr(TempName: PChar);
var
Temp: PString absolute TempName;
begin
Temp^ := NullToPas(TempName);
EraseTempFile(Temp^);
PasToNull(Temp^, TempName);
end;
{-------------------------- Additional Drive functions ---------------------}
function PathValid(Path: PChar): Boolean;
begin
end;
type
PValidRec = ^TValidRec;
TValidRec = record
Name: array[0..79] of Char;
Fcb : array[0..36] of Byte;
end;
function DosPathValid(const Path: TPathStr): Boolean; assembler;
var
Rec: PValidRec absolute DosBuf;
{$ifdef DPMI}
var
Regs: TRegisters;
{$endif DPMI}
asm
push ds { Convert Pascal-style string to a }
les di,[DosBuf.Buf] { (ES:DI = @Rec.Name) }
xor cx,cx { null-terminated string. }
cmp [DosBuf.Size],type TValidRec
lds si,[Path]
jae @@1
mov al,0
pop ds
jmp @@Exit
@@1: cld
mov cl,[si]
inc si
rep movsb
mov [es:di],cl
{$ifdef MSDOS}
mov ax,es
mov si,cx { DS:SI = @Rec.Name }
mov ds,ax
mov di,TValidRec.Fcb { ES:DI = @Rec.Fcb }
mov ax,$2906; { DOS function 29h = Parse Filename }
int intDos
pop ds
{$else MSDOS}
pop ds
mov dx,[DosBuf.RealSeg]
push ss
lea di,Regs
pop es { ES:DI = @RealRegs }
cld
mov cx,type TRegisters / 2
xor ax,ax
rep stosw
mov [Regs.&DS],dx { Regs.DS:SI = @Rec.Name }
lea di,Regs { ES:DI = @Regs }
mov [Regs.&ES],dx
mov ax,dpmiCallRealInt { Call real-mode interrupt }
mov [Regs.&AX],$2906; { DOS Fn 2906h = Parse Filename }
mov bx,intDos { BL = interrupt number ($21) }
mov [Regs.&DI],TValidRec.Fcb { Regs.ES:DI = @Rec.Fcb }
int intDPMI { CX already equals 0 }
mov ax,[Regs.&AX]
{$endif MSDOS}
inc al
jz @@Exit { if Regs.al <> $FF }
mov al,1 { then Path is valid }
@@Exit:
end;
function GetDrives: String; { Return list of valid system }
var { drives. eg: a return string of}
Drv : Char; { 'ACD' means drives A:, C: and }
Pad : Char; { D: are valid on this machine. }
Regs: TRegisters;
begin
if DriveList = '' then
begin
for Drv := 'A' to 'Z' do
if DosPathValid(Drv + ':')
then DriveList := DriveList + Drv;
if Pos('AB', DriveList) <> 0 then { Check for single floppy }
begin
ClearRegs(Regs);
Regs.AX := $440E; { IOCTL - Get logical device map}
Regs.BL := 1; { for Drive A: }
if (Lo(MsDos(Regs)) <> 0) then
begin
if Regs.AL <> 1 then
begin
Regs.AX := $440F; { IOCTL - Set logical device map}
Regs.BL := 1;
MsDos(Regs);
end;
Delete(DriveList, 2, 1); { Remove Drive B from list }
end;
end;
end;
GetDrives := DriveList;
end;
function DriveValid(Drive: Char): Boolean;
begin
DriveValid := Pos(DosUpCase(Drive), GetDrives) <> 0;
end;
function DriveRemove(Drive: Char): Boolean;
var
V: PVolumeInfo;
begin
V := GetVolumeInfo(UpCase(Drive));
DriveRemove := (V <> nil) and (V^.Attributes and vaIsRemoveable <> 0);
end;
function GetDriveInfo(Drive: Char; var Info: TBlockDevInfo): Byte;
{$ifdef DPMI}
var
Regs : TRegisters;
SaveBuf: TBlockDevInfo;
DevInfo: PBlockDevInfo absolute DosBuf;
begin
GetDriveInfo := dtError;
Drive := DosUpCase(Drive);
CheckDosBuf(SaveBuf, SizeOf(SaveBuf)); { Save contents of DosBuf }
DevInfo^.SpecialFunc := 1; { Use exsisting BPB }
ClearRegs(Regs);
Regs.AX := $440D; { IOCTL Generic block I/O }
Regs.CX := $0860; { Disk drive: Get device params }
Regs.BL := Byte(Drive) - (Byte('A')-1); { BL contains drive number }
Regs.DS := DosBuf.RealSeg;
MsDos(Regs);
if Regs.Flags and fCarry = 0
then GetDriveInfo := DevInfo^.DeviceType
else GetDriveInfo := dtError;
Move(DevInfo^, Info, SizeOf(Info));
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf)); { Restore contents of DosBuf }
{$else DPMI} assembler;
asm
push ds
mov dl,[Drive]
and dl,01011111b { Convert Drive letter to upper }
cmp dl,'A' { case and Check if in range }
jb @@BadExit
cmp dl,'Z'
ja @@BadExit
and dl,00011111b { Convert to drive number }
lds bx,[Info] { Index Block Device info block }
mov ax,$440D { IOCTL: Generic block I/O }
mov cx,$0860 { Disk drive: Get device params }
mov byte ptr [ds:bx],1 { Use exsisting BPB }
xchg dx,bx { DS:DX indexes the param block }
int 21h { BL contains drive number }
mov bx,dx
mov al,[ds:bx+TBlockDevInfo.DeviceType]
jnc @@Exit
@@BadExit: mov al,-1 { -1 indicates a bad call }
@@Exit: pop ds { Otherwise function returns the}
{$endif DPMI} { device type }
end;
function IsCdRom(Drive: Char): WordBool; assembler;
{$ifdef DPMI} { Check to see if it's a CD-ROM }
var
Regs: TRegisters;
asm
push ss
xor ax,ax
pop es
mov cx,type TRegisters / 2 { FillChar(Regs,SizeOf(Regs), 0)}
lea di,Regs
cld
rep stosw
mov cl,[Drive]
mov [Regs.&AX],$150B
sub cl,'A' { "A:" = 0, "B:" = 1 etc }
sub di,type TRegisters { ES:DI = @RealRegs }
mov [Regs.&CX],cx { CX = Drive number }
xor cx,cx { Use stack provided by DPMI }
mov ax,dpmiCallRealInt { Call real-mode interrupt }
mov bx,$002F { $2F (DOS Multiplex interrupt) }
int intDPMI
mov ax,0
jc @@NotCD { DPMI function failed }
cmp [Regs.&BX],$ADAD { Verification ID }
jne @@NotCD
mov ax,[Regs.&AX] { Non-zero means Drive is a CD }
{$else DPMI}
asm
mov cl,[Drive]
mov ax,$150B
mov ch,0
push bp
sub cl,'A'
push ds
int $2F
pop ds
pop bp
xor dx,dx
cmp bx,$ADAD
xchg dx,ax
jne @@NotCD
mov ax,dx { Non-zero means Drive is a CD }
{$endif DPMI}
@@NotCD:
end;
function GetFloppyType(Drive: Char): Byte; assembler;
{$ifdef DPMI}
var
Regs: TRegisters;
asm
push ss
xor ax,ax
pop es
mov cx,type TRegisters / 2
lea di,Regs
cld
mov dl,[Drive]
rep stosw
and dl,01011111b { Convert Drive letter to upper }
mov [Regs.&AH],$08
sub dl,'A'
lea di,Regs { ES:DI = @Regs }
mov [Regs.&DL],dl { DL = Drive number }
mov ax,dpmiCallRealInt { Simulate real-mode interrupt }
mov bx,$13 { $13 (BIOS Drives) }
int intDPMI
mov ah,[Regs.&AH] { AH <> 0 means error }
mov bl,[Regs.&BL] { 1 = 360, 2 = 1200, 3 = 720 }
{$else DPMI}
asm
mov dl,[Drive]
mov ah,$08
and dl,01011111b { Convert Drive letter to upper }
sub dl,'A' { A: = 0 }
int $13
{$endif DPMI} { 4 = 1440 }
cmp ah,0
je @@1 { AH <> 0 means error }
mov bl,0
@@1: mov al,bl
end;
function GetChangeLineType(Drive: Char): Byte; assembler;
{$ifdef DPMI}
var
Regs: TRegisters;
asm
push ss
xor ax,ax
pop es
mov cx,type TRegisters / 2
lea di,Regs
cld
mov dl,[Drive]
rep stosw { CX=0 = Use stack provided by }
and dl,01011111b { Convert Drive letter to upper }
mov [Regs.&AX],$1500 { the DPMI server }
sub dl,'A'
sub di,type TRegisters { ES:DI = @RealRegs }
mov [Regs.&DL],dl { DL = Drive number }
mov ax,dpmiCallRealInt { Simulate real-mode interrupt }
mov bx,$13 { $13 (BIOS Drives) }
int intDPMI
shr [Regs.&Flags],1 { 0= NoDrive 1= Floppy w/o }
mov al,[Regs.&AH] { 2= Floppy with 3= HardDisk }
{$else DPMI}
asm
mov dl,[Drive]
mov ah,$15
and dl,01011111b { Convert Drive letter to upper }
sub dl,'A'
int 13h
mov al,ah
{$endif DPMI}
jnc @@Exit { Call is valid AL contains type}
mov al,4
@@Exit:
end;
function GetDriveType(Drive: Char;
var IsRemoveable, HasChangeLine: Boolean): Byte;
var
V: PVolumeInfo;
begin
V := GetVolumeInfo(Drive);
IsRemoveable := V^.Attributes and vaIsRemoveable <> 0;
HasChangeLine:= V^.Attributes and vaHasChangeLine <> 0;
GetDriveType := V^.DriveType;
end;
{ Validate and return drive type given a drive letter }
type
PDosDPB = ^TDosDPB;
TDosDPB = record { 21 32-- DOS Drive Parameter Block: }
DriveNum : Byte; { drive number (00h = A:, 01h = B:, etc) }
UnitNum : Byte; { unit number within device driver }
BytesPerSector : Word; { bytes per sector }
HighestSectInCluster: Byte; { highest sector number within a cluster }
ClustToSectShiftCnt : Byte; { shift count for clusters into sectors }
ReservedSectors : Word; { No. of reserved sectors at strt of drive}
NumOfFats : Byte; { number of FATs }
NumOfRootEntries : Word; { number of root directory entries }
FistUserDataSector : Word; { number of 1st sector containg user data }
HighestClusterNum : Word; { highest cluster number (data clusters+1)}
{ 16-bit FAT if > 0FF6h, else 12-bit FAT }
SectorsPerFat : Byte; { number of sectors per FAT }
FirstDirSector : Word; { sector number of first directory sector }
DeviceDriverHeader : DosPtr; { address of device driver header }
MediaID : Byte; { media ID byte (see #0655) }
DiskAccessed : Byte; { 00h if disk accessed, FFh if not }
NextDPB : DosPtr; { pointer to next DPB }
end;
function GetStdDriveType(Drive: Char;
var IsRemoveable, HasChangeLine: Boolean): Byte;
var
CLT : Byte;
DevType: Byte;
Info : TBlockDevInfo;
DPB : PDosDPB;
Regs : TRegisters;
const
FloppyTbl: array[0..6] of Byte = (
dtUnKnown, dtFloppy360, dtFloppy1200, dtFloppy720, dtFloppy1440,
dtFloppy2880, dtUnKnown);
begin
GetStdDriveType := dtError;
IsRemoveable := false;
HasChangeLine:= false;
Drive := DosUpCase(Drive);
if (Drive < 'A') or (Drive > 'Z')
then Exit;
{ First see if it's a floppy with or without a change-line }
CLT := GetChangeLineType(Drive); { "Floppy" probably means any drive }
case CLT of { with removable media. }
{ 0 is supposed to indicate an invalid drive, but some BIOS's report }
{ fixed drives as invalid too, so GetChangeLineType cannot be used to }
{ weed out drive B: on a single floppy drive system. }
(*
0: { CLT = 0 => drive not valid. This }
Exit; { weeds out B: on a single floppy sys. }
*)
1,2:
begin { Floppy with and w/o changeline }
IsRemoveable := true; { Drive media is removable. }
HasChangeLine := CLT = 2; { Device supports changeline if CLT =2 }
if IsCDRom(Drive)
then GetStdDriveType := dtCDRom
else GetStdDriveType := FloppyTbl[Min(GetFloppyType(Drive),
High(FloppyTbl))];
end
else { CLT is not 1 or 2 }
begin
DevType := GetDriveInfo(Drive, Info);
if DevType >= dtUnknown
then if IsCDRom(Drive) { Not a standard DOS block device }
then begin { It's a CD-ROM drive }
GetStdDriveType := dtCDRom;
IsRemoveable := true;
end
else begin
if DevType = dtError then
begin
DevType := dtFixedDisk;
ClearRegs(Regs);
Regs.AH := $32;
Regs.DL := Ord(Drive) - Ord('A') + 1;
MsDos(Regs);
if Regs.AL = 0 then
begin
DPB := MapDosPtr(Ptr(Regs.DS, Regs.BX));
if (DPB^.NumOfFATs = 1) and (DPB^.UnitNum = 0)
then DevType := dtRAM;
FreeDosPtr(DPB);
end;
end;
GetStdDriveType := DevType;
end
else begin { Standard DOS block device }
IsRemoveable := Info.DeviceAttr and bdaNotRemoveable = 0;
HasChangeLine := Info.DeviceAttr and bdaHasChangeLine <> 0;
GetStdDriveType := DevType;
if (DevType = dtFixedDisk) and
(Info.NumFATs = 1) and (Info.NumHeads = 1)
then GetStdDriveType := dtRAM;
end;
end;
end;
end;
{ Determine the properties of a given drive volume }
{ The following assumptions have to be made about local file/dir paths:}
{ Non-LFN: }
{ MaxNameLen = 12 (FILENAME.EXT) }
{ MaxPathLen = 79 (Including local drive name) }
{ MaxExtLen = 4 (.EXT) }
{ }
{ LFN: }
{ MaxExtLen = 4 if MaxNameLen = 12 else MaxExtLen = MaxNameLen - 1 }
type
PLfnRootVolInfo = ^TLfnRootVolInfo;
TLfnRootVolInfo = record
FileSysName: array[0..High(TFileSysName)] of Char;
RootName : array[0..3] of Char;
end;
function GetVolumeSerialNum(Drive: Char): DWord;
type
PDiskSerialInfo = ^TDiskSerialInfo;
TDiskSerialInfo = record
CallLevel : Word;
SerialNum : DWord;
VolLabel : array[0..10] of Char;
FileSysName: array[0..7] of Char;
end;
var
Regs : TRegisters;
SerialInfo: PDiskSerialInfo absolute DosBuf;
SaveBuf : TDiskSerialInfo;
begin
GetVolumeSerialNum := 0;
if DosVersion < $400
then Exit;
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
FillChar(SerialInfo^, SizeOf(SerialInfo^), 0);
ClearRegs(Regs);
Regs.AX := $440D; { Generic IOCTL - block device request }
Regs.CX := $0866; { Disk Drive - Get Volume Serial number}
Regs.BL := Ord(DosUpCase(Drive)) - (Ord('A') - 1);
Regs.DS := DosBuf.RealSeg; { DS.DX = @SerialInfo }
MsDos(Regs);
if Regs.Flags and fCarry = 0
then GetVolumeSerialNum := SerialInfo^.SerialNum;
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
procedure SetVolumeInfo(VolInfo: PVolumeInfo);
var
Regs : TRegisters;
IsRemoveable : Boolean;
HasChangeLine: Boolean;
Attr : Word;
j : Integer;
P : PNetNet absolute DosBuf;
SaveBuf : TNetNet;
SR : TSearchRec;
{$ifdef LongNames}
LfnInfo : PLfnRootVolInfo absolute DosBuf;
VolInfoBuf : TLfnRootVolInfo absolute SaveBuf;
{$endif LongNames}
begin
ClearRegs(Regs);
Regs.Flags := fCarry;
Attr := 0;
{$ifdef LongNames}
if VFat then { Set Win95 attributes flags and FileSysName fields }
begin
CheckDosBuf(VolInfoBuf, SizeOf(VolInfoBuf));
Regs.AX := $71A0;
Regs.CX := SizeOf(TFileSysName); { CX = SizeOf(LfnInfo.FileSysName) }
Regs.DX := SizeOf(TFileSysName); { DS:DX = @LfnInfo.RootName }
Regs.ES := DosBuf.RealSeg; { ES:DI = @LfnInfo.FileSysName }
Regs.DS := Regs.ES;
PasToNull(VolInfo^.DriveName + '\', @LfnInfo^.RootName);
MsDos(Regs);
if Regs.Flags and fCarry = 0
then begin
Attr := Regs.BX and { Just keep Win95 flags we know about }
(vaCaseSensitive + vaCasePreserve + vaUnicodeChars +
vaDosLongNames + vaCompressed);
VolInfo^.MaxNameLen := Regs.CX; { CX excludes the null }
VolInfo^.MaxExtLen := Regs.CX; { Can start with a '.' }
VolInfo^.MaxPathLen := Regs.DX-1; { DX includes the null! }
if Regs.CX = fsDosName
then VolInfo^.MaxExtLen := fsDosExt;
VolInfo^.FileSysName:= NullToPas(@LfnInfo^.FileSysName);
end
else begin
{ This code has to assume $71A0 will fail (carry = 1) with AX }
{ unchanged or = $7100 on a non-LFN system. There is no other }
{ way of detectng LFN support except by checking for the }
{ presence of Win9x. $71A0 on Win9x will fail if no media is in}
{ the drive. It has to be assumed AX will not equal $71A0 under}
{ these circumstances under any LFN capable system. }
if (Regs.AX <> $7100) and (Regs.AX <> $71A0) { function supptd }
then Attr := vaDosLongNames + vaIsRemoveable + vaNoDiskInDrive;
Attr := VolInfo^.Attributes or Attr;
end;
RestoreDosBuf(VolInfoBuf, SizeOf(VolInfoBuf));
end;
{$endif LongNames}
IsRemoveable := false;
HasChangeLine:= false;
{ Get Drive type, IsRemovable and Has ChangeLine attributes. }
if VolInfo^.NetName <> nil
then Attr := Attr or (vaIsNetWorkDrive + vaIsRemoveable) { Network drive }
else VolInfo^.DriveType := GetStdDriveType(VolInfo^.DriveName[1],
IsRemoveable, HasChangeLine);
if VolInfo^.FileSysName = '' then
begin
VolInfo^.MaxNameLen := fsDosName;
VolInfo^.MaxPathLen := fsDosPath;
VolInfo^.MaxExtLen := fsDosExt;
if VolInfo^.DriveType = dtCdRom
then VolInfo^.FileSysName:= 'CDFS'
else VolInfo^.FileSysName:= 'FAT';
end;
if IsRemoveable then
begin
Attr := Attr or vaIsRemoveable; { Drive media is removable }
if HasChangeLine then
begin
Inc(Attr, vaHasChangeLine);
if (Attr and vaNoDiskInDrive = 0) and { When not under LFN O/S }
(CheckDrvMedia(VolInfo) = mcNotReady)
then Inc(Attr, vaNoDiskInDrive);
end;
end;
VolInfo^.Attributes := Attr;
if (VolInfo^.NetName = nil) then
begin
{ Return the network-style equivalent name of the local drive }
{ because some non-networked local drives may have a network-style }
{ cannonical name. NWCDEX for example returns network-style names }
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
PasToNull(VolInfo^.DriveName, @P^.NetPath);
AddDirSepStr(@P^.NetPath);
ClearRegs(Regs);
Regs.Flags := fCarry;
Regs.ES := DosBuf.RealSeg; { Regs.ES:DI = @P^.LongPath (o/p) }
Regs.DS := Regs.ES;
Regs.SI := SizeOf(TPathNet); { Regs.DS:SI = @P^.NetPath (i/p name)}
Regs.AH := $60; { DOS - Get cannonical true name }
{$ifdef LongNames}
if Attr and vaDosLongNames <> 0 then
begin
Regs.AX := $7160; { LFN - Get cannonical LFN or path }
Regs.CX := $0002; { Return network drive name }
end;
{$endif LongNames}
MsDos(Regs);
if (Regs.Flags and fCarry = 0) and
(StrComp(@P^.LongPath, @P^.NetPath) <> 0) then
begin
DelDirSepStr(@P^.LongPath);
VolInfo^.NetName := PNetName(NewStr(StrPas(@P^.LongPath)));
end;
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
VolInfo^.MediaState := mcNo; { Clear media changed }
{ Get the volume label }
if Volinfo^.Attributes and vaNoDiskInDrive = 0 then
begin
if FindFirst(Volinfo^.DriveName + '\*.*', faVolumeID, SR)
then begin
j := Pos('.', SR.Name);
if j <> 0
then Delete(SR.Name, j, 1);
{$ifdef LongNames}
if Volinfo^.Attributes and vaCaseSensitive = 0 then
{$endif LongNames}
begin
{$V-} DosLowerCase(SR.Name); {$V+}
SR.Name[1] := DosUpCase(SR.Name[1]);
end;
Volinfo^.VolumeLabel := SR.Name;
FindClose(SR);
end
else begin
Volinfo^.VolumeLabel := '';
DosError := deNoError;
end;
{ Get volume serial number }
Volinfo^.SerialNum := GetVolumeSerialNum(Volinfo^.DriveName[1]);
end;
end;
{ Return a redirected device entry into the specified buffers }
type
PNetDevName = ^TNetDevName;
TNetDevName = record
Local: array[0..15] of char;
Net : array[0..127] of char;
end;
function GetRedirEntry(Entry: Word): Byte;
{$ifdef DPMI}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.DS := DosBuf.RealSeg;
Regs.ES := Regs.DS;
Regs.DI := 16; { ES:DI = @Net name buffer }
Regs.AX := $5F02; { DOS Get Redirection list entry}
Regs.BX := Entry;
Regs.CX := 0;
MsDos(Regs);
GetRedirEntry := Byte(-1);
if (Regs.Flags and fCarry = 0) and (Regs.BH <> 1)
then GetRedirEntry := Regs.BL;
end;
{$else DPMI} assembler;
asm
push ds
lds si,[DosBuf.RealBuf] { DS:SI = @Local name buffer }
push ds
mov di,TNetDevName.Net { ES:DI = @Net name buffer }
pop es
mov ax,$5F02 { DOS Get Redirection list entry}
mov bx,[Entry]
mov cx,0
int intDos
pop ds
mov al,-1
jc @@Exit
cmp bh,1
je @@Exit
mov ax,bx
@@Exit:
end;
{$endif DPMI}
{ Returns the first networked drive. Adds all networked and CD-ROM }
{ drives to the valid drive list the first time it's called. }
function Get1stNetDrive: PVolumeInfo;
var
V : PVolumeInfo;
Result : PVolumeInfo;
j : Word;
DevType : Byte;
Padder : Byte;
NetDevName: PNetDevName absolute DosBuf;
SaveBuf : TNetDevName;
begin
if VolumeList = nil then
begin
Get1stNetDrive := nil;
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
GetDrives; { Make sure DriveList is defined}
for j := 0 to 99 do
begin
DevType := GetRedirEntry(j);
if DevType = dtError
then Break;
if (DevType = 4) and { Network device must be a drive}
(NetDevName^.Local[1] = ':') and { mapped to a local drive letter}
(Pos(NetDevName^.Local[0], DriveList) <> 0) then
begin
V := CreateVolume(NetDevName^.Local[0]);
V^.DriveType := dtNet1;
V^.DriveName := StrLPas(NetDevName^.Local, 2); { Local drive name}
if Length(V^.DriveName) = 1
then V^.DriveName := V^.DriveName + ':'
else V^.DriveName[0] := #2; { Network drive name }
V^.NetName := PNetName(NewStr(NullToPas(@NetDevName^.Net)));
SetVolumeInfo(V); { Set rest of TVolumeInfo fields }
end;
end;
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
Result := VolumeList;
while Result <> nil do
if Result^.Attributes and vaIsNetWorkDrive <> 0
then Break
else Result := Result^.Next;
Get1stNetDrive := Result;
end;
procedure InsertVolume(V: PVolumeInfo); { Add V to List of defined volumes }
var
P: ^PVolumeInfo;
begin
P := @VolumeList;
while (P^ <> nil) and (V^.DriveName[1] > P^^.DriveName[1]) do
P := @P^^.Next;
V^.Next := P^;
P^ := V;
end;
function StdCreateVolume(Drive: Char): PVolumeInfo; far;
var
V: PVolumeInfo;
begin
New(V); { Represents a valid drive }
FillChar(V^, SizeOf(V^), 0);
V^.DriveName := Drive + ':';
InsertVolume(V);
StdCreateVolume := V;
end;
function GetVolumeInfo(Drive: Char): PVolumeInfo;
var
V: PVolumeInfo;
begin
Drive := DosUpCase(Drive);
V := VolumeList;
if V = nil
then V := Get1stNetDrive; { Define all network drives }
while V <> nil do { Look for predefined volume}
begin
if V^.DriveName[1] = Drive then
begin
GetVolumeInfo := V;
{ Check the media state flag. If it is not mcNo }
{ then refresh the volume information. }
if V^.MediaState <> mcNo
then SetVolumeInfo(V); { If there was no media in }
Exit; { drive last time then try }
end; { to set volume info again. }
V := V^.Next;
end;
if DriveValid(Drive)
then begin { Not predefined, so create }
V := CreateVolume(Drive); { new volume if drive letter}
SetVolumeInfo(V);
end
else DosError := deInvalidDrive;
GetVolumeInfo := V; { nil if Drive is invalid }
end;
(*
function CheckCdMedia(Drive: Char): TMediaLevel;
type
PCdMediaChk = ^TCdMediaChk;
TCdMediaChk = record
DataLen : Byte; { 00 }
SubUnit : Byte; { 01 }
Command : Byte; { 02 }
Status : Word; { 03 }
Reserved : array[0..3] of Byte; { 05 }
NextHeader: PCdMediaChk; { 09 }
Media : Byte; { 0D }
MediaState: Byte; { 0E }
PrevID : PChar; { 0F }
end;
var
Regs : TRegisters;
SaveBuf: TCdMediaChk;
Data : PCdMediaChk absolute DosBuf;
begin
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
Data^.DataLen := SizeOf(SaveBuf);
Data^.Command := $01;
ClearRegs(Regs);
Regs.AX := $1510; { MSCDEX - Send Device Driver Request }
Regs.CX := Ord(Drive) - (Ord('A')-1); { Drive Letter }
Intr($2F, Regs);
if (Regs.Flags and fCarry <> 0) or (Data^.Status and $80 <> 0)
then CheckCdMedia := mcUnknown
else if Data^.MediaState = 9
then CheckCdMedia := mcNotReady
else case Data^.MediaState of
$00:
CheckCdMedia := mcUnknown;
$01:
CheckCdMedia := mcNo;
else
CheckCdMedia := mcYes;
end;
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
*)
{ Return the Media Changed status for a Drive }
function CheckDrvMedia(V: PVolumeInfo): TMediaLevel; assembler;
var
Regs: TRegisters;
asm
les di,[V]
test es:[di].TVolumeInfo.Attributes,vaNoDiskInDrive
mov al,mcNotready
jnz @@Exit
test es:[di].TVolumeInfo.Attributes,vaIsRemoveable
mov al,mcNo
je @@Exit
test es:[di].TVolumeInfo.Attributes,vaHasChangeLine
jnz @@ChkLine
push es:[di].TVolumeInfo.DriveName.Word[1]
call GetVolumeSerialNum
les di,[V]
mov cx,ax
mov al,mcYes
cmp cx,es:[di].TVolumeInfo.SerialNum.Word[0]
jne @@Exit
cmp dx,es:[di].TVolumeInfo.SerialNum.Word[2]
jne @@Exit
{ The serial numbers are the same, but they might be both zero (unsupported)}
or cx,dx { If they are non-zero, they are valid and }
mov al,mcNo { the media has definately not changed. }
jne @@Exit
mov al,mcUnknown { There is no serial number, so we don't }
jmp @@Exit { know if the media has changed or not. }
{$ifdef MsDos}
@@ChkLine: mov ah,$16
mov dl,es:[di].TVolumeInfo.DriveName.Byte[1]
sub dl,'A'
int $13
{$else MsDos}
@@ChkLine: push ss
mov dl,es:[di].TVolumeInfo.DriveName.Byte[1]
sub dl,'A'
lea di,Regs
pop es { ES:DI = @RealRegs }
cld
mov cx,type TRegisters / 2
xor ax,ax
rep stosw
mov [Regs.&AH],$16 { RealRegs.AH = $16 }
lea di,Regs { ES:DI = @Regs }
mov [Regs.&DX],dx { DL = Drive number (A = 0) }
mov ax,$300 { Call real-mode interrupt }
mov bx,$0013 { BL = interrupt number (Disk) }
int $31 { CX already equals 0 }
mov ax,[Regs.&AX] { CF = 0, AH = 0 if not changed }
shr [Regs.Flags],1 { Real Mode carry -> CF }
{$endif MsDos}
mov al,mcUnknown
jc @@Exit { Function failed }
cmp ah,-1 { Detection system failed }
je @@Exit
cmp ah,$80
mov al,mcNotReady
jz @@Exit { Drive not ready }
mov al,mcNo { Assume drive media not changed}
cmp ah,0
je @@Exit
mov al,mcYes { Drive media has changed }
@@Exit: mov es:[di].TVolumeInfo.MediaState,al
end;
function GetVolumeLabel(Drive: Char): TVolLabel;
var
V: PVolumeInfo;
begin
DosUpCase(Drive);
V := GetVolumeInfo(Drive);
if V = nil
then GetVolumeLabel := '';
GetVolumeLabel := V^.VolumeLabel;
end;
function GetVolumeLabelStr(VolLabel: PChar; Drive: Char): PChar;
var
V: PVolumeInfo;
begin
V := GetVolumeInfo(Drive);
if V = nil
then VolLabel^ := asNull
else PasToNull(V^.VolumeLabel, VolLabel);
GetVolumeLabelStr := VolLabel;
end;
type
PDosFcb = ^TDosFcb;
TDosFcb = record { DOS extended File Control Block }
Flag : Byte; { must be $ff! }
Reserv1: array[1..5] of Byte;
Attr : Byte;
Drive : Byte;
Name : array[1..8] of Char;
Ext : array[1..3] of Char;
FPos : Word;
RecSize: Word;
FSize : LongInt;
FDate : Word;
FTime : Word;
Reserv2: array[1..8] of Byte;
CurRec : Byte;
RelRec : LongInt;
end;
procedure SetFcbName(var Fcb: TDosFcb; Name: TNameExt);
var
P,X: Byte;
begin
P := Pos('.', Name);
if P = 0 then
begin
P := Length(Name)+1;
Name := Name + '.';
end;
FillChar(Fcb.Name, 11, ' ');
Move(Name[1], Fcb.Name, P-1);
Move(Name[P+1], Fcb.Ext, Length(Name)-P);
end;
{ Call a Dos Fcb function. The DosBuf memory buffer must point to a }
{ predefined DOS extended File Control Block. This function can be }
{ used for any DOS function that takes a pointer to a memory block }
{ in DS:DX and an error code is returned in AL. }
function CallDosFcb(Fn: Word): Byte; assembler;
{$ifdef DPMI}
var
Regs: TRegisters;
asm
mov dx,word ptr [DosBuf.RealSeg] { DX:0000 = DosBuf (real) }
push ss
lea di,Regs
pop es { ES:DI = @RealRegs }
cld
mov cx,type TRegisters / 2
xor ax,ax
rep stosw
mov ax,[Fn]
lea di,Regs { ES:DI = @Regs }
mov [Regs.&AX],ax { RealRegs.AX = Fn }
mov ax,dpmiCallRealInt { Call real-mode interrupt }
mov [Regs.&DS],dx { Regs.DS:DX = DosBuf.RealBuf }
mov bx,intDos { BL = interrupt number ($21) }
int $31 { CX already equals 0 }
mov ax,[Regs.&AX]
{$else DPMI}
asm
push ds
push bp
lds dx,[DosBuf.RealBuf] { DS:DX = DosBuf }
mov ax,[Fn]
int intDos
pop bp
pop ds
{$endif DPMI}
end;
function SetVolumeLabel(Drive: Char; VolLabel: TVolLabel): Boolean;
var
Fcb : PDosFcb absolute DosBuf;
SaveBuf: TDosFcb;
V : PVolumeInfo;
label
Done;
begin
DosUpCase(Drive);
{$V-} DosUpperCase(VolLabel); {$V+}
SetVolumeLabel := false;
DosError := deInvalidDrive;
V := GetVolumeInfo(Drive);
if V = nil
then Exit;
DosError := deAccessDenied;
if V^.Attributes and vaIsNetworkDrive <> 0
then Exit;
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
Fcb^.Flag := $FF;
Fcb^.Attr := faVolumeID;
if V^.VolumeLabel <> '' then
begin
SetFcbName(Fcb^, V^.VolumeLabel);
Fcb^.Drive := Byte(Drive) - (Ord('A') - 1);
if CallDosFcb($1300) <> 0 { Delete File }
then goto Done;
V^.VolumeLabel := ''
end;
if VolLabel <> '' then
begin
Fcb^.Drive := Byte(Drive) - (Ord('A') - 1);
SetFcbName(Fcb^, VolLabel);
if (CallDosFcb($1600) <> 0) or { Create File }
(CallDosFcb($1000) <> 0) { Close File }
then Exit;
V^.VolumeLabel := VolLabel;
end;
DosError := deNoError;
SetVolumeLabel := true;
Done:
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
function SetVolumeLabelStr(VolLabel: PChar; Drive: Char): Boolean;
begin
SetVolumeLabelStr := SetVolumeLabel(Drive, StrLPas(VolLabel, fsVolLabel));
end;
{ Looks for a network drive net at the start of Path. Replaces the network }
{ drive name with its local (X:\) drive name if the network drive name is }
{ found in the valid list of drives, else sets DosError to "Path not Found" }
{ Path contains a valid drive, but it may not have a TVolumeInfo assigned it}
function ConvertNetPath(Path: PChar): PVolumeInfo;
var
V : PVolumeInfo;
P : Word;
j : Byte;
Pad : Byte;
Name: array[0..fsNetName] of Char;
const
CheckCD: Boolean = false;
begin
StrLCopy(@Name, Path, fsNetName);
StrUpper(@Name); { Net paths must be case insensitive }
P := 0;
if not CheckCD then { Some MSCDEX drivers return network }
begin { style cannonocal paths so }
for j := Length(GetDrives) downto 1 do { make sure all MSCDEX }
if IsCdRom(DriveList[j]) { drives are defined. }
then GetVolumeInfo(DriveList[j]);
CheckCD := true;
end;
Get1stNetDrive; { Ensure all net drives are defined }
V := VolumeList;
while V <> nil do
begin
if (V^.NetName <> nil) and
(StrLComp(@V^.NetName^[1], @Name, Length(V^.NetName^)) = 0) then
begin { Net drv name found at }
P := Length(V^.NetName^); { the start of Path. }
if (P = StrLen(@Name)) or (Name[P] = '\') then
begin
StrMove(@Path[2], @Path[P], StrLen(@Name[P])+1);{ Remove net name}
Move(V^.DriveName[1], Path^, 2); { Set local name }
DosError := deNoError;
ConvertNetPath := V;
Exit;
end;
end;
V := V^.Next;
end;
DosError := deInvalidDrive; { Path (network drive) not found }
ConvertNetPath := nil;
end;
{ Determine the volume from a path. The network drive name of a network }
{ path is converted to its local drive equivalent. 'X:' is appended if }
{ Path is relative ("\[DIR\]NAME.EXT", "..\NAME.EXT" or "[DIR\]NAME.EXT" }
{ Returns nil and set DosError to "Invalid Drive" if drive not found. }
function GetVolumeFromPath(Path: PChar): PVolumeInfo;
var
C: Char;
P: Char;
L: Integer;
T: TPathName;
const
NetPath: array[0..1] of Char = '\\'; { Network paths must start with '\\' }
begin
L := StrLen(Path);
if L = 0 then
begin
GetVolumeFromPath := nil;
Exit;
end;
{SlashConvert(L, Path);}
C := Path[0];
if PWord(Path)^ = Word(NetPath)
then GetVolumeFromPath := ConvertNetPath(Path)
else begin
if (C = '\') or (Path[1] <> ':')
then begin
C := GetCurDrive;
Move(Path^, Path[2], StrLen(Path)+1);
Path[0] := C;
Path[1] := ':';
end
else begin
Path[0] := UpCase(Path[0]); { Make sure the drive letter }
C := Path[0]; { is an uppercase letter }
end;
GetVolumeFromPath := GetVolumeInfo(C);
{ Some versions of DOS (eg 7.x) will treat "X:" as an invalid path, }
{ so we have to add any implied current directory. }
if Path[2] <> '\' then
begin
StrCopy(@T, @Path[2]); { Save everything after "X:" }
GetCurDir(Path, Ord(C) - (Ord('A')-1)); { Get Drive + directory}
StrLCat(Path, @T, fsNetPath); { Add the rest of passed path}
end; { to "X:\CURDIR\" }
end;
end;
function GetVolumeOf(const Path: TNetPath): PVolumeInfo;
var
P: array[0..fsNetPath] of Char;
begin
StrPCopy(@P, Path);
GetVolumeOf := GetVolumeFromPath(@P);
end;
function GetVolumeOfStr(Path: PChar): PVolumeInfo;
var
P: array[0..fsNetPath] of Char;
begin
StrLCopy(@P, Path, fsNetPath);
GetVolumeOfStr := GetVolumeFromPath(@P);
end;
{------------------- Standard Disk/Drive related functions -----------------}
function GetVerify: Boolean;
{$ifdef TurboDos} assembler;
asm
mov ah,$54 { DOS - Get Verify Flag }
int intDos
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AH := $54; { DOS - Get Verify Flag }
GetVerify := Lo(MsDos(Regs)) <> 0;
end;
{$endif TurboDos}
procedure SetVerify(Verify: Boolean);
{$ifdef TurboDos} assembler;
asm
mov ah,$2E { DOS - Set Verify Flag }
mov al,[Verify]
int intDos
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AL := Ord(Verify);
Regs.AH := $2E; { DOS - Set Verify Flag }
Verify := Lo(MsDos(Regs)) <> 0;
end;
{$endif TurboDos}
{ Replacement for System GetDir }
procedure LfnGetDir(D: Byte; var S: String; MaxLen: Word); far;
var
Regs : TRegisters;
SaveBuf: TPathName;
P : PChar absolute DosBuf;
Drive : Char;
Padder : Char;
V : PVolumeInfo;
begin
S := '';
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
if D = 0
then Drive := GetCurDrive
else Drive := Chr(D + (Ord('A') - 1));
PasToNull(Drive + ':\', P);
V := GetVolumeInfo(Drive);
if V = nil
then DosError := deInvalidDrive
else begin
ClearRegs(Regs);
Regs.DL := D;
Regs.DS := DosBuf.RealSeg;
Regs.SI := SizeOf(Char) * 3; { @Next char after 'X:\' drive }
Regs.AH := $47; { DOS - Get current directory }
{$ifdef LongNames}
if V^.Attributes and vaDosLongNames <> 0
then Regs.AX := $7147; { LFN - Get current directory }
{$endif LongNames}
Regs.Flags := fCarry;
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0 then
begin
DosError := deNoError;
ConvertPathCase(P, V); { Do any case conversions req'd}
AddDirSepStr(P);
{$ifdef Windows}
OemToAnsi(P, P);
{$endif Windows}
end;
end;
S := StrLPas(P, MaxLen);
if DosError = deNoError
then DosError := StrError;
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
procedure GetDir(Drive: Byte; var S: String);
begin
LfnGetDir(Drive, S, High(S));
end;
function GetCurDir(S: PChar; Drive: Byte): PChar;
var
Regs : TRegisters;
P : PChar absolute DosBuf;
SaveBuf: TPathName;
D : Char;
Padder : Char;
V : PVolumeInfo;
begin
GetCurDir := S;
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
if Drive = 0
then D := GetCurDrive
else D := Chr(Drive + (Ord('A') - 1));
PasToNull(D + ':\', P);
V := GetVolumeInfo(D);
if V = nil
then DosError := deInvalidDrive
else begin
Inc(P, 3); { Add cur dir to end of "X:\" }
ClearRegs(Regs);
Regs.DL := Drive;
Regs.DS := DosBuf.RealSeg;
Regs.SI := 3;
Regs.AH := $47; { DOS - Get current directory }
{$ifdef LongNames}
if V^.Attributes and vaDosLongNames <> 0
then Regs.AX := $7147; { LFN - Get current directory }
{$endif LongNames}
Regs.Flags := fCarry;
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0 then
begin
DosError := 0;
PtrRec(P).Ofs := 0; { Add the 'X:\' prefix to path }
AddDirSepStr(P); { Make sure it ends in a '\' }
ConvertPathCase(P, V);
end;
end;
StrCopy(S, P); { Copy path into result }
PtrRec(P).Ofs := 0;
{$ifdef Windows}
OemToAnsi(S, S);
{$endif Windows}
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
procedure DoDirOp(var S: String; DosOp: Byte);
var
Regs : TRegisters;
P : PChar absolute DosBuf;
SaveBuf: TPathNet;
Path : TPathNet;
V : PVolumeInfo;
label
Done;
begin
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
ClearRegs(Regs);
StrPLCopy(@Path, S, fsNetPath);
if StrError <> deNoError then
begin
DosError := StrError;
goto Done
end;
{$ifdef Windows}
AnsiToOem(@Path, @Path);
{$endif Windows}
V := FileExpand(@Path, @Path, fcCasePreserve);
if DosError <> deNoError
then goto Done;
StrCopy(P, @Path);
Regs.DS := DosBuf.RealSeg;
Regs.AH := DosOp; { DOS - Create/Remove Directory }
Regs.Flags := fCarry;
{$ifdef LongNames}
if V^.Attributes and vaDosLongNames <> 0
then Regs.AX := $7100 + DosOp; { LFN - Create/Remove Directory }
{$endif LongNames}
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0
else GetExtError;
Done:
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
{ Replacement for System MkDir }
procedure LfnMkDir(var S: String); far;
begin
DoDirOp(S, $39); { DOS - Create Directory }
end;
procedure LfnRmDir(var S: String); far;
begin
DoDirOp(S, $3A); { DOS - Remove Directory }
end;
procedure ChDir(Dir: String); { Enhanced verion of System.ChDir that: }
var { (1) Allows Dir to contain LFN dir names}
Regs : TRegisters; { (2) Allows Dir to contain a net path }
V : PVolumeInfo; { (3) Allows paths containing subdirs to }
L : Word;
{$ifdef DPMI} { end with or without a trailing '\' }
P : PChar absolute DosBuf;
SaveBuf: array[0..fsDirectory] of Char;
{$else DPMI}
P : TPathName absolute Dir;
{$endif DPMI}
label
Done; { Change Dir to a PChar^ and expand to Dir}
begin { To allow network names and to validate }
V := FileExpand(@Dir, StrPCopy(PChar(@Dir), Dir), fcCasePreserve);
L := StrLen(PChar(@Dir));
if (L > 3) and (PChar(@Dir)[L-1] = '\') { To allow paths to end }
then PChar(@Dir)[L-1] := #0; { with a '\' or not }
if DosError = deNoError
then DosError := StrError;
if DosError = deNoError then
begin
{$ifdef DPMI}
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
StrLCopy(P, PChar(@Dir), fsDirectory);
{$endif DPMI}
ClearRegs(Regs);
if P[1] = ':' then
begin
Regs.AH := $0E; { DOS - Set default drive }
Regs.DL := Ord(DosUpCase(P[0])) - Ord('A'); { 0 = A, 1 = B etc }
MsDos(Regs);
Regs.AH := $19; { DOS - Get default drive }
if Lo(MsDos(Regs)) <> Regs.DL then
begin
DosError := deInvalidDrive;
goto Done;
end;
ClearRegs(Regs);
Move(P[2], P[0], L-1);
end;
{$ifdef DPMI}
Regs.DS := DosBuf.RealSeg; { DS:DX = @P (directory) }
{$else DPMI}
Regs.DS := Seg(P);
Regs.DX := Ofs(P);
{$endif DPMI}
Regs.AH := $3B; { DOS - Set Current Dir }
Regs.Flags := fCarry;
{$ifdef LongNames}
if V^.Attributes and vaDosLongNames <> 0
then Regs.AX := $713B; { LFN - Set Current Dir }
{$endif LongNames}
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0
else GetExtError;
Done:
{$ifdef DPMI}
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
{$endif DPMI}
end;
end;
procedure ChangeDir(Dir: PChar);
begin
ChDir(StrLPas(Dir, fsDirectory));
end;
function GetCurDrive: Char; { Return the current drive }
{$ifdef TurboDos} assembler;
asm
mov ah,$19 { Dos - Get Current drive }
int intDos
mov cx,ax
add al,'A'
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AH := $19; { Dos - Get Current drive }
GetCurDrive := Chr(MsDos(Regs) + Ord('A'));
end;
{$endif TurboDos}
type
PExtDriveParamInfo = ^TExtDriveParamInfo;
TExtDriveParamInfo = packed record { Extended Drive Parameter Block }
DriveNum : Byte; { Drive number (0 = A, 1 = B etc }
UnitNum : Byte; { Unit number within device driver }
BytesPerSec : Word; { Number of bytes in each sector }
MaxSecInClust: Byte; { Highest sector number in a cluster }
Clust2SecShft: Byte; { Shift count to convert clusters -> sectors }
RsvdSects : Word; { Number of reserved sectors @ start of drive}
NumFATs : Byte; { Number of File Allocation Tables }
NumRootEnts : Word; { Number of root directory entries }
FirstUserSec : Word; { First sector containing user data }
MaxClustNumS : Word; { Highest cluster number }
SectPerFAT : Byte; { Number of sectors per File Allocation Table}
FirstDirSec : Word; { First directory sector }
DevDriveHdr : Pointer; { Pointer to device driver header }
MediaID : Byte;
ForceMediaChk: Byte; { $FF -> force a media check }
NextDPB : DosPtr; { Pointer to next Drive Parameter Block }
FreeSpcClustS: Word; { Cluster to start looking for free spce from}
ClustFree : DWord; { Number of free clusters - $FFFF = unknown }
FatMirrorFlgs: Word;
FileSysInfSec: Word;
BootBackupSec: Word;
FirstSecNum : DWord; { Sector number of the first cluster }
MaxClustNum : DWord; { Highest cluster number of volume }
FATSecCount : DWord; { Number of sectors occupied by FAT }
RootDirClust : DWord; { Cluster number of root directory }
FreeSpceClust: DWord; { Cluster to start looking for free spce from}
end;
PFat32Info = ^TFat32Info;
TFat32Info = record
StrucSize : Word; { (ret) size of returned structure }
StrucVer : Word; { (call) structure version (0000h) (ret) actual structure version (0000h) }
SectsPerCluster: DWord; { Number of sectors per cluster (with adjustment for compression) }
BytesPerSector : DWord; { Number of Bytes per Sector }
ClustersFree : DWord; { Number of Free Clusters }
ClustersTotal : DWord; { Total number of Clusters on disk }
SectsFreeNoCmp : DWord; { Number of physical sectors available on the drive, without adjustment for compression }
SectsTotalNoCmp: DWord; { total number of physical sectors on the drive, without adjustment for compression }
ClustFreeNoCmp : DWord; { Number of available allocation units, without adjustment for compression }
ClustTotalNoCmp: DWord; { Total allocation units, without adjustment for compression }
Reserved : array[0..7] of Byte;
end;
PExtInfoBuf = ^TExtInfoBuf;
TExtInfoBuf = record
RootStr: array[0..3] of Char;
ExtInfo: TFat32Info;
end;
function GetDiskInfo(Drive: Byte;
var DiskInfo: TDiskInfo): Boolean;
{$ifdef LongNames}
var
Regs: TRegisters;
SaveBuf: TExtInfoBuf;
V : PVolumeInfo;
Info : PExtInfoBuf absolute DosBuf;
const
RootTail: array[1..3] of Char = ':\'#0;
begin
GetDiskInfo := false;
ClearRegs(Regs);
if Drive = 0
then V := GetVolumeInfo(GetCurDrive)
else V := GetVolumeInfo(Chr(Drive + (Ord('A') -1)));
if V^.Attributes and vaDosLongNames <> 0
then begin
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
Info^.RootStr[0] := Chr(Drive + (Ord('A') -1));
Move(RootTail, Info^.RootStr[1], SizeOf(RootTail));
Info^.ExtInfo.StrucVer := 0;
Regs.DS := DosBuf.RealSeg;
Regs.ES := Regs.DS;
Regs.DI := SizeOf(Char) * 4;
Regs.CX := SizeOf(TFat32Info);
Regs.AX := $7303; { FAT32 - Get Extended free space }
Regs.Flags:= fCarry;
MsDos(Regs);
if Regs.Flags and fCarry = 0 then
begin
DiskInfo.SectsPerCluster:= Info^.ExtInfo.SectsPerCluster;
DiskInfo.BytesPerSector := Info^.ExtInfo.BytesPerSector;
DiskInfo.ClustersFree := Info^.ExtInfo.ClustFreeNoCmp;
DiskInfo.ClustersTotal := Info^.ExtInfo.ClustTotalNoCmp;
GetDiskInfo := true;
end;
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end
else
{$else LongNames}
{$ifdef TurboDos}
assembler;
{$else TurboDos}
var
Regs: TRegisters;
{$endif TurboDos}
{$endif LongNames}
{$ifdef TurboDos}
asm
mov dl,[Drive]
mov ah,$36
int intDos
les di,[DiskInfo]
cmp ax,-1
mov [es:di+TDiskInfo.SectsPerCluster],ax
mov ax,false
je @@Exit
mov [es:di+TDiskInfo.BytesPerSector],cx
mov [es:di+TDiskInfo.ClustersFree].Word[0],bx
mov [es:di+TDiskInfo.ClustersFree].Word[2],ax
mov [es:di+TDiskInfo.ClustersTotal].Word[0],dx
mov [es:di+TDiskInfo.ClustersTotal].Word[2],ax
mov al,true
{$ifdef LongNames}
mov [@Result],al
{$endif LongNames}
@@Exit:
end;
{$else TurboDos}
begin
Regs.DL := Drive;
Regs.AH := $36; { DOS - Get Free Disk Space }
MsDos(Regs);
if Regs.AX = $FFFF
then Exit;
DiskInfo.SectsPerCluster:= Regs.AX;
DiskInfo.ClustersFree := Regs.BX;
DiskInfo.BytesPerSector := Regs.CX;
DiskInfo.ClustersTotal := Regs.DX;
GetDiskInfo := true;
end;
{$endif TurboDos}
{$ifdef LongNames}
end;
{$endif LongNames}
function DiskFree(Drive: Byte): DWord;
var
DiskInfo: TDiskInfo;
begin
DiskFree := -1;
if GetDiskInfo(Drive, DiskInfo) then
asm
db $66; xor ax,ax
mov ax,[DiskInfo.SectsPerCluster]
mov dx,[DiskInfo.BytesPerSector]
db $66; mul dx
db $66; mov dx,[DiskInfo.ClustersFree].Word[0]
db $66; mul dx
jnc @@1
db $66, $B8; dd -1 { mov eax,$FFFFFFFF }
@@1:
db $66; mov word ptr [@Result],ax
end;
end;
function DiskSize(Drive: Byte): DWord;
var
DiskInfo: TDiskInfo;
begin
DiskSize := -1;
if GetDiskInfo(Drive, DiskInfo) then
asm
db $66; xor ax,ax
mov ax,[DiskInfo.SectsPerCluster]
mov dx,[DiskInfo.BytesPerSector]
db $66; mul dx
db $66; mov dx,[DiskInfo.ClustersTotal].Word[0]
db $66; mul dx
jnc @@1
db $66, $B8; dd -1 { mov eax,$FFFFFFFF }
@@1:
db $66; mov word ptr [@Result],ax
end;
end;
{--------------------- File properties related functions -------------------}
procedure GetFTime(var F; var Time: Longint);
var
SR: TSearchRec;
begin
case TFileRec(F).Mode of
fmClosed:
InOutRes := 103; { Error = File not Open }
fmInput..fmInOut:
Time := FileGetTime(TFileRec(F).Handle);
else
InOutRes := 102; { Error = File not Assigned }
end;
end;
procedure SetFTime(var F; Time: Longint);
begin
case TFileRec(F).Mode of
fmClosed:
InOutRes := 103; { Error = File not Open }
fmInput..fmInOut:
FileSetTime(TFileRec(F).Handle, Time);
else
InOutRes := 102; { Error = File not Assigned }
end;
end;
procedure CheckForLfnDrv; assembler; { Inputs : EAX = PChar to file name }
asm { Outputs: ES:DI = @TVolumeInfo }
db $66; push ax { ZF = 0 if LFN capable }
call GetVolumeOfStr
mov es,dx
mov di,ax
or dx,ax
jz @@Exit { Invalid drive, ZF = 1, ES:DI = nil}
test [es:di].TVolumeInfo.Attributes,vaDosLongNames
@@Exit:
end;
type
PWin95FileInfo = ^TWin95FileInfo;
TWin95FileInfo = packed record
Attributes: DWord;
CTimeLo : DWord;
CTimeHi : DWord;
ATimeLo : DWord;
ATimeHi : DWord;
WTimeLo : DWord;
WTimeHi : DWord;
VolSerNum : DWord;
FSizeHi : DWord;
FSizeLo : DWord;
LinkCnt : DWord;
FileID_Hi : DWord;
FileID_Lo : DWord;
end;
function FileGetSetAttr(PathName: PChar; Attr: Word; Op: TAttrOp): Word;
{$ifdef TurboLong} assembler;
asm
push ds { Save global DS }
{$ifdef LongNames}
db $66; mov ax,PathName.Word[0]
call CheckForLfnDrv
mov ax,$7143 { LFN - Get/Set Ext file attribs}
mov bl,[Op] { BL = Get file attributes }
jnz @@LFN { - or Set File Attributes }
{$endif LongNames}
mov ah,$43 { DOS - Get File Attributes }
mov al,[Op] { - or Set File Attributes }
@@LFN: lds dx,[PathName] { DS:DX = address of file name }
mov cx,[Attr] { For Set file attribute }
stc
int intDos
pop ds
mov bx,ax
mov bx,deNoError
jnc @@Exit
call GetExtError { Extended error code => AX & BX}
xor cx,cx
@@Exit: mov [DosError],bx
mov ax,cx { Return Attributes for GetFAttr}
nop
end;
{$else TurboLong}
var
Regs : TRegisters;
{$ifdef DPMI}
Name : PChar absolute DosBuf;
SaveBuf: TPathName;
{$else DPMI}
Name : PChar absolute PathName;
{$endif DPMI}
begin
ClearRegs(Regs);
Regs.AX := $4300 + Ord(Op); { DOS - Get file attributes }
{$ifdef DPMI}
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
Regs.DS := DosBuf.RealSeg; { or - Get file attributes }
StrLCopy(Name, PathName, High(SaveBuf)+1);{ BL = 0 = Get file attributes }
DosError := StrError;
if DosError = deNoError then
{$else DPMI}
Regs.DS := PtrRec(PathName).Seg;
Regs.DX := PtrRec(PathName).Ofs;
{$endif DPMI}
begin
{$ifdef LongNames}
if GetVolumeFromPath(Name)^.Attributes and vaDosLongNames <> 0 then
begin
Regs.AX := $7143; { LFN - Get/Set Ext file attribs}
Regs.BX := Ord(Op);
end;
{$endif LongNames}
Regs.Flags := fCarry;
Regs.CX := Attr; { Set CX for Set File Attributes}
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0
else begin
GetExtError;
Regs.CX := 0;
end;
end;
{$ifdef DPMI}
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
{$endif DPMI}
FileGetSetAttr := Regs.CX; { For Get File attributes }
end;
{$endif TurboLong}
procedure GetFAttr(var F; var Attr: Word);
var
TF: TFileRec absolute F;
begin
case TFileRec(F).Mode of { Make sure F is a File or Text }
fmClosed..fmInOut: { variable. Let the O/S decide }
{$ifdef LongNames} { if an error occurs on an open }
Attr := FileGetSetAttr(TF.Name, 0, faGet); { file, rather than }
{$else LongNames} { just assuming it will}
Attr := FileGetSetAttr(@TF.Name, 0, faGet);
{$endif LongNames}
else
InOutRes := 102; { Error = File not Assigned }
end;
end;
procedure SetFAttr(var F; Attr: Word);
var
TF: TFileRec absolute F;
begin
case TFileRec(F).Mode of { Make sure F is a File or Text }
fmClosed..fmInOut: { variable. Let the O/S decide }
{$ifdef LongNames} { if an error occurs on an open }
FileGetSetAttr(TF.Name, Attr, faSet); { file, rather than }
{$else LongNames} { just assuming it will}
FileGetSetAttr(@TF.Name, Attr, faSet);
{$endif LongNames}
else
InOutRes := 102; { Error = File not Assigned }
end;
end;
procedure GetFSize(var F; var Size: Longint);
var
SR : TSearchRec;
{$ifdef Windows}
Name: TPathName;
{$else Windows}
Name: TPathStr;
{$endif Window}
begin
Size := -1;
case TFileRec(F).Mode of { Make sure F is a File or Text }
fmClosed: { variable }
begin
{$ifdef Windows}
if FindFirst(TFileRec(F).Name, faAnyFile, SR) then
{$else Windows}
{$ifdef LongNames}
Name := NullToPas(TFileRec(F).Name);
{$else LongNames}
Name := NullToPas(@TFileRec(F).Name);
{$endif LongNames}
if FindFirst(Name, faAnyFile, SR) then
{$endif Windows}
begin
Size := SR.Size;
FindClose(SR);
end;
end;
fmInput..fmInOut:
Size := FileSize(TFileRec(F).Handle);
else
InOutRes := 102; { Error = File not Assigned }
end;
end;
procedure StdOutName; assembler;
asm db 6,'StdOut'; db 0 end;
procedure StdInName; assembler;
asm db 5,'StdIn'; db 0 end;
procedure StdErrName; assembler;
asm db 6,'StdErr'; db 0 end;
procedure StdPrnName; assembler;
asm db 6,'StdPrn'; db 0 end;
const
StdNames: array[1..4] of NearPtr = (
Ofs(StdOutName),Ofs(StdInName),Ofs(StdErrName),Ofs(StdPrnName));
function GetFName(var F): TPathStr;
var
FR: TFileRec absolute F;
begin
case FR.Mode of
fmClosed..fmInOut:
begin
if FR.Handle <= 4
then GetFName := PPathStr(Ptr(Seg(StdOutName), StdNames[FR.Handle]))^
else begin
{$ifdef LongNames}
GetFName := StrLPas(FR.Name, High(TPathStr));
{$else LongNames}
GetFName := StrPas(@FR.Name);
{$endif LongNames}
InOutRes := StrError;
end;
end
else
begin
InOutRes := 102; { File not assigned error }
GetFName := '';
end;
end;
end;
function GetFileName(var F): PChar;
var
FR: TFileRec absolute F;
begin
case FR.Mode of
fmClosed..fmInOut:
begin
if FR.Handle <= 4
then GetFileName := PChar(Ptr(Seg(StdOutName), StdNames[FR.Handle]+1))
else
{$ifdef LongNames}
GetFileName := FR.Name;
{$else LongNames}
GetFileName := @FR.Name;
{$endif LongNames}
InOutRes := 0;
end
else
begin
InOutRes := 102; { File not assigned error }
GetFileName := nil;
end;
end;
end;
procedure SetFileCase(CaseRule: TFileCase);
begin
FileCase := CaseRule;
end;
procedure UnpackTime(Time: Longint; var DT: TDateTime); assembler;
asm
les di,[DT]
mov ax,[LongRec(Time).Hi] { Packed date }
mov dx,ax
shr ax,9
cld
add ax,1980
stosw { DT.Year }
mov ax,dx
shr ax,5
and ax,$000F
stosw { DT.Month }
mov ax,dx
and ax,$1F
stosw { DT.Day }
mov ax,[LongRec(Time).Lo] { Packed time }
mov dx,ax
shr ax,11
stosw { DT.Hour }
mov ax,dx
shr ax,5
and ax,$3F
stosw { DT.Minute }
mov ax,dx
and ax,$1F
shl ax,1
stosw { DT.Second }
end;
procedure PackTime(const DT: TDateTime; var Time: Longint); assembler;
asm
push ds
lds si,[DT]
cld
les di,[Time]
lodsw { DT.Year }
sub ax,1980
shl ax,9
mov dx,ax
lodsw { DT.Month }
shl ax,5
add dx,ax
lodsw { DT.Day }
add ax,dx
db $66; shl ax,16 { Packed Date }
lodsw { DT.Hour }
shl ax,11
mov cx,ax
lodsw { DT.Minute }
shl ax,5
add cx,ax
lodsw { DT.Second }
shr ax,1
add ax,cx
pop ds
db $66; stosw { Store Time/Date}
end;
{------------------- File/Directory name related functions -----------------}
procedure DelDirSep(var Dir: TDirStr);
begin
if Dir[Length(Dir)] = '\'
then Dec(Dir[0]);
end;
procedure DelDirSepStr(Dir: PChar);
var
Len: Word;
begin
Len := MaxWord(StrLen(Dir)-1, 0);
if Dir[Len] = '\'
then Dir[Len] := #0;
end;
procedure AddDirSep(var Dir: TDirStr);
begin
if Dir[Length(Dir)] <> '\'
then Dir := Dir + '\';
end;
procedure AddDirSepStr(Dir: PChar);
var
Len: Word;
begin
Len := StrLen(Dir);
if (Len > 0) and (Dir[Len-1] <> '\') then
begin
Dir[Len] := '\';
Dir[Len+1] := #0;
end;
end;
function IsRootDir(const S: TPathStr): Boolean;
var
L: Word;
begin
L := Length(S);
IsRootDir := (L - 2 <= 1) and (S[2] = ':') and
((L = 2) or (S[3] = '\')) and DriveValid(S[1]);
end;
function IsRootDirStr(S: PChar): Boolean;
var
L: Word;
begin
L := StrLen(S);
IsRootDirStr := (L - 2 <= 1) and (S[1] = ':') and
((L = 2) or (S[2] = '\')) and DriveValid(S[0]);
end;
function IsDirectory(S: TPathStr): Boolean;
var
SR: TSearchRec;
begin
IsDirectory := false;
if S = ''
then Exit;
IsDirectory := true;
if not IsRootDir(S) then
begin
{$V-} DelDirSep(S); {$V+}
{$ifdef Windows}
PasToNull(S, @S)
FindFirst(@S, faReqDirectory + faAnyFile, SR);
{$else Windows}
FindFirst(S, faReqDirectory + faAnyFile, SR);
{$endif Windows}
FindClose(SR);
IsDirectory := IOResult = 0;
end;
end;
function IsDirectoryStr(S: PChar): Boolean;
var
SR: TSearchRec;
P : array[0..fsPathName] of Char;
begin
IsDirectoryStr := false;
if (S = nil) or (S^ = #0)
then Exit;
IsDirectoryStr := true;
if not IsRootDirStr(S) then
begin
StrLCopy(@P, S, fsPathName);
DosError := StrError;
if DosError = deNoError then
begin
DelDirSepStr(@P);
{$ifdef Windows}
FindFirst(@P, faReqDirectory + faAnyFile, SR);
{$else Windows}
FindFirstStr(@P, faReqDirectory + faAnyFile, SR);
{$endif Windows}
FindClose(SR);
end;
IsDirectoryStr := IOResult = 0;
end;
end;
procedure DoDirOpStr(Dir: PChar; DosOp: Byte);
var
Regs : TRegisters;
P : PChar absolute DosBuf;
SaveBuf: TNetPath;
Path : TPathNet;
V : PVolumeInfo;
label
Done;
begin
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
ClearRegs(Regs);
StrLCopy(@Path, Dir, fsNetPath);
if StrError <> 0 then
begin
DosError := StrError;
goto Done;
end;
{$ifdef Windows}
AnsiToOem(@Path, @Path);
{$endif Windows}
V := FileExpand(@Path, @Path, fcCasePreserve);
if DosError <> deNoError
then goto Done;
StrCopy(P, @Path);
Regs.DS := DosBuf.RealSeg;
Regs.AH := DosOp;
Regs.Flags := fCarry;
{$ifdef LongNames}
if V^.Attributes and vaDosLongNames <> 0
then Regs.AX := $7100 + DosOp; { LFN Create/Remove Directory }
{$endif LongNames}
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0
else GetExtError;
Done:
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
procedure CreateDir(Dir: PChar);
begin
DoDirOpStr(Dir, $39); { DOS - Create Directory }
end;
procedure RemoveDir(Dir: PChar);
begin
DoDirOpStr(Dir, $3A); { DOS - Remove Directory }
end;
type
PDosSearch = ^TDosSearch;
TDosSearch = packed record
Fill : packed array[1..21] of Byte; {00..20}
Attr : Byte; {21..21}
Time : Longint; {22..25}
Size : Longint; {26..29}
Name : array[0..High(TDosName)] of Char; {30..41}
end;
PLongSearch = ^TLongSearch;
{$ifdef LongNames}
TLongSearch = packed record
Attr : Longint; {000..003}
CTime : Longint; {004..007}
CTimeH : Longint; {008..011}
ATime : Longint; {012..015}
ATimeH : Longint; {016..019}
Time : Longint; {020..023}
TimeH : Longint; {024..027}
SizeH : Longint; {028..031}
Size : Longint; {032..035}
Rsvd : packed array[0..7] of Byte; {036..043}
Name : packed array[0..259] of Char; {044..303}
DosName: packed array[0..13] of Char; {304..317}
end;
{$else LongNames}
TLongSearch = TDosSearch;
{$endif LongNames}
PSearchBuf = ^TSearchBuf;
TSearchBuf = packed record
SR : TLongSearch;
Name: TPathName;
end;
PDosCdEntry = ^TDosCdEntry;
TDosCdEntry = record
EntryLen : Byte;
XarLen : Byte;
IntelLBN : DWord;
MotLBN : DWord;
IntelFileLen: DWord;
MotFileLen : DWord;
Reserved : array[0..7] of Byte;
InterSize : Byte;
InterSkip : Byte;
IntelVolNum : Word;
MotVolNum : Word;
FileName : String[223];
end;
PDosCdRom = ^TDosCDRom;
TDosCdRom = record
Path : array[0..fsDosPath] of Char;
Entry: TDosCDEntry;
end;
procedure ConvertSearchRec(var SR: TSearchRec);
var
L: PLongSearch absolute DosBuf; { Converts a Win95 style TLongSearch }
D: PDosSearch absolute DosBuf; { into a TP-style TSearchRec. Also }
IsDosName: Boolean; { converts the filname case if compiled}
Padder : Boolean; { for LFN support, and FileCase is set }
begin { to fnDosLower or fnDos1stCapital }
{$ifdef LongNames}
if SR.VolAttribs and vaDosLongNames <> 0
then begin
SR.Time := L^.Time;
SR.Size := L^.Size;
SR.Attr := WordRec(LongRec(L^.Attr).Lo).Lo;
Move(L^.Name, SR.Name, StrLen(@L^.Name)+1);
IsDosName := (SR.VolAttribs and vaCaseSensitive = 0) and
((L^.DosName[0] = #0) or
(StrComp(@L^.DosName, @L^.Name) = 0));
end
else
{$endif LongNames}
begin
Move(D^, SR, SizeOf(TDosSearch));
IsDosName := true;
end;
{ If IsDosName is true, then we have an all-uppercase 8.3 name in SR.Name }
if (SR.VolAttribs and vaCaseSensitive = 0) and (FileCase <> fnPreserve) then
case FileCase of
fnLowerCase:
StrLower(@SR.Name);
fnUpperCase:
StrUpper(@SR.Name);
else
{$ifdef LongNames}
if IsDosName then
{$endif LongNames}
if FileCase = fnDos1stUpper
then StrLower(@SR.Name[1]) { Capitalize 1st letter }
else StrLower(@SR.Name);
end;
{$ifdef Windows}
OemToAnsi(@SR.Name, @SR.Name); { SR.Name is array of Char in Windows }
{$else Windows}
SR.Name := NullToPas(@SR.Name);{ SR.Name is Pascal string in DOS/DPMI}
{$endif Windows}
end;
function FindNext(var SR: TSearchRec): Boolean;
var
Regs: TRegisters;
S : PDosSearch absolute DosBuf;
L : PLongSearch absolute DosBuf;
begin
FindNext := false;
ClearRegs(Regs);
{$ifdef LongNames}
if SR.VolAttribs and vaDosLongNames <> 0
then repeat { Win9x is buggy, so we have to to }
Regs.BX := SR.Handle; { check the returned attribs ourself }
Regs.SI := 1; { Return MS-DOS style time/date }
Regs.AX := $714F; { Dos7.x - Find Next Matching file }
Regs.ES := DosBuf.RealSeg;
DosError := MsDos(Regs); { LongFindNext }
if Regs.Flags and fCarry <> 0 then { carry set indicates an error }
begin
FindClose(SR); { Auto close the file search on error}
Exit;
end;
until WordRec(LongRec(L^.Attr).Lo).Lo and SR.AttrMask = SR.AttrMask
else
{$endif LongNames}
begin
Regs.DS := DosBuf.RealSeg;
Move(SR, S^, SizeOf(TDosSearch)); { Copy last SearchRec to DTA }
repeat
Regs.AH := $4F; { DOS - Find Next matching filespec }
DosError := MsDos(Regs); { FindNext }
if Regs.Flags and fCarry <> 0 { If carry is set then an error }
then Exit; { occured. No need to FindClose }
until S^.Attr and SR.AttrMask = SR.AttrMask;
end;
ConvertSearchRec(SR); { Convert to TSearchRec and Name case}
DosError := 0;
FindNext := true;
end;
{$ifndef Windows}
function FindFirst(Path: TPathStr; Attr: Word; var SR: TSearchRec): Boolean;
var
Regs : TRegisters;
SrchBuf: PSearchBuf absolute DosBuf;
S : PDosSearch absolute DosBuf;
SaveBuf: TSearchBuf;
V : PVolumeInfo;
label
Error;
begin
FindFirst := false;
FillChar(SR, SizeOf(SR), 0);
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
{ Add the "must have" file attributes to the "can have" file attributes }
WordRec(Attr).Lo := WordRec(Attr).Lo or WordRec(Attr).Hi;
PasToNull(Path, @SrchBuf^.Name); { ASCIIZ @DosBuf:SizeOf(TLongName) }
V := GetVolumeFromPath(@SrchBuf^.Name);
if V = nil
then goto Error;
SR.VolAttribs := V^.Attributes;
ClearRegs(Regs);
Regs.DS := DosBuf.RealSeg;
{$ifdef LongNames}
if SR.VolAttribs and vaDosLongNames <> 0
then begin
Regs.DX := SizeOf(TLongSearch); { DS:DX = @ASCIIZ filter string }
Regs.CX := Attr;
Regs.ES := DosBuf.RealSeg; { ES:DI := @TSearchRec }
Regs.Flags := fCarry; { Set CF for function supprt checking}
Regs.SI := 1; { Use MsDos date/time format }
Regs.AX := $714E; { DOS 7.x - Find First matching file }
end
else
{$endif LongNames}
begin { Set Disk Transfer Address to DosBuf}
if Length(Path) > High(TDosPath) then { Paths are limited to 79 }
begin { chars without LFN }
DosError := dePathTooLong;
goto Error;
end;
ClearRegs(Regs);
Regs.DS := DosBuf.RealSeg;
Regs.DX := SizeOf(TLongSearch); { DS:DX = @ASCIIZ filter string }
Regs.CX := Attr;
Regs.AH := $4E; { AH = Dos Function (FindFirstFile) }
end;
DosError := MsDos(Regs); { FindFirst or FindFirstLong }
if Regs.Flags and fCarry = 0 then { Carry set indicates an error, but }
begin { no need to FindClose. }
{ WIN95 BUG: Network drives on Direct Cable ignore the faReqXXXX flags. }
{ ie LFN $714E on network drives only works like DOS $4E }
{ This means TSearchRec always needs an AttrMask and FindFirst & }
{ FindNext always have to check the returned attributes. }
DosError := 0;
ConvertSearchRec(SR);
{$ifdef LongNames}
SR.Handle := Regs.AX; { Set FindFirst handle if successful }
{$endif LongNames}
SR.AttrMask:= WordRec(Attr).Hi; { Implement Win95-style "must have"s }
if (SR.Attr and SR.AttrMask <> SR.AttrMask)
then FindNext(SR);
end;
Error:
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
FindFirst := DosError = 0;
end;
function FindFirstStr(Path: PChar; Attr: Word; var SR: TSearchRec): Boolean;
{$else !Windows}
function FindFirst(Path: PChar; Attr: Word; var SR: TSearchRec): Boolean;
{$endif !Windows}
var
Regs : TRegisters;
SrchBuf: PSearchBuf absolute DosBuf;
S : PDosSearch absolute DosBuf;
V : PVolumeInfo;
SaveBuf: TSearchBuf;
label
Error;
begin
{$ifdef Windows}
FindFirst := false;
{$else Windows}
FindFirstStr := false;
{$endif Windows}
FillChar(SR, SizeOf(SR), 0);
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
{ Add the "must have" file attributes to the "can have" file attributes }
WordRec(Attr).Lo := WordRec(Attr).Lo or WordRec(Attr).Hi;
{ Copy Path into Buf.Name and use it to get the volume info }
StrLCopy(@SrchBuf^.Name, Path, SizeOf(TPathName));
DosError := StrError;
if DosError <> 0
then goto Error;
V := GetVolumeFromPath(@SrchBuf^.Name);
if V = nil
then goto Error;
SR.VolAttribs := V^.Attributes;
SR.Handle := 0;
ClearRegs(Regs);
Regs.DS := DosBuf.RealSeg;
{$ifdef LongNames}
if V^.Attributes and vaDosLongNames <> 0
then begin
Regs.DX := SizeOf(TLongSearch); { DS:DX = @ASCIIZ filter string }
Regs.CX := Attr;
Regs.ES := DosBuf.RealSeg; { ES:DI := @TSearchRec }
Regs.Flags := fCarry; { Set CF for function supprt checking}
Regs.SI := 1; { Use MsDos date/time format }
Regs.AX := $714E; { DOS 7.x - Find First matching file }
end
else
{$endif LongNames}
begin { Set Disk Transfer Address to DosBuf}
if StrLen(@SrchBuf^.Name) > fsDosPath then { Paths are limited to }
begin { 79 chars without LFN }
DosError := dePathTooLong;
goto Error;
end;
ClearRegs(Regs);
Regs.DS := DosBuf.RealSeg;
Regs.DX := SizeOf(TLongSearch); { DS:DX = @ASCIIZ filter string }
Regs.CX := Attr;
Regs.AH := $4E; { AH = Dos Function (FindFirstFile) }
end;
DosError := MsDos(Regs); { FindFirst or FindFirstLong }
if Regs.Flags and fCarry = 0 then { Carry set indicates an error, so }
begin { no need to FindClose. }
DosError := 0;
ConvertSearchRec(SR);
{$ifdef LongNames}
SR.Handle := Regs.AX; { Set FindFirst handle if successful }
{$endif LongNames}
SR.AttrMask:= WordRec(Attr).Hi; { Implement Win95-style "must have"'s}
if (SR.Attr and SR.AttrMask <> SR.AttrMask)
then FindNext(SR);
end;
Error:
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
{$ifdef Windows}
FindFirst := DosError = 0;
{$else Windows}
FindFirstStr := DosError = 0;
{$endif Windows}
end;
{$ifdef LongNames}
procedure FindClose(var SR: TSearchRec);
var
Regs: TRegisters;
begin
if (SR.VolAttribs and vaDosLongNames <> 0) and (SR.Handle <> 0) then
begin
ClearRegs(Regs);
Regs.AX := $71A1;
Regs.BX := SR.Handle;
MsDos(Regs);
SR.Handle := 0;
end;
end;
{$endif LongNames}
procedure ExpandPath; near; assembler; { NOT USED DS:SI = @Source ES:DI = @Target }
var { CX = Length(Source) }
Result: TPathName; { AX = Max length of return string }
asm
push ds { Save callers DS register }
push es { Save address of Target string }
push di
push ax { Save MaxLen argument }
push cx { Convert forslashes to backslashes }
push ds
push si
call SlashConvert
push ss
lea di,[Result]
pop es
add cx,si { DS:CX = @End of source path + 1 }
lodsw { if AH = ":" then AL = drive name }
cmp si,cx { past end of source path? }
ja @@GetDrive { Yes, so get current drive }
cmp ah,':' { AL represent drive letter? }
jne @@GetDrive { No, so get current drive }
cmp al,'a' { Validate drive letter }
jb @@2 { uppcase drive letter on invalid }
cmp al,'z'
ja @@2 { Invalid drive letter }
sub al,'a' - 'A' { Drive letter is always uppercase }
jmp @@2
@@GetDrive: sub si,2 { Go back 2 source path characters }
push ds
push cx
push es
mov cx,seg @Data
push di
push si
mov ds,cx
call GetCurDrive { Get the current drive letter in AL }
pop si
pop di
pop es
pop cx
pop ds
mov ah,':'
@@2: stosw { Store first 2 result path chars }
cmp si,cx { Reached end of source path string? }
je @@GetDir { Yes, so get current directory }
cmp byte ptr [si],'\' { Start from the root directory? }
je @@3 { Yes. }
@@GetDir: sub al,'A'-1 { Path is relative to current dir. }
sub di,2 { Remove 'D:' from @Result because }
push si { GetDir will set the drive as well }
push cx { as the current directory }
push ds
mov cx,seg @Data
push ax { Drive number argument }
push es { Directory PString argument }
push di
mov ds,cx
push fsPathName { Max string length argument }
push cs
call near ptr LfnGetDir
push ss
lea si,[Result] { Convert Pascal string to ASCII-Z }
pop ds
xor cx,cx
push ss
lea di,[Result]
xor ax,ax
pop es
cld
lodsb { Length of current directory string }
mov cl,al
rep movsb
mov al,'\' { Add the trailing backslash }
stosb
pop ds { DS:SI = @Source[n] }
pop cx
pop si
@@3: sub cx,si { Copy rest of source path to result }
rep movsb
mov ax,cx { AX = 0 }
push es
lea si,[Result]
stosb { End of result string marker }
lea si,[Result]
pop ds { DS:SI = @Result }
pop cx { CX = MaxLen argument }
pop di { ES:DI = @Target }
pop es
mov dx,di { Save Target.Ofs in DX }
@@4: lodsb { Run through the result string to }
or al,al { look for and remove any expanded }
jz @@6 { parts copied from the source path }
cmp al,'\' { All expanded parts are terminated }
je @@6 { by a backslash character }
@@5: stosb
loop @@4
mov ax,201 { Range check error }
jmp @@Exit
@@6: cmp word ptr [di-2],'.\' { "Root Directory" expanded? }
jne @@7 { No }
sub di,2 { Remove the ".\" from result }
jmp @@9
@@7: cmp word ptr [di-2],'..' { "Parent Directory" expanded? }
jne @@9 { No }
cmp byte ptr [di-3],'\' { ".." will have a double \\ before }
jne @@9 { it after expansion, so remove it }
sub di,3
cmp byte ptr [di-1],':'
je @@9
@@8: dec di
cmp byte ptr [di],'\'
jne @@8
@@9: or al,al
jne @@5
cmp byte ptr [di-1],':' { If the expanded path is just "D:" }
jne @@10 { then add a backslash to make it }
mov al,'\' { relative to the root directory. }
stosb
xor ax,ax
@@10: push es { ES:DI = @Target.NullChar }
push di { Store null terminator }
stosb { Convert case of path according to }
push es { (ES:DX = @Target) }
push dx { drive properties & user preferences}
mov dx,seg @Data
mov ds,dx
call ConvertPathCase
xor ax,ax { Returns AX = ZF = 0 if no error }
pop di { ES:DI = @Target.LastChar + 1 }
pop es
@@Exit: pop ds
or ax,ax
end;
function FStdExpand(Path: TPathStr): TPathStr; assembler; { NOT USED }
asm
push ds
lds si,[Path] { DS:SI = @Source path }
cld
lodsb { AL = Len(Path) }
xor cx,cx
les di,[@Result] { ES:DI = @Result }
mov cl,al { CX = Length(Path) }
inc di { Move past Result length byte }
mov ax,type TPathStr-1 { AX = max length of return string }
jcxz @@2
call ExpandPath { ES:DI @Result[1] DS:SI @Path[1] }
jz @@1 { No Error }
lds si,[Path] { Return Path unchanged }
push ax
xor ax,ax
les di,[@Result]
lodsb
stosb
rep movsb
pop ax
jmp @@Exit
@@1: mov ax,di { ES:AX = @LastChar+1 }
les di,[@Result] { ES:DI = @Length byte }
sub ax,di
dec ax
@@2: stosb { Store string length byte,+ }
xor ax,ax
@@Exit: pop ds
mov [InOutRes],ax
end;
function FileStdExpand(Dest, Name: PChar): PChar; assembler; { NOT USED }
asm
push ds
db $66; push [Name].Word[0] { Get length of source string }
call StrLen
lds si,[Name] { DS:SI = @source path }
mov cx,ax { CX = Length(Name) }
les di,[Dest] { ES:DI = @Result }
cld
mov ax,fsPathName { AX = Max length of return string }
jcxz @@1 { Null Name returns a null Dest }
call ExpandPath { ES:DI = @Result DS:SI @Arg CX=Len }
@@1: pop ds
mov [InOutRes],ax
end;
{ Looks for the network drive specified in P. Returns nil if P doesn't }
{ denote a locally mapped network drive. (NOT USED) }
function GetNetVolume(Path: PChar): PVolumeInfo;
var
V: PVolumeInfo;
P: TNetPath;
N: TNetName;
begin
P := StrLPas(Path, High(TNetPath));
{$V-} DosUpperCase(P); {$V+}
V := Get1stNetDrive;
while V <> nil do
begin
if (V^.Attributes and vaIsNetworkDrive) <> 0 then
begin
N := V^.NetName^;
{$V-} DosUpperCase(N); {$V+}
if Compare(N[1], P[1], Length(N)) = 0
then Break;
end;
V := V^.Next;
end;
GetNetVolume := V;
end;
{ Returns the ordinal position of the first wild character in P, or 0 }
{ if P does not contain any wildcards. Return value is 1 based. }
function FirstWildChar(P: PChar): Word; assembler;
asm
push P.Word[2]
push P.Word[0]
call StrLen
les di,[P]
mov bx,ax
mov cx,ax
mov al,'*'
repne scasb
je @@1
mov cx,bx
sub di,bx
mov al,'?'
repne scasb
mov ax,0
jne @@Exit
@@1: mov ax,bx
sub ax,cx
@@Exit:
end;
{ Return the start position of the next component in Path to the left of }
{ LastPos. Returns 0 if start of Path. Path must be a local drive path }
function PrevPathComp(Path: PChar; LastPos: Word): Word; assembler;
asm
push ds
lds si,[Path]
mov cx,[LastPos]
std
jcxz @@Exit
dec cx
add si,cx
jcxz @@Exit
@@NxtChar: lodsb
cmp al,':'
je @@Found
cmp al,'\'
je @@Found
loop @@NxtChar
@@Found: inc cx
cmp cx,[LastPos]
jb @@Exit
dec cx
loop @@NxtChar
@@Exit: mov ax,cx
cld
pop ds
end;
{ Checks the lengths of each path component of P against the MaxNameLen and }
{ MaxExtLEn of the TVolumeInfo in V. Also checks entire path length against }
{ MaxPathLen. P must not be a network path. DosError set if P invalid. }
function ValidPath(P: PChar; V: PVolumeInfo): Boolean; near; assembler;
var
EndPos, LastPos: Word;
MaxName,MaxExt : Word;
CompCnt : Word;
asm
les di,[V]
mov [CompCnt],0
mov ax,es:[di].TVolumeInfo.MaxNameLen
mov [MaxName],ax
mov ax,es:[di].TVolumeInfo.MaxExtLen
mov [MaxExt],ax
push es:[di].TVolumeInfo.MaxPathLen
db $66; push [P].Word[0]
call StrLen { EndPos := StrLen(P) }
pop cx { CX = V^.MaxPathLen }
mov [EndPos],ax
cmp ax,cx
jbe @@NxtComp
mov [DosError],dePathTooLong
jmp @@Error
@@NxtComp: db $66; push [P].Word[0] { LastPos:= PrevPathComp(P, EndPos) }
push [EndPos]
call PrevPathComp
les di,[P]
mov cx,[EndPos]
add di,ax { S := P + LastPos }
sub cx,ax { Count := EndPos - LastPos }
mov [LastPos],ax
dec ax
cld
mov [EndPos],ax { EndPos := LastPos -1 }
jcxz @@Ok
cmp cx,[MaxName]
mov al,'.'
jbe @@ChkExt
mov [DosError],deNameTooLong
cmp [CompCnt],0
je @@Error
mov [DosError],deDirTooLong
jmp @@Error
@@ChkExt: repne scasb
jne @@Ok
inc cx
cmp cx,[MaxExt]
jbe @@Ok
mov [DosError],deExtTooLong
@@Error: mov al,false
jmp @@Exit
@@Ok: inc [CompCnt]
cmp [LastPos],0
jne @@NxtComp
mov al,true
@@Exit:
end;
{ Returns the cannonical path and filename of the Path argument. The local }
{ substituted drive is returned for networked drives unless fcNetPath is set}
{ Wildcards are only allowed if the fcWildcards flags is set. }
function FExpand(const Path: String; Flags: Word): TPathStr;
var
Name : array[0..High(String)] of Char;
MaxLen: Word;
begin
MaxLen := High(TPathStr);
{$ifndef LongNames} { MaxLen not needed with LFN because }
if Flags and fcNetPath <> 0 { TPathStr and TNetPath are same size}
then MaxLen := High(TNetPath);
{$endif !LongNames}
FileExpand(@Name, StrPCopy(@Name, Path), Flags);
FExpand := StrLPas(@Name, MaxLen);
if DosError = deNoError
then DosError := StrError;
end;
type
PFileExpand = ^TFileExpand;
TFileExpand = record
SR : TDosSearch;
LongPath: TPathNet;
NetPath : TPathNet;
end;
function FileExpand(Dest, Name: PChar; Flags: Word): PVolumeInfo;
var
Regs : TRegisters;
P : PFileExpand absolute DosBuf;
SaveBuf : TFileExpand;
V : PVolumeInfo;
Pos : Integer;
NewPos : Integer;
SaveChar: Char;
Padder : Char;
MaxLen : Word;
SR : TSearchRec;
label
Error, Done, GetOut;
begin
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
ClearRegs(Regs);
StrLCopy(@P^.NetPath, Name, fsNetPath);
MaxLen := fsPathName;
if Flags and fcNetPath <> 0
then MaxLen := fsNetPath;
{ Determine the volume of Name from its path. Network }
{ names are converted to their local drive equivalent }
V := GetVolumeFromPath(@P^.NetPath); { Appends 'X:' if no drive specified }
FileExpand := V;
if V = nil
then goto Error;
{ Make sure the local drive path is not too long }
{ and does not contain any overlength components }
if not ValidPath(@P^.NetPath, V) then
Error:
begin
StrLCopy(Dest, Name, MaxLen);
goto GetOut;
end;
{ At this point we have validated the drive and made sure Name }
{ is not to long and doesn't contain any overlength components.}
if Flags and fcFileName <> 0
then Flags := Flags or fcDirectory;
{ The DOS/LFN functions don't handle wildcard characters as we would like,}
{ so only use the DOS/LFN function to expand the drive and directory if }
{ Name contains wildcards (in the Name.Ext), then add the Name.Ext }
{ containing the wildcard[s] to the expanded directory afterwards. }
Pos := FirstWildChar(@P^.NetPath);
if Pos <> 0 then
begin
if Flags and fcWildCards = 0 then
begin
DosError := deNoWildCards;
goto Error;
end;
if Flags and fcDirectory <> 0 { If we are to verify the dir }
then Flags := Flags or fcFileName; { then rest of the path must exist}
repeat
Dec(Pos);
until (Pos = 0) or (P^.NetPath[Pos] = '\') or (P^.NetPath[Pos] = ':');
{ The Name.Ext contains wildcard[s], but a path of some sort has also }
{ the been supplied or added, so we'll expand the given directory path }
{ then add Name.Ext to the expanded directory path at the end. }
Inc(Pos);
SaveChar := P^.NetPath[Pos]; { Replace 1st name char with #0 }
P^.NetPath[Pos] := #0;
end;
{ Use the operating system call to expand the given path. The LFN }
{ version should translate network names to local drive names. }
ClearRegs(Regs);
Regs.ES := DosBuf.RealSeg;
Regs.DI := SizeOf(TDosSearch); { ES:DI = @P^.LongPath }
Regs.DS := Regs.ES;
Regs.SI := SizeOf(TDosSearch) + SizeOf(TPathNet); { DS:SI = @P^.NetPath }
Regs.AH := $60; { DOS - Get cannonical true name }
{$ifdef LongNames}
if V^.Attributes and vaDosLongNames <> 0 then
begin
Regs.AX := $7160; { LFN - Get cannonical path }
Regs.CX := $8000; { Return subst'd drive }
end;
{$endif LongNames}
DosError:= MsDos(Regs);
if Regs.Flags and fCarry = 0
then begin
DosError := 0;
if Flags and (fcFileName + fcDirectory) <> 0 then
repeat { Either the full path or the directory has to exist }
{$ifdef LongNames}
if V^.Attributes and vaDosLongNames <> 0
then begin
{ Expand any short 8.3 DOS alias file/dir names to }
{ their true longname. May return 'PathNotFound' }
{ WIN95 BUG: Does not always return qualified path! }
{ Passing "[C:]FILENAME.EXT" returns unchanged!!!! }
Regs.AX := $7160; { LFN - Get cannonical LFN path }
Regs.CX := $8002; { Return subst'd drive }
DosError:= MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := deNoError;
end
else
{$endif LongNames} begin
{ We'll use the DOS FindFirst function to determine if }
{ the path exists. TDosSearch is returned in the DTA }
Regs.DX := SizeOf(TDosSearch); { DS:DX = @ASCIIZ filter}
Regs.CX := faAnything;
Regs.AH := $4E; { AH = Dos Function (FindFirstFile) }
DosError:= MsDos(Regs); { DOS - FindFirst }
if Regs.Flags and fCarry = 0
then DosError := deNoError
else if DosError = deNoMoreFiles
then DosError := dePathNotFound;
end;
if DosError <> deNoError then
begin
if Flags and fcFileName <> 0 { Whole path must exist or }
then Break; { removed NAME.EXT already }
Inc(Flags, fcFileName); { Break on next iteration }
{ Remove FILENAME.EXT from P^.LongPath and try again }
NewPos := PrevPathComp(@P^.LongPath, StrLen(P^.LongPath));
if NewPos > 3 { Only keep the trailing '\' }
then Pos := NewPos-1 { if we are at the root dir }
else begin { Otherwise we know the path }
DosError := deNoError; { is valid because the }
Break; { GetVolumeFromPath function }
end; { has told us already. }
P^.LongPath[Pos] := #0;
{ Mark the position of FILENAME.EXT in P^.NetPath }
Pos := PrevPathComp(@P^.NetPath, StrLen(P^.NetPath));
if Pos > 3 { Only keep the trailing '\' }
then Dec(Pos); { if we are at the root dir }
if Pos > 0 then
begin
SaveChar := P^.NetPath[Pos];
P^.NetPath[Pos] := #0;
end;
end;
until (DosError = deNoError) or (Pos <= 0);
if (Flags and fcNetPath = 0) and (P^.LongPath[1] <> ':')
then ConvertNetPath(@P^.LongPath); { Not normal 'X:\' form }
StrLCopy(Dest, P^.LongPath, MaxLen); { Net name -> drv name }
if Flags and fcCasePreserve = 0
then ConvertPathCase(Dest, V); { Convert file case of result }
end
else begin
StrLCopy(Dest, Name, MaxLen); { Given path is invalid }
if Integer(Word(DosError) - 2) < 1 { Invalid/Malformed component }
then DosError := deInvalidPath;
end;
Done:
if Pos <> 0 then { Add non-exsistant part of path to }
begin { the returned expanded path string }
if SaveChar <> '\'
then AddDirSepStr(Dest); { Add trailing backslash to directory}
P^.NetPath[Pos] := SaveChar; { Add Name.Ext to directory. }
StrLCat(Dest, PChar(@P^.NetPath[Pos]), MaxLen);
if DosError = deNoError
then DosError := StrError;
end;
GetOut:
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
function FDosExpand(const Path: TPathStr): TDosPath;
var
P: TPathName;
begin
PasToNull(Path, @P);
FileDosExpand(@P, @P);
FDosExpand := StrLPas(@P, High(TPathStr));
end;
function FileDosExpand(DosPath, LongPath: PChar): PChar;
{$ifdef LongNames}
var
Regs : TRegisters;
P : PNetNet absolute DosBuf;
SaveBuf: TNetNet;
V : PVolumeInfo;
begin
FileDosExpand := DosPath;
{ We have to determine what volume is associated with Path in order to }
{ determine whether to use the DOS or the LFN "truename" function. }
V := GetVolumeFromPath(LongPath);
if (V <> nil) and (V^.Attributes and vaDosLongNames <> 0)
then begin
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
StrLCopy(@P^.LongPath, LongPath, High(TNetPath));
ClearRegs(Regs);
Regs.DS := DosBuf.RealSeg; { Convert long path and put in }
Regs.ES := Regs.DS; { DosBuf. DS:SI = @P^.LongPath }
Regs.DI := SizeOf(TPathNet); { ES:DI = @P^.NetPath }
Regs.CX := $8001; { Get short path, use subst drive }
Regs.AX := $7160; { LFN - Get short filename }
DosError:= MsDos(Regs);
if Regs.Flags and fCarry = 0
then begin
if P^.NetPath[1] <> ':' { Not normal 'X:\' form}
then ConvertNetPath(@P^.NetPath); { Net name -> drv name }
StrLCopy(DosPath, P^.NetPath, fsDosPath);
end
else StrLCopy(DosPath, LongPath, fsDosPath);
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end
else
{$else LongNames}
begin
{$endif LongNames}
FileExpand(DosPath, LongPath, fcCasePreserve);
FileDosExpand := DosPath;
end;
function FDosContract(const Name: TPathStr): TDosPath;
var
CD,ND: TPathStr; { Current Dir / Name Dir }
DL,NL: Byte; { Length(CD) / Length(Name)}
begin
{$V-}
ND := FDosExpand(Name); { 8.3 name of Name path }
DelDirSep(ND);
GetDir(0, CD);
CD := FDosExpand(CD); { 8.3 name of current path }
DelDirSep(CD);
{$V+}
DL := Length(CD);
NL := Length(ND);
if (NL > DL) and (ND[DL+1] = '\') and (Compare(CD[1], ND[1], DL) = 0)
then FDosContract := Copy(ND, DL+2, 255)
else FDosContract := ND;
end;
function FileDosContract(Dest, Name: PChar): PChar;
var
CD,ND: TPathName;
DL,NL: Word;
begin
DelDirSepStr(FileDosExpand(@ND, Name));
DelDirSepStr(FileDosExpand(@CD, GetCurDir(@CD, 0)));
DL := StrLen(@CD);
NL := StrLen(@NL);
if (NL > DL) and (ND[DL+1] = '\') and (Compare(CD, ND, DL) = 0)
then StrLCopy(Dest, @ND[DL+1], fsPathName)
else StrLCopy(Dest, Name, fsPathName);
FileDosContract := Dest;
end;
function FContract(const Path: TPathStr): TPathStr;
var
CD : TDirStr; { Current Directory }
DL,NL: Byte; { Length(CD) / Length(Name)}
begin
{$V-}
GetDir(0, CD);
DelDirSep(CD);
{$V+}
DL := Length(CD);
NL := Length(Path);
if (NL > DL) and (Path[DL+1] = '\') and
(Compare(CD[1], Path[1], DL) = 0)
then FContract := Copy(Path, DL+2, 255)
else FContract := Path;
end;
function FileContract(Dest, Name: PChar): PChar;
var
CD,ND: TPathName;
DL,NL: Word;
begin
DelDirSepStr(GetCurDir(@CD, 0));
DL := StrLen(CD);
StrLCopy(@ND, Name, fsPathName);
NL := StrLen(ND);
if (NL > DL) and (ND[DL+1] = '\') and (Compare(CD, ND, DL) = 0)
then StrLCopy(Dest, @ND[DL+1], fsPathName)
else StrLCopy(Dest, Name, fsPathName);
FileContract := Dest;
end;
function FSearch(const Path: String; DirList: String): TPathStr;
function GetNextDir: TDirStr; near;
var
P: Integer;
begin
P := Pos(';', DirList);
if P = 0
then P := Length(DirList) + 1;
GetNextDir := Copy(DirList, 1, P-1);
DirList := Copy(DirList, P+1, 255);
end;
var
Dir: TPathStr;
SR : TSearchRec;
begin
FSearch := '';
if Length(Path) = 0
then Exit;
if (Path[1] = '\') or (Path[1] = '/') or (Path[2] = ':') { If given the }
then DirList := ''; { path or current directory, then }
repeat { don't use the DirList at all. }
Dir := GetNextDir;
if (Dir <> '') and (Dir[Length(Dir)] <> '\')
then Dir := Dir + '\';
{$ifdef Windows}
PasToNull(Dir + Path, @Dir);
FindFirst(@Dir, faArchive + faReadOnly + faSysFile, SR);
{$else Windows}
FindFirst(Dir + Path, faAnyFile, SR);
{$endif Windows}
if IOResult = deNoError then
begin
FindClose(SR);
FSearch := FExpand(Dir + Path, fcFileName);
Exit;
end;
until DirList = '';
end;
function FileSearch(Dest, Path, DirList: PChar): PChar;
begin
FileSearch := StrPCopy(Dest, FSearch(NullToPas(Path), NullToPas(DirList)));
end;
procedure FSplit(const Path: TPathStr; var Dir: TDirStr; var Name: TNameStr;
var Ext: TExtStr);
var
N: TPathName; { Splits a path into its constituent parts. }
P: PChar; { Ext contains the last "." plus all characters}
C: Word; { after the last ".", up to the maximum allowed}
begin { for TExtStr. Name contains all characters }
P := @N; { after the last "\", excluding the extension }
C := PasToNull(Path, P); { (if any). Dir will contain all remaining }
Inc(P, C); { characters (if any) in Path, including any }
Dir := ''; { trailing '\' character. }
Name:= ''; { Any or all returned components could be null.}
Ext := '';
while C <> 0 do
begin
Dec(P);
Dec(C);
case P^ of
'.':
if Ext = '' then
begin
Ext := StrLPas(P, High(TExtStr));
P^ := #0;
end;
'\':
if Name = '' then
begin
Inc(P);
Inc(C);
Name := StrLPas(P, High(TNameStr));
P^ := #0;
Dir := StrLPas(@N, High(TDirStr));
Exit;
end;
end;
end;
Name := StrLPas(@N, High(TDirStr))
end;
function FileSplit(Path, Dir, Name, Ext: PChar): Word;
var
N: TPathName; { Splits a path into its constituent parts. }
P: PChar; { Ext contains the last "." plus all characters}
C: Word; { after the last ".", up to the maximum allowed}
R: Word; { for TExtStr. Name contains all characters }
begin { after the last "\", excluding the extension }
R := 0; { (if any). Dir will contain all remaining }
P := @N; { characters (if any) in Path, including any }
C := StrLCopy(P, Path, fsPathName); { trailing '\' character. }
SlashConvert(C, N); { Any or all returned components could be null.}
Inc(P, C); { the returned word indicates those parts that }
Dir^ := asNull; { that contain a non-null string. }
Name^:= asNull;
Ext^ := asNull;
while C <> 0 do
begin
Dec(P);
Dec(C);
case P^ of
'.':
if R and (fcExtension + fcFileName) = 0 then
begin
StrLCopy(Ext, P, fsExtension);
P^ := #0;
R := fcExtension;
end;
'*','?':
R := R or fcWildCards;
'\':
if R and fcFileName = 0 then
begin
Inc(P);
Inc(C);
if StrLCopy(Name, P, fsFileName) <> 0
then Inc(R, fcFileName);
P^ := #0;
if StrLCopy(Dir, @N, fsDirectory) <> 0
then Inc(R, fcDirectory);
FileSplit := R;
Exit;
end;
end;
end;
if StrLCopy(Name, @N, fsDirectory) <> 0
then Inc(R, fcFileName);
FileSplit := R;
end;
function FCompare(Name1, Name2: String): Integer;
var
Result: Integer;
L1,L2 : Integer;
V : PVolumeInfo;
begin
PasToNull(Name1, @Name1);
PasToNull(Name2, @Name2);
FCompare := FileCompare(@Name1, @Name2);
end;
function FileCompare(Name1, Name2: PChar): Integer;
var
Result: Integer;
L1,L2 : Integer;
N1,N2 : TPathName;
V : PVolumeInfo;
begin
FileExpand(@N2, Name2, fcWildcards + fcDirectory + fcCasePreserve);
DelDirSepStr(@N2);
L2 := StrLen(@N2);
V := FileExpand(@N1, Name1, fcWildcards + fcDirectory + fcCasePreserve);
DelDirSepStr(@N1);
L1 := StrLen(@N1);
if (DosError = deNoError) and (V^.Attributes and vaCaseSensitive = 0) then
begin
StrUpper(@N1);
StrUpper(@N2);
end;
Result := Compare(N1, N2, Min(L1, L2));
if (Result = 0) and (L1 <> L2)
then Result := -1 + (Ord(L1 > L2) shl 1);
FileCompare := Result;
end;
procedure FErase(const FileName: String);
var
Name: array[0..High(String)] of Char;
begin
PasToNull(FileName, @Name);
FileErase(@Name);
end;
procedure FileErase(FileName: PChar);
{$ifdef TurboLong} assembler;
asm
push ds
{$ifdef LongNames}
mov [DosError],0
db $66; mov ax,[FileName].Word[0]
call CheckForLfnDrv
mov ah,$41 { DOS - delete file }
jz @@NoLFN
mov ax,$7141 { LFN - delete file }
xor si,si { No wildcards allowed }
@@NoLFN: xor cx,cx
{$else LongNames}
xor cx,cx
mov [DosError],cx
mov ah,$41 { DOS - delete file }
{$endif LongNames}
lds dx,[FileName] { DS:DX = @FileName }
int intDos
pop ds
jnc @@Exit
call GetExtError
@@Exit:
end;
{$else TurboLong}
var
Regs : TRegisters;
Name : PChar absolute DosBuf;
SaveBuf: TPathNet;
V : PVolumeInfo;
label
Error;
begin
ClearRegs(Regs);
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
StrLCopy(Name, FileName, fsNetPath);
Regs.DS := DosBuf.RealSeg;
Regs.AH := $41; { DOS - delete file }
Regs.Flags := fCarry;
{$ifdef LongNames}
V := GetVolumeFromPath(Name);
if V = nil
then goto Error;
if V^.Attributes and vaDosLongNames <> 0
then Regs.AX := $7141; { LFN - delete file }
{$endif LongNames}
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0
else GetExtError;
Error:
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
{$endif TurboLong}
procedure FRename(const OldName, NewName: String);
var
Old: array[0..High(String)] of Char;
New: array[0..High(String)] of Char;
begin
PasToNull(OldName, Old);
PasToNull(NewName, New);
FileRename(@Old, @New);
end;
procedure FileRename(OldName, NewName: PChar);
{$ifdef TurboLong} assembler;
var
New: TPathName;
asm
lea si,[New]
push ss { FileExpand.Dest argument }
push si
db $66; push word ptr [NewName] { FileExpand.Path argument }
push fcDirectory + fcCasePreserve { New doesn't have to exist }
push cs { but it needs validating }
call near ptr FileExpand { because DOS Rename will }
cmp [DosError],deNoError { trancate an overlength }
jne @@Exit { name and give no error! }
mov es,dx { ES:DI = @VolumeInfo }
mov di,ax
push ds
mov ah,$56 { DOS - Rename File }
xor cx,cx
mov [DosError],cx
{$ifdef LongNames}
test es:[di].TVolumeInfo.Attributes,vaDosLongNames
je @@1
mov ax,$7156 { LFN - Rename File }
{$endif LongNames}
@@1: lds dx,[OldName] { DS:DX = @Old_name }
push ss
lea di,[New]
pop es { ES:DI = @New_name }
int intDos
pop ds
jnc @@Exit
call GetExtError
@@Exit:
end;
{$else TurboLong}
var
Regs : TRegisters;
Names : PRename absolute DosBuf;
SaveBuf: TRename;
New : TPathName;
Flags : Word;
V : PVolumeInfo;
label
Done;
begin
ClearRegs(Regs);
CheckDosBuf(SaveBuf, SizeOf(SaveBuf)); { Validate new name because}
V := FileExpand(@New, NewName, fcDirectory + fcCasePreserve);
if DosError <> deNoError { DOS Rename function will }
then goto Done; { just truncate a too-long }
StrLCopy(@Names^.Old, OldName, fsNetName); { filename & return OK!!! }
StrCopy(@Names^.New, NewName);
Regs.DS := DosBuf.RealSeg;
Regs.ES := DosBuf.RealSeg;
Regs.DI := SizeOf(TRename) div 2; { ES:DI = @Names.NewName }
Regs.AH := $56; { DOS - Rename File }
{$ifdef LongNames}
if V^.Attributes and vaDosLongNames <> 0
then Regs.AX := $7156; { LFN - Rename file }
{$endif LongNames}
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0
else GetExtError;
Done:
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
{$endif TurboLong}
{************************* File Handle based functions *********************}
function FileGetTime(Handle: Word): Longint;
{$ifdef TurboDos} assembler;
asm
mov ax,$5700
mov bx,[Handle]
int intDos
mov bx,0
jnc @@NoError
call GetExtError
xor cx,cx
xor dx,dx
@@NoError: mov [DosError],bx
mov ax,cx
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.BX := Handle;
Regs.AX := $5700; { DOS - Get file date and time }
Regs.Flags := fCarry;
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0
else GetExtError;
FileGetTime := LongMake(Regs.DX, Regs.CX);
end;
{$endif TurboDos}
procedure FileSetTime(Handle: Word; Time: Longint);
{$ifdef TurboDos} assembler;
asm
mov [DosError],0
mov cx,[LongRec(Time).Lo]
mov dx,[LongRec(Time).Hi]
mov ax,$5701 { DOS - Set file date and time }
mov bx,[Handle]
int intDos
jnc @@Exit
call GetExtError
@@Exit:
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.BX := Handle;
Regs.CX := LongRec(Time).Lo;
Regs.DX := LongRec(Time).Hi;
Regs.AX := $5701; { DOS - Set file date and time }
DosError:= MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0
else GetExtError;
end;
{$endif TurboDos}
function FileOpen(const Name: String; Mode: Word): TFileHandle;
var
Path: array[0..High(String)] of Char;
begin
PasToNull(Name, @Path);
FileOpen := FileOpenStr(@Path, Mode);
end;
function FileOpenStr(Name: PChar; Mode: Word): TFileHandle;
var
Regs : TRegisters;
V : PVolumeInfo;
{$ifndef TurboLong}
SaveBuf: TPathNet;
Path : PChar absolute DosBuf;
ErrStr : String[5];
{$endif !TurboLong}
label
Error;
begin
FileOpenStr := TFileHandle(-1);
ClearRegs(Regs);
{$ifdef TurboLong}
Regs.DS := PtrRec(Name).Seg; { No Need to use DosBuf - Just set }
Regs.DX := PtrRec(Name).Ofs; { Regs.DS:DX to @File_Name (for DOS) }
{$else TurboLong}
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
Regs.DS := DosBuf.RealSeg; { Regs.DS:SI and DS:DX = @Name }
StrLCopy(Path, Name, fsNetPath); { Copy Filename into DosBuf }
{$endif TurboLong}
Regs.AX := Mode;
if WordRec(Mode).Hi - $3C > 1 then { Mode.Hi must be $3C or $3D }
begin
DosError := deInvalidfunc;
goto Error;
end;
{$ifdef LongNames}
V := GetVolumeFromPath(Name); { See if LFN functions are supported }
if V = nil { by the target drive }
then goto Error;
if V^.Attributes and vaDosLongNames <> 0 then
begin
{$ifdef TurboLong}
Regs.SI := Regs.DX; { Regs.DS:SI = @File_Name for LFN }
{$endif TurboLong}
if WordRec(Mode).Hi = $3C { Create file }
then begin
Regs.DX := $12; { Create new file or truncate old }
Mode := Mode or $02; { Allow read/write access. }
end
else Regs.DX := $01; { Open file - fail if not exist }
Regs.BL := WordRec(Mode).Lo; { Access and sharing flags }
Regs.AX := $716C; { LFN - Open or create file }
end;
{$endif LongNames}
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then begin
FileOpenStr := Regs.AX; { Return file handle }
DosError := 0;
end
else GetExtError;
Error:
{$ifndef TurboLong}
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
{$endif !TurboLong}
end;
procedure FileClose(Handle: TFileHandle);
{$ifdef TurboDos} assembler;
asm
mov bx,[Handle]
mov ah,$3E
int intDos
mov bx,0
jnc @@Exit
call GetExtError
@@Exit: mov [DosError],bx
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.BX := Handle;
Regs.AH := $3E;
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0
else GetExtError;
end;
{$endif TurboDos}
function FilePosition(Handle: TFileHandle): Longint;
{$ifdef TurboDos} assembler;
asm
mov bx,[Handle]
xor cx,cx
mov [DosError],0
mov ax,$4200 + skCurrent
mov dx,cx
int intDos
jnc @@Ok
call GetExtError
mov ax,-1
cwd
@@Ok:
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.BX := Handle; { Use Dos LSeek(0, fsCurPos) to }
Regs.AX := $4200 + Ord(skCurrent); { get the current file position }
DosError:= MsDos(Regs); { without changing file position}
FilePosition := -1;
if Regs.Flags and fCarry = 0
then begin
FilePosition := LongMake(Regs.DX, Regs.AX);
DosError := 0;
end
else GetExtError;
end;
{$endif TurboDos}
function FileSize(Handle: TFileHandle): Longint;
{$ifdef TurboDos} assembler;
asm { Save current file position }
mov [DosError],0
xor dx,dx
xor cx,cx
mov bx,[Handle]
mov ax,$4200 + skCurrent
int intDos
push dx
push ax
xor dx,dx
xor cx,cx
mov ax,$4200 + skEnd { Seek to EOF, returning pos }
int intDos
pop si
pop cx
push dx
push ax
mov dx,si
mov ax,$4200 + skStart { Seek to saved file position }
int intDos
pop ax
pop dx
jnc @@Exit
call GetExtError
mov ax,-1
cwd
@@Exit:
end;
{$else TurboDos}
var
Regs: TRegisters;
Pos : Longint;
label
Error;
begin
ClearRegs(Regs);
Regs.BX := Handle;
Regs.AX := $4200 + Ord(skCurrent); { Save current file position }
LongRec(Pos).Lo := MsDos(Regs);
if Regs.Flags and fCarry <> 0
then goto Error;
LongRec(Pos).Hi := Regs.DX;
ClearRegs(Regs);
Regs.AX := $4200 + Ord(skEnd); { Seek to end of file }
Regs.BX := Handle; { returns position of EOF }
MsDos(Regs);
if Regs.Flags and fCarry <> 0
then goto Error;
FileSize := LongMake(Regs.DX, Regs.AX);
ClearRegs(Regs);
Regs.AX := $4200 + Ord(skStart); { Seek to saved file position}
Regs.BX := Handle;
Regs.DX := LongRec(Pos).Lo;
Regs.CX := LongRec(Pos).Hi;
MsDos(Regs);
if Regs.Flags and fCarry <> 0
then goto Error;
Regs.AX := 0;
Error:
DosError := Regs.AX;
if DosError <> 0 then
begin
GetExtError;
FileSize := -1;
end;
end;
{$endif TurboDos}
function FileSeek(Handle: TFileHandle; Pos: Longint; SeekType: TFileSeek): Longint;
{$ifdef TurboDos} assembler;
asm
mov [DosError],0
mov ah,$42
mov bx,[Handle]
mov dx,Pos.Word[0] { Pos goes in CX:DX }
mov cx,Pos.Word[2]
mov al,[SeekType]
int intDos { Abs pos returned in DX:AX }
jnc @@Ok
call GetExtError
mov ax,-1
mov dx,ax
@@Ok:
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.AH := $42; { Seek to given file position}
Regs.AL := Ord(SeekType);
Regs.BX := Handle;
Regs.DX := LongRec(Pos).Lo;
Regs.CX := LongRec(Pos).Hi;
DosError:= MsDos(Regs);
FileSeek := -1;
if Regs.Flags and fCarry = 0
then begin
FileSeek:= LongMake(Regs.DX, Regs.AX); { Abs pos retrnd in DX:AX }
DosError := 0
end
else GetExtError;
end;
{$endif TurboDos}
function FileRead(Handle: TFileHandle; var Buf; Count: Word): Word;
{$ifdef TurboDos} assembler;
asm
push ds
mov [DosError],0
lds dx,[Buf]
mov cx,[Count]
mov bx,[Handle]
mov ah,$3F
int intDos
pop ds
jnc @@Ok
call GetExtError
les si,[Buf]
xor ax,ax
mov cx,[Count]
cld
rep stosb
@@Ok:
end;
{$else TurboDos}
var
Regs : TRegisters;
T : TByteArray absolute Buf;
S : PByteArray absolute DosBuf;
J,C,R: Word;
begin
FileRead := 0;
if (DosBuf.Size < 512) and not DosInit then
begin
DosError := deBadMemBlock;
FillChar(Buf, Count, 0);
Exit;
end;
J := 0;
R := 0;
ClearRegs(Regs);
Regs.DS := DosBuf.RealSeg;
DosError := 0;
Regs.BX := Handle;
while Count <> 0 do
begin
C := MinWord(Count, DosBuf.Size);;
Regs.CX := C;
Regs.AH := $3F;
MsDos(Regs);
if Regs.Flags and fCarry <> 0 then
begin
FillChar(T[J], Count, 0);
DosError := Regs.AX;
GetExtError;
Break;
end;
Move(S^, T[j], Regs.AX);
Inc(J, Regs.AX);
Dec(Count, Regs.AX);
Inc(R, Regs.AX);
if Regs.AX <> C
then Break;
end;
FileRead := R;
end;
{$endif TurboDos}
function FileWrite(Handle: TFileHandle; const Buf; Count: Word): Word;
{$ifdef TurboDos} assembler;
asm
mov cx,[Count]
mov [DosError],0
jcxz @@1
push ds
lds dx,[Buf]
mov bx,[Handle]
mov ah,$40
int intDos
pop ds
jnc @@2
call GetExtError
@@1: xor ax,ax
@@2:
end;
{$else TurboDos}
var
Regs : TRegisters;
S : TByteArray absolute Buf;
T : PByteArray absolute DosBuf;
j,C,R: Word;
begin
FileWrite := 0;
if (Count = 0) or ((DosBuf.Size < 512) and not DosInit)
then Exit;
j := 0;
R := 0;
ClearRegs(Regs);
Regs.DS := DosBuf.RealSeg;
DosError := 0;
Regs.BX := Handle;
while Count <> 0 do
begin
C := MinWord(Count, DosBuf.Size);
Move(S[j], T^, C);
Regs.AH := $40;
Regs.CX := C;
MsDos(Regs);
if Regs.Flags and fCarry <> 0 then
begin
DosError := Regs.AX;
GetExtError;
Break;
end;
Inc(j, Regs.AX);
Dec(Count, Regs.AX);
Inc(R, Regs.AX);
if Regs.AX <> C
then Break;
end;
FileWrite := R;
end;
{$endif TurboDos}
procedure FileTruncate(Handle: TFileHandle);
{$ifdef TurboDos} assembler;
asm
xor cx,cx
mov bx,[Handle]
mov [DosError],cx
mov ah,$40
mov [DosError],cx
int intDos
jnc @@Ok
call GetExtError
@@Ok:
end;
{$else TurboDos}
var
Regs: TRegisters;
begin
ClearRegs(Regs);
Regs.BX := Handle;
Regs.AH := $40;
DosError := MsDos(Regs);
if Regs.Flags and fCarry = 0
then DosError := 0
else GetExtError;
end;
{$endif TurboDos}
{-------------------------- Case conversion functions ----------------------}
procedure DosUpperCase(var S: String); assembler;
asm
les di,[S]
xor cx,cx
xor bx,bx
mov cl,[es:di]
inc di
jcxz @@Exit
@@Next: mov bl,[es:di]
inc di
mov al,[bx+offset LoToUpTbl]
dec cx
mov [es:di-1],al
jnz @@Next
@@Exit:
end;
procedure DosLowerCase(var S: String); assembler;
asm
les di,[S]
xor cx,cx
xor bx,bx
mov cl,[es:di]
inc di
jcxz @@Exit
@@Next: mov bl,[es:di]
inc di
mov al,[bx+offset UpToLoTbl]
dec cx
mov [es:di-1],al
jnz @@Next
@@Exit:
end;
function StrUpper(Str: PChar): PChar; assembler;
asm
les di,[Str]
xor cx,cx
mov ax,es
xor bx,bx
or ax,di
jz @@Exit
@@Next: mov bl,[es:di]
inc di
or bx,bx
mov al,[bx+offset LoToUpTbl]
mov [es:di-1],al
jnz @@Next
@@Exit: mov dx,es
mov di,[Str].Word[0]
end;
function StrLower(Str: PChar): PChar; assembler;
asm
les di,[Str]
xor cx,cx
mov ax,es
xor bx,bx
or ax,di
jz @@Exit
@@Next: mov bl,[es:di]
inc di
or bx,bx
mov al,[bx+offset UpToLoTbl]
mov [es:di-1],al
jnz @@Next
@@Exit: mov dx,es
mov di,[Str].Word[0]
end;
{------------------ Programmable Interrupt Timer functions -----------------}
function GetPit0Count: Word; assembler;
asm
xor ax,ax
cli
out pitCtrl,al
in al,pitTimer0
shl ax,8
in al,pitTimer0
xchg al,ah
sti
end;
function GetPit1Count: Word; assembler;
asm
mov al,2
cli
out pitCtrl,al
in al,pitTimer1
shl ax,8
in al,pitTimer1
xchg al,ah
sti
end;
function GetPit2Count: Word; assembler;
asm
xor ax,ax
cli
out pitCtrl,al
in al,pitTimer2
shl ax,8
in al,pitTimer2
xchg al,ah
sti
end;
procedure SetPit0Mode(Mode: Word; Value: Word); assembler;
asm
mov ax,[Mode]
cmp ax,6
ja @@Exit
shl ax,1
add al,$30
cli
out pitCtrl,al
mov ax,[Value]
out pitTimer0,al
shr ax,8
out pitTimer0,al
sti
@@Exit:
end;
function GetPit0Mode: Word;
begin
Port[pitCtrl] := $C2; { Readback command is only }
GetPit0Mode := (Port[pitTimer0] and $E) shr 1;{ possible with the 8254 ! }
end;
function GetPitType: Word;
function GetPITValue(Channel: Byte): Word;
var
j: Word;
begin
Port[pitCtrl] := Channel shl 6;
j := Port[pitTimer0 + Channel];
j := Port[pitTimer0 + Channel] shl 8 + j;
GetPITValue := j;
end;
const
testValue = $55AA;
backwards = Lo(TestValue) shl 8 + Hi(TestValue);
expCntStatus = $30; { Expected counter status }
var
Port61 : Byte;
PitType : Byte;
RdbStatus1 : Byte;
RdbStatus2 : Byte;
RdbCount1 : Word;
RdbCount2 : Word;
i,j : Word;
label
GotType;
begin
PitType := pitEmulated;
DisableInterrupts;
{ Turn off speaker & set gate2 input to low }
Port61 := Port[$61];
Port[$61] := Port61 and $FC;
{ Program channel 2 to mode 0, two bytes, binary }
Port[$43] := $B0;
Port[$42] := Lo(TestValue);
Port[$42] := Hi(TestValue);
{ Wait until the value of counter 0 changes }
i := GetPitValue(0);
repeat
j := GetPitValue(0);
until i <> j;
repeat
until j <> GetPitValue(0);
{ Read value from counter 2, test if readout is stable }
i := GetPitValue(2);
j := GetPitValue(2);
{ if not then the PIT is bad or emulated }
if (i <> j) or (i <> TestValue)
then goto GotType;
{ Readback command will reverse lo/hi flag on a 8053 }
Port[pitCtrl] := $C8;
RdbStatus1 := Port[pitTimer2];
RdbCount1 := Port[pitTimer2];
RdbCount1 := (Port[pitTimer2] shl 8) + RdbCount1;
i := GetPitValue(2);
{ Read again to fix hi/lo flag }
Port[pitCtrl] := $C8;
RdbStatus2 := Port[pitTimer2];
RdbCount2 := Port[pitTimer2];
RdbCount2 := (Port[pitTimer2] shl 8) + RdbCount2;
j := GetPitValue (2);
if (RdbStatus1 <> expCntStatus) and (RdbStatus2 <> expCntStatus) and
(i = backwards) and (j = TestValue)
then PitType := pit8253
else if (RdbStatus1 = expCntStatus) and (RdbStatus2 = expCntStatus) and
(i = TestValue) and (j = TestValue)
then PitType := pit8254;
GotType:
EnableInterrupts;
GetPitType := PitType;
end;
{-------------------------- Unit initialization code -----------------------}
type
PObject = ^TObject;
TObject = object
destructor Done; virtual;
end;
destructor TObject.Done;
begin
end;
function DisposeVolume(V: PVolumeInfo): PVolumeInfo;
begin
DisposeVolume := nil;
if V = nil
then Exit;
DisposeVolume := V^.Next;
if V^.VmtOffset <> 0
then Dispose(PObject(V), Done)
else begin
DisposeStr(PString(V^.NetName));
Dispose(V);
end;
end;
function DosInit: Boolean;
var
Regs: TRegisters;
begin
DosInit := true;
if DosBufSize <> DosBuf.Size then
begin
FreeDosMem(DosBuf); { Release any previous DOS transfer buffer }
DosInit := GetDosMem(DosBuf, DosBufSize); { Allocate DOS xfer buffer }
FillChar(DosBuf.Buf^, DosBuf.Size, 0);
ClearRegs(Regs);
Regs.AH := $2F; { MsDos - Get Disk Transfer Address }
MsDos(Regs);
SaveDTA := Ptr(Regs.ES, Regs.BX);
Regs.AH := $1A; { MsDos - Set Disk Transfer Address }
Regs.DS := DosBuf.RealSeg; { DTA = DosBuf.RealSeg:$0000 }
Regs.DX := 0;
MsDos(Regs);
end;
end;
procedure DosDone;
var
Regs: TRegisters;
V : PVolumeInfo;
begin
V := VolumeList; { Destroy all volume info records }
repeat
V := DisposeVolume(V);
until V = nil;
VolumeList := nil;
if SaveDTA <> nil then { Restore DTA to previous address }
begin
ClearRegs(Regs);
Regs.AH := $1A; { MsDos - Set Disk Transfer Address }
Regs.DS := PtrRec(SaveDTA).Seg;
Regs.DX := PtrRec(SaveDTA).Ofs;
MsDos(Regs);
end;
FreeDosMem(DosBuf); { Release DOS transfer buffer }
end;
{---------------------------------------------------------------------------}
{ }
{ System Unit hooking code }
{ }
{---------------------------------------------------------------------------}
type
TPatchCode = record
OpCode: Byte;
Addr : Pointer;
end;
TPatch = record
Old: Pointer;
New: Pointer;
end;
PatchType = (paMkDir, paRmDir, paChDir, paGetDir, paFRewrite, paErase,
paRename, paAssignText, paAssignFile, paFClose
{$ifndef TurboDos},
paBlockWrite, paBlockRead, paFileSeek, paFilePos,
paFileRead, paFileWrite
{$endif !TurboDos}
);
const
Patches: array[PatchType] of TPatch = (
(Old: nil; New: @LfnMkDir),
(Old: nil; New: @LfnRmDir),
(Old: nil; New: @GDos.ChDir),
(Old: nil; New: @LfnGetDir),
(Old: Ptr(0,6); New: @LfnOpenFile), { For ResetFile and ReWriteFile }
(Old: nil; New: @LfnErase),
(Old: Ptr(0,2); New: @LfnRename),
(Old: Ptr(0,4); New: @LfnAssignText),
(Old: Ptr(0,4); New: @LfnAssignFile),
(Old: nil; New: @LfnCloseFile)
{$ifndef TurboDos},
(Old: nil; New: @BlockWrite),
(Old: nil; New: @BlockRead),
(Old: nil; New: @SeekFile),
(Old: nil; New: @FilePos),
(Old: nil; New: @LfnFileRead),
(Old: nil; New: @LfnFileWrite)
{$endif !TurboDos}
);
procedure HookSystemCalls;
label
lMkDir, lRmDir, lChDir, lGetDir, lFRewrite, lErase, lRename, lAssignText,
lAssignFile, lFClose,
{$ifndef TurboDos}
lBlockWrite, lBlockRead, lSeekFile, lFilePos, lFileRead, lFileWrite,
{$endif !TurboDos}
Start;
var
S : String[1];
F : file;
TF: file of byte absolute F;
T : Text;
W : Word;
B : Byte absolute W;
L : Longint;
P : PChar;
i : PatchType;
Patch: TPatchCode;
Selector: Word;
begin
goto Start;
{ These standard functions are replaced because they are not LFN-capable }
{ and/or they need a DOS extender to work. }
System.MkDir(''); lMkDir:
System.RmDir(''); lRmDir:
System.ChDir(''); lChDir:
System.GetDir(0, S); lGetDir:
System.Rewrite(F,1); lFRewrite:
System.Erase(F); lErase:
System.Rename(F, S); lRename:
System.Assign(T, S); lAssignText:
System.Assign(F, S); lAssignFile:
System.Close(F); lFClose:
{$ifndef TurboDos}
{ These standard functions are replaced because they need a DOS Extender }
System.BlockWrite(F, W, 1, W); lBlockWrite:
System.BlockRead(F, W, 1, W); lBlockRead:
System.Seek(F, 0); lSeekFile:
L := System.FilePos(F); lFilePos:
System.Read(TF, B); lFileRead:
System.Write(TF, B); lFileWrite:
{$endif !TurboDos}
asm
@@StorePtr:
shl si,3 { SI = Index into Patches Array }
sub bx,4 { Position of the CALL FAR }
lea di,Patches[si] { DS:DI = @Patches[BX].Old }
mov ax,cs:[bx] { AX = Offset of the CALL FAR address }
add [di],ax { Add adjust and Store ofs(SystemFuncXXXX)}
mov ax,cs:[bx+2] { Store segment of System function }
mov [di+2],ax
retn
Start:
mov si,paMkDir
mov bx,offset lMkDir
call @@StorePtr
mov si,paRmDir
mov bx,offset lRmDir
call @@StorePtr
mov si,paChDir
mov bx,offset lChDir
call @@StorePtr
mov si,paGetDir
mov bx,offset lGetDir
call @@StorePtr
mov si,paFRewrite
mov bx,offset lFRewrite
call @@StorePtr
mov si,paErase
mov bx,offset lErase
call @@StorePtr
mov si,paRename
mov bx,offset lRename
call @@StorePtr
mov si,paAssignText
mov bx,offset lAssignText
call @@StorePtr
mov si,paAssignFile
mov bx,offset lAssignFile
call @@StorePtr
mov si,paFClose
mov bx,offset lFClose
call @@StorePtr
{$ifndef TurboDos}
mov si,paBlockWrite
mov bx,offset lBlockWrite
call @@StorePtr
mov si,paBlockread
mov bx,offset lBlockRead
call @@StorePtr
mov si,paFileSeek
mov bx,offset lSeekFile
call @@StorePtr
mov si,paFilePos
mov bx,offset lFilePos - 8
call @@StorePtr
mov si,paFileRead
mov bx,offset lFileRead - 3
call @@StorePtr
mov si,paFileWrite
mov bx,offset lFileWrite - 3
call @@StorePtr
{$endif !TurboDos}
end;
Patch.OpCode := $EA; { Machine opcode for JMP FAR }
{$ifdef Windows}
Selector := AllocSelector(0);
{$endif}
for i := Low(Patches) to High(Patches) do
begin
Patch.Addr := Patches[i].New; { Where we want to jump to }
{$ifdef DPMI}
Inc(PtrRec(Patches[i].Old).Seg, { Where we want to jump from, }
SelectorInc); { converted to a DATA selector }
{$endif DPMI}
{$ifdef Windows}
ChangeSelector(CSeg, Selector); { Ensure a read/write selector }
PtrRec(Patches[i].Old).Seg := Selector;
{$endif Windows}
Move(Patch, Patches[i].Old^, SizeOf(Patch)); { Insert the "hook" }
end;
{$ifdef Windows}
FreeSelector(Selector);
{$endif}
Assign(OutPut, ''); { Make the standard files use }
Rewrite(Output); { the GDOS "Text" functions }
Assign(Input, '');
Reset(Input);
end;
{ Return the uppercase version of the character passed in Ch using the }
{ Country dependant information upper case map function }
function CaseMapUpCh(InCh:Char):Char; assembler;
{$ifndef DPMI}
asm
mov al,[InCh]
call [DosCountry.UpCase]
end;
{$else !DPMI}
var
Regs: TRegisters;
asm
push ss
lea di,Regs
pop es { ES:DI = @RealRegs }
cld
mov cx,type TRegisters/2 { Zero all Registers }
xor ax,ax
mov dx,[word ptr DosCountry.UpCase]
rep stosw
mov bx,[word ptr DosCountry.UpCase+2] { CX is now 0 }
mov [Regs.&IP],dx { Regs.CS:IP = Country.UpCase }
mov al,[InCh]
mov [Regs.&CS],bx
mov [Regs.&AX],ax { Regs.AL = InCh }
lea di,Regs { ES:DI = @Regs }
xor bx,bx { BH and CX must equal 0 }
mov ax,dpmiCallRealFar { Simulate real-mode far call }
int intDPMI
mov ax,[Regs.&AX]
end;
{$endif !DPMI}
function InitCountry: Boolean;
var
L,U : Char;
Regs : TRegisters;
Buf : TDosBuf;
CDI : ^TDosCountry absolute Buf;
begin
InitCountry := False;
if not GetDosMem(Buf, SizeOf(TDosCountry))
then Exit;
ClearRegs(Regs);
Regs.AX := $3800; { DOS - Get country dependant information }
Regs.DS := Buf.RealSeg; { DOS function $3800 requires the address of a }
Regs.DX := Buf.RealOfs; { TDosCountry structure to be passed in DS:DX }
MsDos(Regs); { Call DOS function $3800 }
DosCountry := CDI^; { Copy country info to permanent buffer. }
DosCountry.CountryCode := Regs.BX;
FreeDosMem(Buf);
if not Assigned(DosCountry.UpCase)
then Exit;
DosCountry.CurrencyStr := NullToPas(@DosCountry.CurrencyStr);
for L := #128 to #255 do
begin
U := CaseMapUpCh(L); { Get the uppercase equivalent of L }
LoToUpTbl[L] := U; { Store it in the LowerCase->UpperCase table }
if U >= #128 { Store the inverse in the Upper->Lower table }
then UpToLoTbl[U] := L;{ if it's an extended uppercase character }
if L = U { add all non-lowercase extended chars to }
then Include(DosChars, U); { set of valid DOS 8.3 filename char set }
end;
end;
procedure CalcExeDir; { Calculate the drive and directory of the program. }
{ The EXE path is always terminated with a "\". }
begin { This procedure means that GV must run on DOS 3.0+ }
FSplit(FExpand(ParamStr(0), fcFileName + fcCasePreserve),{ Use FExpand to }
ExeDir, ExeName, ExeExt); { convert short name components }
AddDirSep(ExeDir); { to their true Long file name }
end;
procedure CheckForLongNames; { Determine LFN support by checking the volume }
var { attributes first of Drive C:, then trying }
C : Char; { A: if C: doesn't exist. This should prove }
P : Char; { reliable on all PC's, even those where there }
Regs : TRegisters; { are no functioning hard or network drives. }
LfnInfo: PLfnRootVolInfo absolute DosBuf;
SaveBuf: TLfnRootVolInfo;
begin
CheckDosBuf(SaveBuf, SizeOf(SaveBuf));
C := 'E';
repeat
Dec(C, 2);
ClearRegs(Regs);
Regs.Flags := fCarry;
Regs.AX := $71A0; { LFN - Get Volume Information }
Regs.CX := SizeOf(TFileSysName); { CX = SizeOf(LfnInfo.FileSysName) }
Regs.DX := SizeOf(TFileSysName); { DS:DX = @LfnInfo.RootName }
Regs.ES := DosBuf.RealSeg; { ES:DI = @LfnInfo.FileSysName }
Regs.DS := Regs.ES;
PasToNull(C + ':\', @LfnInfo^.RootName);
if DriveValid(C) then
begin
MsDos(Regs);
VFat := (Regs.Flags and fCarry = 0) and
(Regs.BX and vaDosLongNames <> 0);
Break;
end;
until C = 'A';
RestoreDosBuf(SaveBuf, SizeOf(SaveBuf));
end;
function Win9xRunning: Boolean; assembler;
asm
mov ax,$160A
int 2Fh
or ax,ax
je @@Yes
@@No: mov al,false
jmp @@Exit
@@Yes: cmp bx,$0395
jl @@No
mov al,true
@@Exit:
end;
{$ifdef DPMI}
procedure InitHardInt; assembler; { Initialize the hardware interrupt number}
asm { to interrupt vector lookup table. }
mov ax,dpmiGetInfo
int intDPMI { Get primary and secondary hardware interrupt }
xchg dl,dh { DL = Primary offset DH = Secondary offset }
mov [word ptr MasterPicBase],dx { offset values. }
end;
{$else DPMI}
function NoXmsDriver: WordBool; far; assembler;
asm
xor ax,ax { Xms function failed }
mov bl,$80 { Xms function not supported }
end;
{= XMSDetect ============================================================}
{ Determines whether XMS is present. If so initialises the driver entry }
{ point variable, and various state flags & variables. This MUST be }
{ called before any other XMS routine. After calling XMSInitHeap, }
{ check the XMSinstalled flag to see whether XMS is available. }
{========================================================================}
procedure XmsDetect; assembler;
asm
mov ax,$4300 { Perform standard test for XMS driver }
int $2F
sub al,$80 { AL = 80h => XMS present }
jnz @@NoXMS { No XMS, jump out of asm block }
mov ax,$4310 { XMS present, so get driver entry point }
int $2F
mov [XmsFunc].Word[0],bx
mov [XmsFunc].Word[2],es
mov [XmsInstalled],true
mov ah,xmsGetVersion
call [XmsFunc]
mov [XmsVersion],ax
{$ifdef XMS30}
cmp ax,$300 { Must be version 3.00 compliant }
jae @@Exit { It is, so XMS functions are supported }
{$else XMS30}
jmp @@Exit
{$endif XMS30}
@@NoXMS: lea ax,NoXmsDriver
mov [XmsFunc].Word[0],ax
mov [XmsFunc].Word[2],cs
mov [XmsInstalled],false
@@Exit:
end;
{$endif DPMI}
var
OldExitProc: Pointer;
procedure GDosExitProc; far;
var
E,J,K : Integer;
Suffix: String[3];
begin
ExitProc := OldExitProc;
{ Erase all auto-erase temporary files created by this application }
K := 0;
E := DosError;
while TempNums <> [] do
begin
if K in TempNums then
begin
Exclude(TempNums, K);
Str(K, Suffix); { Create numeric suffix }
for J := Length(Suffix)+1 to High(Suffix) do
Suffix := '0' + Suffix;
FErase(TempDir^ + TempPrefix + Suffix + '.TMP');
end;
Inc(K);
end;
DosError := E;
UnHookAll; { Unhook user installed interrupts and realmode callbacks }
DosDone; { Dispose of heap memory used by GDos }
DisposeStr(PString(TempDir));
{$ifdef MsDos}
{ Releases the XMS block used by the overlay file allocated }
if OvrXmsHandle <> 0
then FreeXms(OvrXmsHandle);
OvrXmsHandle := 0;
{$endif MsDos}
end;
procedure Proc386; assembler;
asm
db 73,13,10,'This program requires a 386 or later processor',13,10
db 'Program Terminated.',13,10,13,10
end;
procedure Dos33; assembler;
asm
db 66,13,10,'This program requires Dos 3.3 or later.',13,10
db 'Program Terminated.',13,10,13,10
end;
begin { GDos startup code }
if Test8086 < 2 then { The GDos unit requires a 386 or better }
begin
PrintStr(PString(@Proc386)^);
RunError(254); { Unsupported CPU }
end;
asm
mov ax,$3000 { Get the version of DOS that's running }
int intDos
xchg al,ah
mov [DosVersion],ax
end;
if DosVersion < $0303 then { O/S must be DOS must be 3.3 or higher }
begin
PrintStr(PString(@Dos33)^);
RunError(255); { Unsupported operating system }
end;
InitCountry; { Initialize county-dependant information }
CreateVolume := StdCreateVolume;{ Assign default TVolumeInfo creator }
{$ifdef LongNames}
CheckForLongNames; { Check for an LFN-capable O/S (sets VFAT)}
HookSystemCalls; { Hook System unit calls for LFN & DPMI }
{$else LongNames}
{$ifndef TurboDos}
HookSystemCalls; { Hook System unit calls for DPMI reasons }
{$endif TurboDos}
{$endif LongNames}
CalcExeDir; { Define exe directory, name and extension}
{$ifdef DPMI}
InitHardInt; { Set MasterPicBase and SlavePicBase }
{$else DPMI}
XmsDetect;
{$endif DPMI}
OldExitProc := ExitProc; { Chain GDos.ExitProc to exit chain }
ExitProc := @GDosExitProc;
DosDone; { Deallocate all heap memory used and }
end. { the exe's volume information record. }