lazarus-ccr/components/fpspreadsheet/source/common/uvirtuallayer_ole_helpers.pas

1349 lines
42 KiB
ObjectPascal

{
uvirtuallayer_ole_helpers.pas
Part of "uvirtuallayer_ole".
Supports most of the logic of Indirect FAT and Double Indirect FAT accesses for
OLE files.
Presented as an unit to hide the class and definitions when using OLE virtual
layer. This class is not intented to general use, only by TVirtualLayer_OLE.
AUTHORS: José Mejuto Porral
}
unit uvirtuallayer_ole_helpers;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, uvirtuallayer_ole_types;
type
TFATCacheItem=record
Sector: SECT;
Cache: PSECT;
Dirty: Boolean;
end;
TFATStreamContext=record
FATFirstIndex: SizeUint;
FATIndex: SizeUint;
Position: int64;
Size: int64;
Dirty: Boolean;
AccessMode: WORD;
MiniStream: TMemoryStream;
end;
TWCBFOpenStream=record
Handle: SID;
Context: TFATStreamContext;
end;
{ TMaskFile }
TMaskFile=class
private
protected
FMask: UTF8String;
public
function Matches(const AFileName: UTF8String): Boolean;
Constructor Create(const AMask: UTF8String);
end;
{ TFATIndirect }
TFATIndirect=class
private
procedure InitializeMiniDataStream();
protected
FDirtyMedia: Boolean;
FFATCache: TFATCacheItem;
FMiscSectorBuffer: PBYTE;
FStream: TStream;
FHeader: TWCBFStructuredStorageHeader;
FSectorSize: SizeUint;
FEntriesShort: SizeUint;
FDIFArray: array of PSECT;
FFATEntriesPerSect: SizeUint;
FNeedEndianChange: Boolean;
FHeaderLength: SizeUint;
procedure FlushCaches();
function iLEToN(const AValue: DWORD): DWORD;
function iNToLE(const AValue: DWORD): DWORD;
procedure iDIFFATLEToN(var ADIF: PSECT);
procedure iDIFFATNToLE(var ADIF: PSECT);
procedure iHeaderLEToN(var AHeader: TWCBFStructuredStorageHeader);
procedure iHeaderNToLE(var AHeader: TWCBFStructuredStorageHeader);
function ComposeFATEntry(const ADIF,AFATInDIF,AOffSetInFAT: SECT): SECT; inline;
procedure DecomposeFATEntry(const AFATEntry: SECT; out DIF,FATInDIF,OffsetInFAT: SECT); inline;
function WriteToMiniFAT(var AContext: TFATStreamContext; const AStream: TStream): DWORD;
function ReadFromMiniFAT(const AContext: TFATStreamContext; const AStream: TStream): DWORD;
procedure ReadSector(const ASector: SECT; const ABuffer: Pointer); inline;
procedure WriteSector(const ASector: SECT; const ABuffer: Pointer); inline;
procedure ReadFATSector(const ASector: SECT);
procedure WriteFATSector(const ASector: SECT;const ABuffer: PSECT); overload;
procedure WriteFATSector(const ASector: SECT); overload;
function IsReservedValue(const ASectorValue: SECT): Boolean;inline;
function LoadDIFArray(): Boolean;
function UnloadDIFArray(): Boolean;
function SectorAbsolute(const ASector: SECT): int64; inline;
function FindFreeFAT(const ABuffer: PSECT; const AStartIndex: SizeUint): SizeUint;
function AllocateNewSector(const AStartingFATIndex: SECT; const ALinkFrom: SECT;const ALinkTo: SECT=SECT_ENDOFCHAIN): SizeUInt;
function GetStreamSizeInSectors(const AContext: TFATStreamContext): int64;
function GetFATOffsetForPosition(const APosition: int64): SizeUint; inline;
function GetFATRemainForPosition(const APosition: int64): SizeUint; inline;
function ReadFATEntryValue(const AFATEntryIndex: SizeUint): SECT; inline;
procedure WriteFATEntryValue(const AFATEntryIndex: SizeUint;const ALinkTo: SECT); inline;
procedure ResynchronizePosition(var AContext: TFATStreamContext);
procedure ResynchronizePositionWithAllocation(var AContext: TFATStreamContext);
function FollowFATLinkage(const AStartFATEntryIndex: SizeUint; const ABytes: int64): SECT;
procedure Format(var AHeader: TWCBFStructuredStorageHeader); virtual;
public
DirectoryContext: TFATStreamContext;
MiniFATContext: TFATStreamContext;
MiniFATDataContext: TFATStreamContext;
property DirtyMedia: Boolean read FDirtyMedia write FDirtyMedia;
property HeaderLength: SizeUint read FHeaderLength;
function OpenStream(const AFirstFATSector: DWORD; const ASize: int64; const AAccessMode: WORD): TFATStreamContext;
procedure CloseStream(var AContext: TFATStreamContext);
function ReadData(var AContext: TFATStreamContext; const ABuffer: PBYTE; const ASize: SizeInt): SizeInt;
function WriteData(var AContext: TFATStreamContext; const ABuffer: PBYTE; const ASize: SizeInt): SizeInt;
function StreamSeekPosition(var AContext: TFATStreamContext; const AOffset: int64; const AOrigin: TSeekOrigin): int64;
procedure ResetMiniFATLinkage(const AStartFATEntryIndex: SizeUint; const ANewFATValue: SECT);
procedure ResetFATLinkage(const AStartFATEntryIndex: SizeUint; const ANewFATValue: SECT);
function IsSizeInMiniFAT(const ASize: int64): Boolean;
function Initialize(const ACreate: Boolean=false): Boolean;
Constructor Create(const AOLEStream: TStream);
Destructor Destroy; override;
end;
implementation
function MatchesMask(What, Mask: string): boolean; forward;
procedure TFATIndirect.InitializeMiniDataStream();
var
RootDir: TWCBFStructuredStorageDirectoryEntry;
begin
//Read the SID 0, RootEntry, or R or whichever name it uses.
RootDir._cb:=0;
FillByte(RootDir,sizeof(RootDir),0);
StreamSeekPosition(DirectoryContext,0,soBeginning);
ReadData(DirectoryContext,@RootDir,SizeOf(RootDir));
StreamSeekPosition(DirectoryContext,0,soBeginning);
MiniFATDataContext.FATFirstIndex:=RootDir._sectStart;
MiniFATDataContext.FATIndex:=RootDir._sectStart;
MiniFATDataContext.Size:=RootDir._ulSize;
MiniFATDataContext.MiniStream:=nil;
end;
procedure TFATIndirect.FlushCaches();
begin
if FFATCache.Dirty then begin
WriteSector(FFATCache.Sector,FFATCache.Cache);
FFATCache.Dirty:=false;
end;
end;
function TFATIndirect.iLEToN(const AValue: DWORD): DWORD;
begin
if FNeedEndianChange then begin
Result:=LEToN(AValue);
end else begin
Result:=AValue;
end;
end;
function TFATIndirect.iNToLE(const AValue: DWORD): DWORD;
begin
if FNeedEndianChange then begin
Result:=NToLE(AValue);
end else begin
Result:=AValue;
end;
end;
procedure TFATIndirect.iDIFFATLEToN(var ADIF: PSECT);
var
j: SizeUint;
begin
if not FNeedEndianChange then Exit;
for j := 0 to FFATEntriesPerSect-1 do begin
ADIF[j]:=LEToN(ADIF[j]);
end;
end;
procedure TFATIndirect.iDIFFATNToLE(var ADIF: PSECT);
var
j: SizeUint;
begin
if not FNeedEndianChange then Exit;
for j := 0 to FFATEntriesPerSect-1 do begin
ADIF[j]:=NToLE(ADIF[j]);
end;
end;
procedure TFATIndirect.iHeaderLEToN(var AHeader: TWCBFStructuredStorageHeader);
var
j: SizeUint;
begin
if Not FNeedEndianChange then exit;
AHeader._uMinorVersion:=iLEToN(FHeader._uMinorVersion);
AHeader._uDllVersion:=iLEToN(FHeader._uDllVersion);
AHeader._uSectorShift:=iLEToN(FHeader._uSectorShift);
AHeader._uMiniSectorShift:=iLEToN(FHeader._uMiniSectorShift);
AHeader._csectFat:=iLEToN(FHeader._csectFat);
AHeader._sectDirStart:=iLEToN(FHeader._sectDirStart);
AHeader._ulMiniSectorCutoff:=iLEToN(FHeader._ulMiniSectorCutoff);
AHeader._sectMiniFatStart:=iLEToN(FHeader._sectMiniFatStart);
AHeader._csectMiniFat:=iLEToN(FHeader._csectMiniFat);
AHeader._sectDifStart:=iLEToN(FHeader._sectDifStart);
AHeader._csectDif:=iLEToN(FHeader._csectDif);
for j := Low(AHeader._sectFat) to High(AHeader._sectFat) do begin
AHeader._sectFat[j]:=iLEToN(AHeader._sectFat[j]);
end;
end;
procedure TFATIndirect.iHeaderNToLE(var AHeader: TWCBFStructuredStorageHeader);
var
j: SizeUint;
begin
if Not FNeedEndianChange then exit;
AHeader._uMinorVersion:=iNToLE(FHeader._uMinorVersion);
AHeader._uDllVersion:=iNToLE(FHeader._uDllVersion);
AHeader._uSectorShift:=iNToLE(FHeader._uSectorShift);
AHeader._uMiniSectorShift:=iNToLE(FHeader._uMiniSectorShift);
AHeader._csectFat:=iNToLE(FHeader._csectFat);
AHeader._sectDirStart:=iNToLE(FHeader._sectDirStart);
AHeader._ulMiniSectorCutoff:=iNToLE(FHeader._ulMiniSectorCutoff);
AHeader._sectMiniFatStart:=iNToLE(FHeader._sectMiniFatStart);
AHeader._csectMiniFat:=iNToLE(FHeader._csectMiniFat);
AHeader._sectDifStart:=iNToLE(FHeader._sectDifStart);
AHeader._csectDif:=iNToLE(FHeader._csectDif);
for j := Low(AHeader._sectFat) to High(AHeader._sectFat) do begin
AHeader._sectFat[j]:=iNToLE(AHeader._sectFat[j]);
end;
end;
function TFATIndirect.ComposeFATEntry(const ADIF, AFATInDIF, AOffSetInFAT: SECT
): SECT;
begin
if ADIF=0 then begin
Result:=AFATInDIF*FFATEntriesPerSect+AOffSetInFAT;
end else begin
Result:=FEntriesShort+
int64((ADIF-1))*((FFATEntriesPerSect-1)*FFATEntriesPerSect)+
int64(AFATInDIF)*FFATEntriesPerSect+
AOffSetInFAT;
end;
end;
procedure TFATIndirect.DecomposeFATEntry(const AFATEntry: SECT; out DIF,
FATInDIF, OffsetInFAT: SECT);
var
TmpEntry: SECT;
begin
if AFATEntry<FEntriesShort then begin
DIF:=0;
FATInDIF:=AFATEntry div FFATEntriesPerSect;
OffsetInFAT:=AFATEntry mod FFATEntriesPerSect;
end else begin
TmpEntry:=AFATEntry-FEntriesShort;
DIF:=TmpEntry div ((FFATEntriesPerSect-1)*FFATEntriesPerSect);
TmpEntry:=TmpEntry-DIF*((FFATEntriesPerSect-1)*FFATEntriesPerSect);
FATInDIF:=TmpEntry div FFATEntriesPerSect;
OffsetInFAT:=TmpEntry mod FFATEntriesPerSect;
DIF:=DIF+1; //+1 the removed by substract FEntriesShort
end;
end;
procedure TFATIndirect.ResetMiniFATLinkage(const AStartFATEntryIndex: SizeUint;
const ANewFATValue: SECT);
var
NextFAT: SECT;
MiniFATBuffer: PSECT;
Index,IndexLimit: SECT;
begin
MiniFATBuffer:=nil;
GetMem(MiniFATBuffer,MiniFATContext.Size);
MiniFATContext.Position:=0;
ReadData(MiniFATContext,PBYTE(MiniFATBuffer),MiniFATContext.Size);
Index:=0;
IndexLimit:=MiniFATContext.Size div BYTES_PER_FAT_ENTRY;
NextFAT:=AStartFATEntryIndex;
while not IsReservedValue(NextFAT) do begin
Index:=MiniFATBuffer[NextFAT];
if Index>=IndexLimit Then begin
Raise Exception.Create('MiniFAT internal structure damaged');
end;
MiniFATBuffer[NextFAT]:=ANewFATValue;
NextFAT:=Index;
end;
StreamSeekPosition(MiniFATContext,0,soBeginning);
WriteData(MiniFATContext,PBYTE(MiniFATBuffer),IndexLimit*BYTES_PER_FAT_ENTRY);
Freemem(MiniFATBuffer);
end;
function TFATIndirect.WriteToMiniFAT(var AContext: TFATStreamContext;
const AStream: TStream): DWORD;
var
MiniSectorSize: DWORD;
MiniEntries: DWORD;
FATArray: array of SECT;
k: SizeUint;
OldPos: int64;
function FindFreeMiniFATArray(const AEntries: DWORD): SECT;
var
Pending: DWORD;
MiniFATBuffer: PSECT;
Index,IndexLimit: SECT;
FATEntries: SizeUint;
j: integer;
begin
Pending:=AEntries;
SetLength(FATArray,0);
MiniFATBuffer:=nil;
GetMem(MiniFATBuffer,MiniFATContext.Size);
MiniFATContext.Position:=0;
ReadData(MiniFATContext,PBYTE(MiniFATBuffer),MiniFATContext.Size);
FATEntries:=0;
Index:=0;
IndexLimit:=MiniFATContext.Size div BYTES_PER_FAT_ENTRY;
while Pending>0 do begin
while (Pending>0) and (Index<IndexLimit) do begin
if MiniFATBuffer[Index]=SECT_FREESECT then begin
SetLength(FATArray,FATEntries+1);
FATArray[FATEntries]:=Index;
if Index>=FHeader._csectMiniFat then begin
FHeader._csectMiniFat:=Index+1;
end;
inc(FATEntries);
dec(Pending);
end;
Inc(Index);
end;
if (Pending>0) then begin
//Allocate a new FAT sector...
MiniFATBuffer:=ReAllocMem(MiniFATBuffer,(IndexLimit+FFATEntriesPerSect)*BYTES_PER_FAT_ENTRY);
IndexLimit:=IndexLimit+FFATEntriesPerSect;
for j := Index to IndexLimit-1 do begin
MiniFATBuffer[j]:=SECT_FREESECT;
end;
end;
end;
//Link the sectors....
for j := 0 to High(FATArray)-1 do begin
MiniFATBuffer[FATArray[j]]:=FATArray[j+1];
end;
//And mark the end
MiniFATBuffer[FATArray[High(FATArray)]]:=SECT_ENDOFCHAIN;
StreamSeekPosition(MiniFATContext,0,soBeginning);
WriteData(MiniFATContext,PBYTE(MiniFATBuffer),IndexLimit*BYTES_PER_FAT_ENTRY);
Freemem(MiniFATBuffer);
Result:=FATArray[0];
end;
begin
if AStream.Size=0 then begin
Result:=0;
Exit;
end;
MiniSectorSize:= 1 shl FHeader._uMiniSectorShift;
OldPos:=AStream.Position;
AStream.Position:=0;
MiniEntries:=AContext.Size div MiniSectorSize;
if AContext.Size mod MiniSectorSize>0 then begin
inc(MiniEntries);
end;
FindFreeMiniFATArray(MiniEntries);
for k := 0 to MiniEntries-1 do begin
StreamSeekPosition(MiniFATDataContext,int64(FATArray[k])*MiniSectorSize,soBeginning);
AStream.Read(FMiscSectorBuffer^,MiniSectorSize);
WriteData(MiniFATDataContext,FMiscSectorBuffer,MiniSectorSize);
end;
AContext.FATFirstIndex:=FATArray[0];
AContext.FATIndex:=SECT_FREESECT;
AStream.Position:=OldPos;
end;
function TFATIndirect.ReadFromMiniFAT(const AContext: TFATStreamContext;
const AStream: TStream): DWORD;
var
NextSector: SECT;
MiniSectorSize: DWORD;
ReadBytes,ThisRead: SizeUInt;
EffectiveRead: SizeUint;
begin
If AContext.Size=0 then begin
Result:=0;
Exit;
end;
MiniSectorSize:= 1 shl FHeader._uMiniSectorShift;
AStream.Position:=0;
AStream.Size:=0;
ReadBytes:=AContext.Size;
NextSector:=AContext.FATFirstIndex;
while not IsReservedValue(NextSector) and (ReadBytes>0) do begin
StreamSeekPosition(MiniFATDataContext,Int64(NextSector)*MiniSectorSize,soBeginning);
if ReadBytes>MiniSectorSize then begin
ThisRead:=MiniSectorSize;
end else begin
ThisRead:=ReadBytes;
end;
EffectiveRead:=ReadData(MiniFATDataContext,FMiscSectorBuffer,ThisRead);
AStream.Write(FMiscSectorBuffer^,EffectiveRead);
StreamSeekPosition(MiniFATContext,int64(NextSector)*BYTES_PER_FAT_ENTRY,soBeginning);
ReadData(MiniFATContext,@NextSector,BYTES_PER_FAT_ENTRY);
Dec(ReadBytes,EffectiveRead);
if ThisRead<>EffectiveRead then exit;
end;
AStream.Size:=AContext.Size-ReadBytes;
Result:=AStream.Size;
end;
procedure TFATIndirect.ReadSector(const ASector: SECT; const ABuffer: Pointer);
begin
FStream.Position:=int64(ASector) * FSectorSize + FHeaderLength;
FStream.ReadBuffer(PBYTE(ABuffer)^,FSectorSize);
end;
procedure TFATIndirect.WriteSector(const ASector: SECT; const ABuffer: Pointer);
begin
FStream.Position:=int64(ASector) * FSectorSize + FHeaderLength;
FStream.WriteBuffer(PBYTE(ABuffer)^,FSectorSize);
FDirtyMedia:=true;
end;
procedure TFATIndirect.ReadFATSector(const ASector: SECT);
begin
if ASector=FFATCache.Sector then exit;
if (FFATCache.Dirty) Then begin
//Write the dirty cached FAT sector
iDIFFATNToLE(FFATCache.Cache);
WriteSector(FFATCache.Sector,FFATCache.Cache);
end;
FFATCache.Sector:=ASector;
FFATCache.Dirty:=false;
ReadSector(ASector,FFATCache.Cache);
iDIFFATLEToN(FFATCache.Cache);
end;
procedure TFATIndirect.WriteFATSector(const ASector: SECT; const ABuffer: PSECT
);
begin
if ASector<>FFATCache.Sector then begin
if FFATCache.Dirty then begin
//Write the dirty cached FAT sector
if FHeader._csectFat<(FFATCache.Sector div FFATEntriesPerSect)+1 then begin
FHeader._csectFat:=(FFATCache.Sector div FFATEntriesPerSect)+1;
end;
iDIFFATNToLE(FFATCache.Cache);
WriteSector(FFATCache.Sector,FFATCache.Cache);
end;
end;
move(PBYTE(ABuffer)^,PBYTE(FFATCache.Cache)^,FSectorSize);
FFATCache.Sector:=ASector;
FFATCache.Dirty:=true;
end;
procedure TFATIndirect.WriteFATSector(const ASector: SECT);
begin
if ASector=FFATCache.Sector then begin
FFATCache.Dirty:=true;
end else begin
if FHeader._csectFat<(FFATCache.Sector div FFATEntriesPerSect)+1 then begin
FHeader._csectFat:=(FFATCache.Sector div FFATEntriesPerSect)+1;
end;
iDIFFATNToLE(FFATCache.Cache);
//Writes the data
WriteSector(FFATCache.Sector,FFATCache.Cache);
FFATCache.Dirty:=false;
end;
end;
function TFATIndirect.IsReservedValue(const ASectorValue: SECT): Boolean;
inline;
begin
if ASectorValue>=$FFFFFFF0 then
Result:=true
else
Result:=false;
end;
function TFATIndirect.LoadDIFArray(): Boolean;
var
NextDIF: SECT;
Index: SizeUint;
begin
SetLength(FDIFArray,1);
//The first DIF is an special one...
FDIFArray[0]:=PSECT(@FHeader._sectFat[0]);
NextDIF:=FHeader._sectDifStart;
Index:=0;
while not IsReservedValue(NextDIF) do begin
Inc(Index);
SetLength(FDIFArray,Index+1);
GetMem(FDIFArray[Index],FSectorSize);
ReadSector(NextDIF,FDIFArray[Index]);
iDIFFATLEToN(FDIFArray[Index]);
NextDIF:=FDIFArray[Index][FFATEntriesPerSect-1];
end;
Result:=true;
end;
function TFATIndirect.UnloadDIFArray(): Boolean;
var
NextDIF: SECT;
NextDIFC: SECT;
Index: SizeUint;
begin
if Length(FDIFArray)=0 then begin
//Nothing to be freed.
Result:=true;
exit;
end;
//The first DIF is an special one and do not need to be
//written as it is written with the header updates.
FDIFArray[0]:=PSECT(@FHeader._sectFat[0]);
//It is in memory in machine endian format.
NextDIF:=FHeader._sectDifStart;
Index:=0;
while not IsReservedValue(NextDIF) do begin
Inc(Index);
NextDIFC:=FDIFArray[Index][FFATEntriesPerSect-1];
if FDirtyMedia then begin
iDIFFATNToLE(FDIFArray[Index]);
WriteSector(NextDIF,FDIFArray[Index]);
end;
FreeMem(FDIFArray[Index]);
NextDIF:=NextDIFC;
end;
SetLength(FDIFArray,0);
Result:=true;
end;
function TFATIndirect.SectorAbsolute(const ASector: SECT): int64;
begin
Result:=int64(ASector)*FSectorSize+FHeaderLength;
end;
function TFATIndirect.FindFreeFAT(const ABuffer: PSECT; const AStartIndex: SizeUint): SizeUInt;
var
j: SizeUint;
begin
Result:=SECT_FREESECT;
for j := AStartIndex to FFATEntriesPerSect-1 do begin
if ABuffer[j]=SECT_FREESECT then begin
Result:=j;
break;
end;
end;
end;
function TFATIndirect.AllocateNewSector(const AStartingFATIndex: SECT;
const ALinkFrom: SECT; const ALinkTo: SECT): SizeUInt;
var
j: SizeUint;
Sector: SECT;
DIFEntries: SECT;
DIFIndex,FATInDIF,OffsetInFAT: SECT;
Index: SizeUint;
DIFData: PSECT;
procedure CreateEmptyFATSector(const ASector: SECT);
var
Buffer: PBYTE;
begin
inc(FHeader._csectFat); //increment the FAT sector counter
Buffer:=nil;
GetMem(Buffer,FSectorSize);
FillByte(Buffer^,FSectorSize,$FF);
WriteFATSector(ASector,PSECT(Buffer));
FreeMem(Buffer);
end;
begin
//DIF sectors points to FAT sectors where the allocation happends.
//The first DIF sector is an special one with 109 entries (fixed),
//without next DIF sector link and it is allocated in the header.
//------------
DecomposeFATEntry(AStartingFATIndex+1,DIFIndex,FATInDIF,OffsetInFAT);
if (AStartingFATIndex>0) and (SizeInt(DIFIndex)<=High(FDIFArray)) then begin
//Due speed reasons, check next sector and all the sectors
//in the same FAT, this improves linear write & read.
//DIF is the special header one ?
if DIFIndex=0 then begin
DIFEntries:=0;
end else begin
DIFEntries:=FSectorSize div BYTES_PER_FAT_ENTRY;
end;
DIFData:=FDIFArray[DIFIndex];
for j := 0 to 0 do begin
Sector:=DIFData[FATInDIF];
if Sector=SECT_FREESECT then begin
//Create a new FAT sector just here
Sector:=ComposeFATEntry(DIFIndex,FATInDIF,0);
CreateEmptyFATSector(Sector);
DIFData[FATInDIF]:=Sector;
WriteFATEntryValue(Sector,SECT_FATSECT);
end;
ReadFATSector(Sector);
if FFATCache.Cache[OffsetInFAT]=SECT_FREESECT then begin
//Found sector as Free sector...
FFATCache.Cache[OffsetInFAT]:=ALinkTo;
WriteFATSector(Sector);
Result:=ComposeFATEntry(DIFIndex,FATInDIF,OffsetInFAT);
end else begin
//Not found, so try the complete FAT sector
Index:=FindFreeFAT(FFATCache.Cache,0);
if Index<>SECT_FREESECT then begin
//Found sector as free sector
FFATCache.Cache[Index]:=ALinkTo;
WriteFATSector(Sector);
Result:=ComposeFATEntry(DIFIndex,FATInDIF,Index);
end else begin
Result:=SECT_FREESECT;
end;
end;
if Result<>SECT_FREESECT then begin
//Write the changes
if ALinkFrom<>SECT_FREESECT then begin
WriteFATEntryValue(ALinkFrom,Result);
end;
end else begin
Result:=AllocateNewSector(0,ALinkFrom,ALinkTo);
end;
end;
if Result<>SECT_FREESECT then exit;
end;
for DIFIndex := 0 to High(FDIFArray) do begin
if DIFIndex=0 then begin
DIFEntries:=Length(FHeader._sectFat);
end else begin
DIFEntries:=(FSectorSize div BYTES_PER_FAT_ENTRY)-1;
end;
FATInDIF:=0;
OffsetInFAT:=0;
DIFData:=FDIFArray[DIFIndex];
for FATInDIF := 0 to DIFEntries-1 do begin
Sector:=DIFData[FATInDIF];
if Sector=SECT_FREESECT then begin
//Create a new FAT sector just here
Sector:=ComposeFATEntry(DIFIndex,FATInDIF,0);
CreateEmptyFATSector(Sector);
DIFData[FATInDIF]:=Sector;
WriteFATEntryValue(Sector,SECT_FATSECT);
end;
ReadFATSector(Sector);
for OffsetInFAT := 0 to FFATEntriesPerSect-1 do begin
if FFATCache.Cache[OffsetInFAT]=SECT_FREESECT then begin
//Found sector as Free sector...
FFATCache.Cache[OffsetInFAT]:=ALinkTo;
WriteFATSector(Sector);
Result:=ComposeFATEntry(DIFIndex,FATInDIF,OffsetInFAT);
if Result<>SECT_FREESECT then begin
//Write the changes
if ALinkFrom<>SECT_FREESECT then begin
WriteFATEntryValue(ALinkFrom,Result);
end;
Exit;
end;
end;
end;
end;
end;
//There is no empty space in current FAT so, add a new DIF
DIFIndex:=High(FDIFArray)+1;
FATInDIF:=0;
OffsetInFAT:=0;
Sector:=ComposeFATEntry(DIFIndex,FATInDIF,OffsetInFAT);
if DIFIndex=1 then begin
FHeader._sectDifStart:=Sector
end;
//Empty free sector
GetMem(DIFData,FSectorSize); //This memblock will be freed by UnloadDIF
FillByte(PBYTE(DIFData)^,FSectorSize,$FF);
//With ENDOFCHAIN marker
DIFData[FFATEntriesPerSect-1]:=SECT_ENDOFCHAIN;
//Link in the previous DIF sector
FDIFArray[High(FDIFArray)][FFATEntriesPerSect-1]:=Sector;
//Update memory block
SetLength(FDIFArray,DIFIndex+1);
FDIFArray[DIFIndex]:=DIFData;
FDirtyMedia:=true; //Forces a rewrite of DIF sectors when destroying the class.
inc(FHeader._csectDif); //Increments the DIF counter.
//Create a new FAT sector just here
CreateEmptyFATSector(Sector+1);
DIFData[FATInDIF]:=Sector+1;
WriteFATEntryValue(Sector,SECT_DIFSECT);
WriteFATEntryValue(Sector+1,SECT_FATSECT);
Result:=AllocateNewSector(Sector+1,ALinkFrom,ALinkTo);
end;
function TFATIndirect.GetStreamSizeInSectors(const AContext: TFATStreamContext
): int64;
var
Sector,LastSector: SECT;
begin
Result:=0;
LastSector:=AContext.FATFirstIndex;
while (LastSector<$FFFFFFF0) do begin
Sector:=LastSector;
LastSector:=ReadFATEntryValue(Sector);
inc(Result);
end;
end;
function TFATIndirect.GetFATOffsetForPosition(const APosition: int64
): SizeUint;
begin
Result:=APosition mod FSectorSize;
end;
function TFATIndirect.GetFATRemainForPosition(const APosition: int64
): SizeUint;
begin
Result:=FSectorSize -(APosition mod FSectorSize);
end;
function TFATIndirect.ReadData(var AContext: TFATStreamContext;
const ABuffer: PBYTE; const ASize: SizeInt): SizeInt;
var
Position: int64;
FAT,NextFAT: SECT;
CanRead: SizeUint;
RemainData: int64;
TargetBuffer: PBYTE;
EffectiveRead: int64;
begin
if ASize<1 then begin
Result:=0;
Exit;
end;
if AContext.AccessMode=fmOpenWrite then begin
if (@AContext<>@DirectoryContext) and
(@AContext<>@MiniFATContext) and
(@AContext<>@MiniFATDataContext) then begin
Raise EStreamError.Create('Stream can not be read, open for write only');
end;
end;
if AContext.Position>=AContext.Size Then begin
//EOF condition, can not read nothing.
Result:=0;
Exit;
end;
if Assigned(AContext.MiniStream) then begin
if AContext.Size<FHeader._ulMiniSectorCutoff then begin
//Read it from MiniFAT
AContext.MiniStream.Position:=AContext.Position;
Result:=AContext.MiniStream.Read(ABuffer^,ASize);
AContext.Position:=AContext.MiniStream.Position;
Exit;
end;
end;
if AContext.FATIndex=SECT_FREESECT then begin
ResynchronizePosition(AContext);
end;
If AContext.FATIndex=SECT_FREESECT then begin
Result:=0;
Exit;
end;
Position:=AContext.Position;
if Position+ASize>AContext.Size then begin
RemainData:=AContext.Size-Position;
end else begin
RemainData:=ASize;
end;
TargetBuffer:=ABuffer;
while RemainData>0 do begin
FAT:=AContext.FATIndex;
CanRead:=GetFATRemainForPosition(Position);
if CanRead>RemainData then begin
CanRead:=RemainData;
end;
if CanRead+AContext.Position>=AContext.Size then begin
CanRead:=AContext.Size-AContext.Position;
end;
if IsReservedValue(FAT) then begin
if FAT<>SECT_ENDOFCHAIN then begin
Raise Exception.Create('Damaged stream');
end;
end;
if CanRead>0 then begin
FStream.Position:=SectorAbsolute(FAT)+GetFATOffsetForPosition(Position);
EffectiveRead:=FStream.Read(TargetBuffer^,CanRead);
dec(RemainData,EffectiveRead);
inc(Position,EffectiveRead);
inc(TargetBuffer,EffectiveRead);
if (EffectiveRead<>CanRead) Then begin
AContext.FATIndex:=SECT_FREESECT;
Result:=ASize-RemainData;
Exit;
end;
end;
AContext.Position:=Position;
if GetFATRemainForPosition(Position)=FSectorSize then begin
//The stream is in sector boundary
NextFAT:=ReadFATEntryValue(FAT);
if NextFAT<>SECT_ENDOFCHAIN then begin
AContext.FATIndex:=NextFAT;
end else begin
AContext.FATIndex:=SECT_FREESECT; //Force resynchronization
Result:=ASize-RemainData;
Exit;
end;
end;
end;
Result:=ASize;
end;
function TFATIndirect.WriteData(var AContext: TFATStreamContext;
const ABuffer: PBYTE; const ASize: SizeInt): SizeInt;
var
Position: int64;
FAT,NextFAT: SECT;
CanWrite: SizeUint;
RemainData: int64;
SourceBuffer: PBYTE;
EffectiveWrite: int64;
procedure MoveMiniStreamToRegularStream();
var
ContextBackup: TFATStreamContext;
begin
ContextBackup:=AContext;
AContext.MiniStream:=nil;
//Delete here the mini stream if needed.
if not IsReservedValue(AContext.FATFirstIndex) then begin
ResetMiniFATLinkage(AContext.FATFirstIndex,SECT_FREESECT);
end;
AContext.FATFirstIndex:=SECT_ENDOFCHAIN;
AContext.FATIndex:=SECT_ENDOFCHAIN;
AContext.Size:=0;
AContext.Position:=0;
WriteData(AContext,ContextBackup.MiniStream.Memory,ContextBackup.MiniStream.Size);
ContextBackup.MiniStream.Free;
end;
begin
if ASize<1 then begin
Result:=0;
Exit;
end;
if AContext.AccessMode=fmOpenRead then begin
Raise EStreamError.Create('Stream can not be written, open for read only');
end;
AContext.Dirty:=true;
if Assigned(AContext.MiniStream) then begin
//It could be a MiniFAT stream.
if AContext.Size+ASize>=FHeader._ulMiniSectorCutoff then begin
//Jump to regular FAT allocation
MoveMiniStreamToRegularStream();
end else begin
//Write in the MiniFAT cache
AContext.MiniStream.Position:=AContext.Position;
Result:=AContext.MiniStream.Write(ABuffer^,ASize);
AContext.Position:=AContext.MiniStream.Position;
AContext.Size:=AContext.MiniStream.Size;
Exit;
end;
end;
if (AContext.FATIndex=SECT_FREESECT) then begin
ResynchronizePositionWithAllocation(AContext);
end;
Position:=AContext.Position;
RemainData:=ASize;
SourceBuffer:=ABuffer;
while RemainData>0 do begin
FAT:=AContext.FATIndex;
CanWrite:=GetFATRemainForPosition(Position);
if CanWrite>RemainData then begin
CanWrite:=RemainData;
end;
if IsReservedValue(FAT) then begin
if FAT<>SECT_ENDOFCHAIN then begin
Raise Exception.Create('Damaged stream');
end;
FAT:=AllocateNewSector(0,SECT_FREESECT,SECT_ENDOFCHAIN);
AContext.FATIndex:=FAT;
AContext.FATFirstIndex:=FAT;
end;
if CanWrite>0 then begin
FStream.Position:=SectorAbsolute(FAT)+GetFATOffsetForPosition(Position);
EffectiveWrite:=FStream.Write(SourceBuffer^,CanWrite);
dec(RemainData,EffectiveWrite);
inc(Position,EffectiveWrite);
inc(SourceBuffer,EffectiveWrite);
if (EffectiveWrite<>CanWrite) Then begin
AContext.FATIndex:=SECT_FREESECT;
Result:=ASize-RemainData;
Exit;
end;
end;
AContext.Position:=Position;
if AContext.Position>AContext.Size then begin
//The file is growing...
AContext.Size:=AContext.Position;
end;
//--------------
if GetFATRemainForPosition(Position)=FSectorSize then begin
if FAT<>SECT_ENDOFCHAIN then begin
NextFAT:=ReadFATEntryValue(FAT);
end;
if NextFAT<>SECT_ENDOFCHAIN then begin
AContext.FATIndex:=NextFAT;
end else begin
//Add a new FAT allocation.
NextFAT:=AllocateNewSector(FAT,FAT,SECT_ENDOFCHAIN);
if NextFAT=SECT_FREESECT then begin
//Failed to allocate new sector, Media full ?
AContext.FATIndex:=SECT_FREESECT; //Force resyncronization
Result:=ASize-RemainData;
Exit;
end;
WriteFATEntryValue(FAT,NextFAT);
AContext.FATIndex:=NextFAT;
NextFAT:=SECT_ENDOFCHAIN;
end;
end;
end;
Result:=ASize;
end;
function TFATIndirect.StreamSeekPosition(var AContext: TFATStreamContext;
const AOffset: int64; const AOrigin: TSeekOrigin): int64;
begin
Case AOrigin of
soBeginning: begin
AContext.FATIndex:=SECT_FREESECT; //Resets FAT seek
AContext.Position:=AOffset;
end;
soEnd: begin
AContext.FATIndex:=SECT_FREESECT;
if AContext.Size<AOffset then begin
Raise EStreamError.Create('Trying to move to before beginning of stream.');
end;
AContext.Position:=AContext.Size+AOffset;
end;
soCurrent: begin
AContext.FATIndex:=SECT_FREESECT;
if AContext.Position+AOffset<0 then begin
Raise EStreamError.Create('Trying to move to before beginning of stream.');
end;
AContext.Position:=AContext.Position+AOffset;
end;
else
Raise Exception.Create('Invalid Seek mode');
end;
Result:=AContext.Position;
end;
function TFATIndirect.Initialize(const ACreate: Boolean): Boolean;
var
RootEntry: TWCBFStructuredStorageDirectoryEntry;
begin
Result:=true;
if ACreate then begin
FillByte(FHeader,SizeOf(FHeader),0);
FHeader._abSig:=OLE_SIGTATURE;
//FHeader._clid:=ALL ZEROS, as set by FillByte
FHeader._uMinorVersion:=$003E;
FHeader._uDllVersion:=$0003;
FHeader._uByteOrder:=NToLE($FFFE);
FHeader._uSectorShift:=9;
FHeader._uMiniSectorShift:=6;
//Some reserved, must be zero..., Reserved, Reserved1 and Reserved2.
FHeader._csectFat:=0;
FHeader._sectDirStart:=SECT_ENDOFCHAIN;
//FHeader._signature:=0; //Transaction not used.
FHeader._ulMiniSectorCutoff:=4096; //This is customizable
FHeader._sectMiniFatStart:=SECT_ENDOFCHAIN;
FHeader._csectMiniFat:=0;
FHeader._sectDifStart:=SECT_ENDOFCHAIN;
FHeader._csectDif:=0;
Format(FHeader);
FStream.Position:=0;
FillByte(FHeader._sectFat[0],Sizeof(FHeader._sectFat),$FF);
FStream.Write(FHeader,Sizeof(FHeader));
end else begin
FStream.Position:=0;
if FStream.Size<SizeOf(FHeader) then begin
Result:=false;
Exit;
end;
if FStream.Read(FHeader,Sizeof(FHeader))<>Sizeof(FHeader) then begin
Result:=false;
end;
end;
if NToLE(FHeader._uByteOrder)=$FFFE then begin
//Does not need to perform any endian change, the file endianess
//matches the current machine endianess. This means big endian file
//in big endian machine or little endian file in little endian machine.
FNeedEndianChange:=false;
end else begin
FNeedEndianChange:=true;
end;
iHeaderLEToN(FHeader);
//Check some structured details to detect file structure as expected
if not CompareMem(@FHeader._abSig[0],@OLE_SIGTATURE[0],sizeof(OLE_SIGTATURE)) then begin
Result:=false;
Exit;
end;
//----------------------
//This code is a placeholder for future compatibility updates when documentation
//about different sector sizes in versions are found.
if FHeader._uDllVersion=$0003 then begin
//Sector size is fixed to 512 :-? really strange way. NOT DOCUMENTED
FHeaderLength:=1 shl FHeader._uSectorShift;
end else if FHeader._uDllVersion=$0004 then begin
//Sector size is fixed to 4096 ????? NOT DOCUMENTED
FHeaderLength:=1 shl FHeader._uSectorShift;
end else begin
Raise Exception.CreateFmt('OLE file version %d not supported.',[FHeader._uDllVersion]);
end;
if FHeaderLength<512 then begin
//Header MUST be at least 512...
FHeaderLength:=512;
end;
//-----------------------
FSectorSize:=1 shl FHeader._uSectorShift;
FFATEntriesPerSect:=(FSectorSize div BYTES_PER_FAT_ENTRY);
FFATCache.Sector:=SECT_FREESECT;
FFATCache.Dirty:=false;
GetMem(FFATCache.Cache,FSectorSize);
//Content of this buffer will not be catched at all, used
//for some read/write operations to avoid the alloc/free
//process. Its content is undetermined.
GetMem(FMiscSectorBuffer,FSectorSize);
//Loads the DIF structure in memory for fast access.
LoadDIFArray();
//FAT entries that fit in the header FAT indirect links.
FEntriesShort:=FFATEntriesPerSect*Length(FHeader._sectFat);
if ACreate then begin
//The root directory entry must be created here just to
//simplificate the design of creation files and Root Entry
//must exists in a compound binary file.
RootEntry._mse:=0;
FillByte(RootEntry,sizeof(RootEntry),0);
RootEntry._ab:='Root Entry';
RootEntry._cb:=(Length('Root Entry')+1)*sizeof(WChar);
RootEntry._mse:=BYTE(STGTY_ROOT);
RootEntry._bflags:=BYTE(DE_BLACK);
RootEntry._sidLeftSib:=WINCOMPOUND_NOSID;
RootEntry._sidRightSib:=WINCOMPOUND_NOSID;
RootEntry._sidChild:=WINCOMPOUND_NOSID;
//RootEntry._clsid:=
RootEntry._dwUserFlags:=0;
RootEntry._time[0].dwHighDateTime:=0;
RootEntry._time[0].dwLowDateTime:=0;
RootEntry._time[1].dwHighDateTime:=0;
RootEntry._time[1].dwLowDateTime:=0;
RootEntry._sectStart:=SECT_ENDOFCHAIN;
RootEntry._ulSize:=0;
RootEntry._dptPropType:=0;
DirectoryContext.FATFirstIndex:=FHeader._sectDirStart;
DirectoryContext.FATIndex:=SECT_FREESECT;
DirectoryContext.Position:=0;
DirectoryContext.Size:=0;
DirectoryContext.MiniStream:=nil;
DirectoryContext.AccessMode:=fmOpenReadWrite;
WriteData(DirectoryContext,@RootEntry,SizeOf(RootEntry));
FillByte(RootEntry,sizeof(RootEntry),0);
WriteData(DirectoryContext,@RootEntry,SizeOf(RootEntry));
FHeader._sectDirStart:=DirectoryContext.FATFirstIndex;
end;
DirectoryContext.FATFirstIndex:=FHeader._sectDirStart;
DirectoryContext.FATIndex:=DirectoryContext.FATFirstIndex;
DirectoryContext.Position:=0;
DirectoryContext.Size:=GetStreamSizeInSectors(DirectoryContext)*FSectorSize;
DirectoryContext.MiniStream:=nil;
DirectoryContext.AccessMode:=fmOpenReadWrite;
MiniFATContext.FATFirstIndex:=FHeader._sectMiniFatStart;
MiniFATContext.FATIndex:=FHeader._sectMiniFatStart;
MiniFATContext.Position:=0;
MiniFATContext.Size:=GetStreamSizeInSectors(MiniFATContext)*FSectorSize;
MiniFATContext.MiniStream:=nil;
MiniFATContext.AccessMode:=fmOpenReadWrite;
InitializeMiniDataStream();
MiniFATDataContext.AccessMode:=fmOpenReadWrite;
end;
function TFATIndirect.ReadFATEntryValue(const AFATEntryIndex: SizeUint): SECT;
var
Sector: SECT;
DIFIndex,FATInDIF,OffSetInFAT: SECT;
begin
DecomposeFATEntry(AFATEntryIndex,DIFIndex,FATInDIF,OffSetInFAT);
Sector:=FDIFArray[DIFIndex][FATInDIF];
ReadFATSector(Sector);
Result:=FFATCache.Cache[OffSetInFAT];
end;
procedure TFATIndirect.WriteFATEntryValue(const AFATEntryIndex: SizeUint;
const ALinkTo: SECT);
var
DIF,FATInDIF,OffSetInFAT: SECT;
Sector: SECT;
begin
DecomposeFATEntry(AFATEntryIndex,DIF,FATInDIF,OffSetInFAT);
Sector:=FDIFArray[DIF][FATInDIF];
ReadFATSector(Sector);
FFATCache.Cache[OffSetInFAT]:=ALinkTo;
WriteFATSector(Sector);
end;
procedure TFATIndirect.ResynchronizePosition(var AContext: TFATStreamContext);
var
LastSector: SECT;
begin
LastSector:=FollowFATLinkage(AContext.FATFirstIndex,AContext.Position);
if LastSector<>SECT_FREESECT then begin
AContext.FATIndex:=LastSector;
end else begin
AContext.FATIndex:=SECT_FREESECT;
end;
end;
procedure TFATIndirect.ResynchronizePositionWithAllocation(
var AContext: TFATStreamContext);
var
Sector,LastSector: SECT;
Skip: int64;
begin
Skip:=AContext.Position;
LastSector:=AContext.FATFirstIndex;
dec(Skip,FSectorSize);
while (Skip>=0) and (LastSector<$FFFFFFF0) do begin
Sector:=LastSector;
LastSector:=ReadFATEntryValue(Sector);
if LastSector=SECT_ENDOFCHAIN then begin
LastSector:=AllocateNewSector(Sector,Sector,SECT_ENDOFCHAIN);
if LastSector=SECT_FREESECT then begin
//Unable to allocate new FAT sector, Media full ????
AContext.FATIndex:=SECT_FREESECT;
Exit;
end;
end;
dec(Skip,FSectorSize);
end;
if (LastSector>=$FFFFFFF0) and (Skip>FSectorSize) then begin
AContext.FATIndex:=SECT_FREESECT;
end else begin
AContext.FATIndex:=LastSector;
end;
end;
function TFATIndirect.FollowFATLinkage(const AStartFATEntryIndex: SizeUint;
const ABytes: int64): SECT;
var
Sector,LastSector: SECT;
Skip: int64;
begin
Skip:=ABytes;
LastSector:=AStartFATEntryIndex;
dec(Skip,FSectorSize);
while (Skip>=0) and (LastSector<$FFFFFFF0) do begin
Sector:=LastSector;
LastSector:=ReadFATEntryValue(Sector);
dec(Skip,FSectorSize);
end;
if (LastSector>=$FFFFFFF0) and (Skip>FSectorSize) then begin
Result:=SECT_FREESECT;
end else begin
Result:=LastSector;
end;
end;
procedure TFATIndirect.ResetFATLinkage(const AStartFATEntryIndex: SizeUint;
const ANewFATValue: SECT);
var
Sector,LastSector: SECT;
begin
LastSector:=AStartFATEntryIndex;
while not IsReservedValue(LastSector) do begin
Sector:=LastSector;
LastSector:=ReadFATEntryValue(Sector);
WriteFATEntryValue(Sector,ANewFATValue);
end;
end;
procedure TFATIndirect.Format(var AHeader: TWCBFStructuredStorageHeader);
begin
//Accept defaults.
AHeader._csectDif:=AHeader._csectDif; //just to avoid hints...
end;
function TFATIndirect.IsSizeInMiniFAT(const ASize: int64): Boolean;
begin
if ASize<FHeader._ulMiniSectorCutoff then begin
Result:=true;
end else begin
Result:=false;
end;
end;
function TFATIndirect.OpenStream(const AFirstFATSector: DWORD;
const ASize: int64; const AAccessMode: WORD): TFATStreamContext;
begin
Result.FATFirstIndex:=AFirstFATSector;
Result.FATIndex:=AFirstFATSector;
Result.Size:=ASize;
Result.Position:=0;
Result.Dirty:=false;
Result.AccessMode:=AAccessMode;
if ASize<FHeader._ulMiniSectorCutoff then begin
Result.MiniStream:=TMemoryStream.Create;
ReadFromMiniFAT(Result,Result.MiniStream);
if Result.MiniStream.Size<>ASize then begin
Result.FATFirstIndex:=SECT_ENDOFCHAIN;
Result.FATIndex:=SECT_ENDOFCHAIN;
Result.Size:=0;
Result.MiniStream.Free;
Result.MiniStream:=nil;
end;
end else begin
Result.MiniStream:=nil;
end;
end;
procedure TFATIndirect.CloseStream(var AContext: TFATStreamContext);
begin
if Assigned(AContext.MiniStream) then begin
if AContext.Dirty then begin
WriteToMiniFAT(AContext,AContext.MiniStream);
end;
FreeAndNIL(AContext.MiniStream);
end;
end;
constructor TFATIndirect.Create(const AOLEStream: TStream);
begin
FStream:=AOLEStream;
FStream.Position:=0; //Beginning
FillByte(DirectoryContext,sizeof(DirectoryContext),0);
end;
destructor TFATIndirect.Destroy;
var
FillUp: int64;
begin
FlushCaches();
UnloadDIFArray();
if FDirtyMedia then begin
//Rewrite the header...
FHeader._sectDirStart:=DirectoryContext.FATFirstIndex;
FHeader._sectMiniFatStart:=MiniFATContext.FATFirstIndex;
FHeader._csectMiniFat:=MiniFATContext.Size div FSectorSize;
iHeaderNToLE(FHeader);
FStream.Position:=0;
FStream.WriteBuffer(FHeader,Sizeof(FHeader));
FillUp:=FStream.Size-FHeaderLength;
if FillUp mod FSectorSize <> 0 then begin
//Fillup to multiple of Sector Size excluding the header.
FStream.Position:=FStream.Size;
FillByte(FMiscSectorBuffer^,FSectorSize,$FF);
FStream.WriteBuffer(FMiscSectorBuffer^,FSectorSize-(FillUp mod FSectorSize));
end;
end;
if Assigned(FFATCache.Cache) then Freemem(FFATCache.Cache);
if Assigned(FMiscSectorBuffer) then FreeMem(FMiscSectorBuffer);
inherited Destroy;
end;
//Matches mask taken from FV package of FPC with little changes
function MatchesMask(What, Mask: string): boolean;
Function CmpStr(const hstr1,hstr2:string):boolean;
var
found : boolean;
i1,i2 : SizeInt;
begin
i1:=0;
i2:=0;
if hstr1='' then
begin
CmpStr:=(hstr2='');
exit;
end;
found:=true;
repeat
inc(i1);
if (i1>length(hstr1)) then
break;
inc(i2);
if (i2>length(hstr2)) then
break;
case hstr1[i1] of
'?' :
found:=true;
'*' :
begin
found:=true;
if (i1=length(hstr1)) then
i2:=length(hstr2)
else
if (i1<length(hstr1)) and (hstr1[i1+1]<>hstr2[i2]) then
begin
if i2<length(hstr2) then
dec(i1)
end
else
if i2>1 then
dec(i2);
end;
else
found:=(hstr1[i1]=hstr2[i2]) or (hstr2[i2]='?');
end;
until not found;
if found then
begin
found:=(i2>=length(hstr2)) and
(
(i1>length(hstr1)) or
((i1=length(hstr1)) and
(hstr1[i1]='*'))
);
end;
CmpStr:=found;
end;
begin
MatchesMask:=CmpStr(Mask,What);
end;
{ TMaskFile }
function TMaskFile.Matches(const AFileName: UTF8String): Boolean;
begin
Result:=MatchesMask(AFileName,FMask)
end;
constructor TMaskFile.Create(const AMask: UTF8String);
begin
FMask:=AMask;
end;
end.