// Nico Bendlin and himitsu
function GetImageLinkTimeStamp(Filename:
String): TDateTime;
const
INVALID_SET_FILE_POINTER = DWORD(-1);
BorlandMagicTimeStamp = $2A425E19;
// Delphi 4-6 (and above?) // jupp, mindestens bis XE, wobei es hier nicht mehr nötig ist, da seit D2009/D2010 endlich das Datum im NTHeader einkompiliert wird.
FileTime1970: TFileTime = (dwLowDateTime:$D53E8000; dwHighDateTime:$019DB1DE);
type
PImageSectionHeaders = ^TImageSectionHeaders;
TImageSectionHeaders =
array [Word]
of TImageSectionHeader;
type
PImageResourceDirectory = ^TImageResourceDirectory;
TImageResourceDirectory =
packed record
Characteristics: DWORD;
TimeDateStamp: DWORD;
MajorVersion: Word;
MinorVersion: Word;
NumberOfNamedEntries: Word;
NumberOfIdEntries: Word;
end;
var
FileHandle: THandle;
BytesRead: DWORD;
ImageDosHeader: TImageDosHeader;
ImageNtHeaders: TImageNtHeaders;
SectionHeaders: PImageSectionHeaders;
Section: Word;
ResDirRVA: DWORD;
ResDirSize: DWORD;
ResDirRaw: DWORD;
ResDirTable: TImageResourceDirectory;
FileTime: TFileTime;
SearchFilename:
String;
Schrott: PChar;
begin
Result := UnixTimeToDateTime(0);
if GetModuleHandle(PChar(Filename)) <> 0
then begin
SearchFilename := GetModuleName(GetModuleHandle(PChar(Filename)));
if (SearchFilename <> '
')
and FileExists(SearchFilename)
then Filename := SearchFilename;
end;
if ExtractFilePath(Filename) = '
'
then begin
SetLength(SearchFilename, MAX_PATH-1);
SetLength(SearchFilename, SearchPath(Pointer(ExtractFileDir(Filename)), PChar(ExtractFileName(Filename)),
nil, MAX_PATH, PChar(SearchFilename), Schrott));
if SearchFilename <> '
'
then Filename := SearchFilename;
end;
FileHandle := CreateFile(PChar(Filename), GENERIC_READ, FILE_SHARE_READ,
nil, OPEN_EXISTING, 0, 0);
if FileHandle <> INVALID_HANDLE_VALUE
then
try
// Read MS-DOS header to get the offset of the PE32 header (not required on WinNT based systems - but mostly available)
if not ReadFile(FileHandle, ImageDosHeader, SizeOf(TImageDosHeader), BytesRead,
nil)
or (BytesRead <> SizeOf(TImageDosHeader))
or (ImageDosHeader.e_magic <> IMAGE_DOS_SIGNATURE)
then
ImageDosHeader._lfanew := 0;
// Read PE32 header (including optional header)
if (SetFilePointer(FileHandle, ImageDosHeader._lfanew,
nil, FILE_BEGIN) = INVALID_SET_FILE_POINTER)
or not (ReadFile(FileHandle, ImageNtHeaders, SizeOf(TImageNtHeaders), BytesRead,
nil)
and (BytesRead = SizeOf(TImageNtHeaders)))
then
Exit;
// Validate PE32 image header
if ImageNtHeaders.Signature <> IMAGE_NT_SIGNATURE
then
Exit;
// Seconds since 1970 (UTC)
Result := UnixTimeToDateTime(ImageNtHeaders.FileHeader.TimeDateStamp);
// Check for Borland's magic value for the link time stamp (we take the time stamp from the resource directory table)
if ImageNtHeaders.FileHeader.TimeDateStamp = BorlandMagicTimeStamp
then
with ImageNtHeaders, FileHeader, OptionalHeader
do begin
// Validate Optional header
if (SizeOfOptionalHeader < IMAGE_SIZEOF_NT_OPTIONAL_HEADER)
or (Magic <> IMAGE_NT_OPTIONAL_HDR_MAGIC)
then
Exit;
// Read section headers
SectionHeaders := GetMemory(NumberOfSections * SizeOf(TImageSectionHeader));
if Assigned(SectionHeaders)
then
try
if (SetFilePointer(FileHandle, SizeOfOptionalHeader - IMAGE_SIZEOF_NT_OPTIONAL_HEADER,
nil, FILE_CURRENT) = INVALID_SET_FILE_POINTER)
or not ReadFile(FileHandle, SectionHeaders^, NumberOfSections * SizeOf(TImageSectionHeader), BytesRead,
nil)
and (BytesRead = NumberOfSections * SizeOf(TImageSectionHeader))
then
Exit;
// Get RVA and size of the resource directory
with DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE]
do begin
ResDirRVA := VirtualAddress;
ResDirSize := Size;
end;
// Search for section which contains the resource directory
ResDirRaw := 0;
for Section := 0
to NumberOfSections - 1
do
with SectionHeaders[Section]
do
if (VirtualAddress <= ResDirRVA)
and (VirtualAddress + SizeOfRawData >= ResDirRVA + ResDirSize)
then begin
ResDirRaw := PointerToRawData - (VirtualAddress - ResDirRVA);
Break;
end;
// Resource directory table found?
if ResDirRaw = 0
then
Exit;
// Read resource directory table
if (SetFilePointer(FileHandle, ResDirRaw,
nil, FILE_BEGIN) = INVALID_SET_FILE_POINTER)
or not (ReadFile(FileHandle, ResDirTable, SizeOf(TImageResourceDirectory), BytesRead,
nil)
and (BytesRead = SizeOf(TImageResourceDirectory)))
then
Exit;
// Convert from DosDateTime to SecondsSince1970
if DosDateTimeToFileTime(HiWord(ResDirTable.TimeDateStamp), LoWord(ResDirTable.TimeDateStamp), FileTime)
then
// FIXME: Borland's linker uses the local system time of the user who linked the executable image file. (is that information anywhere?)
Result := UnixTimeToDateTime((ULARGE_INTEGER(FileTime).QuadPart - ULARGE_INTEGER(FileTime1970).QuadPart)
div 10000000);
finally
FreeMemory(SectionHeaders);
end;
end;
finally
CloseHandle(FileHandle);
end;
end;