Requires: InBin() / Topic split from InBin() on May 21, 2021.
Return value & ErrorLevel
If binary is a AHK PE file, the function returns an object with the following keys:
AhkVersion IsDLL IsFork IsUnicode FileSize ProVersion PtrSize RCDATA (*RCDATA resource names/IDs as a MatchList)
ErrorLevel will be set to NULL ("") and A_LastError will be set to 0.
When function fails,
Return value will be NULL ("")
ErrorLevel is set with either of these messages: Not a AHK PE file. FormatMessage(A_LastError)
A_LastError is set with -1 (Not a AHK PE file.) or will retain the value set by MapAndLoad()
Notes:
InBin() is never used past Import Directory, that is wade in to Resource Directory.
This means file size doesn't matter. My test file was a compiled H w/ resources sized 104 MiB.
The required memory would be around 150 KiB only owing to well computed search range. This could be slightly higher for H versions.
Edit: A_AhkVersion and ProductVersion are different in H binaries.
The function now returns ProductVersion under key ProVersion.
InBin() is used only within VS_VERSIONINFO structure which is less than 750 bytes.
The function now returns ProductVersion under key ProVersion.
InBin() is used only within VS_VERSIONINFO structure which is less than 750 bytes.
How ansi/unicode is determined?
The bytes between Import Directory and Resource Directory (50K max for H, much lesser for others) is searched for the string "RegisterClassEx"
If the PE is importing RegisterClassExW, Unicode is presumed otherwise the PE is deemed to be Ansi.
Further searches with InBin() from this point will use utf-16 for Unicode or cp0 for Ansi.
How version is determined?
The title of A_ScriptHwnd window is usually suffixed with "AutoHotkey v" . A_AhkVersion.
The function first searches for the string "AHK_PlayMe mode" to make sure it is a AHK PE and then searches for
"AutoHotkey v" which is only a few 100 bytes away from "AHK_PlayMe mode"
"AutoHotkey v" is removed from the result. Only A_AhkVersion will be returned.
How IsFork is determined?
Only H binaries (DLL,BIN,EXE) export functions.
As of today, the mere existence of Export Directory is enough to determine if PE is H.
Since this function uses file mapping, disk speed affects function speed, negligible though.
For me: My test 104 MiB compiled script consumes 10ms on USB-2 pen drive, about 4ms on USB-3 external HDD and less than 1ms on my fixed SSD.
Call example:
Code: Select all
#NoEnv
#Warn
#SingleInstance, Force
PEfile := A_AhkPath . "\..\Compiler\Ahk2Exe.exe"
If ! ( A := AhkPEinfo(PEfile) )
MsgBox % ErrorLevel
Else MsgBox % "Version:`t`t" . A.AhkVersion . "`n"
. "Unicode:`t`t" . A.IsUnicode . "`n"
. "PtrSize:`t`t" . A.PtrSize . "`n"
. "IsDLL: `t`t" . A.IsDLL . "`n"
. "IsFork: `t`t" . A.IsFork . "`n"
. "RCDATA: `t`t" . A.RCDATA
The function:
Code: Select all
AhkPEinfo( PEfile* ) { ; v0.14 By SKAN on D45I/D45J @ tiny.cc/ahkpeinfo
Local ; AhkPEinfo() requires InBin() v0.60 @ tiny.cc/inbin
Static _ := "", n := 0
If ( !IsObject(PEfile) ) ; => EnumResourceNames() callback in effect
{
n := NumGet(PEfile + (A_PtrSize*2), "Ptr")
_ .= ( n<0x10000 ? n : StrGet(n) ) . ","
Return True
}
VarSetCapacity(LOADED_IMAGE, 84, 0)
hImagehlp := DllCall("Kernel32.dll\LoadLibrary", "Str","ImageHlp.dll", "Ptr")
If !DllCall("ImageHlp.dll\MapAndLoad", "AStr",PEfile.1, "Int",0, "Ptr",&LOADED_IMAGE, "Int",0, "Int",1)
{
Err := A_LastError ; Save LastError
DllCall("Kernel32.dll\FormatMessage"
, "Int",0x1100 ; FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER
, "Ptr",0, "Int",Err, "Int",0 , "PtrP",hMem:=0, "Int",0, "Ptr",0)
ErrMsg := StrGet(hMem)
hMem := DllCall("Kernel32.dll\LocalFree", "Ptr",hMem, "Ptr")
DllCall("Kernel32.dll\FreeLibrary", "Ptr",hImagehlp)
DllCall("Kernel32.dll\SetLastError", "Int",Err) ; Restore LastError
Return % ("", ErrorLevel := ErrMsg)
}
pMappedAddress := NumGet(LOADED_IMAGE, (A_PtrSize=4) ? 8 : 16)
pFileHeader := NumGet(LOADED_IMAGE, (A_PtrSize=4) ? 12 : 24) ; "PE\0\0" COFF header
PtrSize := NumGet(pFileHeader+ 4,"UShort") = 0x8664 ? 8 : 4 ; 64 bit or 32 bit?
IsDLL := NumGet(pFileHeader+22,"UShort") & 0x2000 ? 1 : 0 ; IMAGE_FILE_DLL := 0x2000
Exp := DllCall("ImageHlp.dll\ImageDirectoryEntryToData"
,"Ptr",pMappedAddress, "Int",False
,"UShort",0 ; IMAGE_DIRECTORY_ENTRY_EXPORT
,"PtrP",n, "Ptr")
Imp := DllCall("ImageHlp.dll\ImageDirectoryEntryToData"
,"Ptr",pMappedAddress, "Int",False
,"UShort",1 ; IMAGE_DIRECTORY_ENTRY_IMPORT
,"PtrP",n, "Ptr")
Res := DllCall("ImageHlp.dll\ImageDirectoryEntryToData"
,"Ptr",pMappedAddress, "Int",False
,"UShort",2 ; IMAGE_DIRECTORY_ENTRY_RESOURCE
,"PtrP",n, "Ptr")
FoundPtr := 0, ApiImp := "", AhkVersion := 0, ProVersion := ""
If ( Imp && Res )
{
ImpLen := Res - Imp
FoundPtr := InBin(Imp, ImpLen, "RegisterClassEx", "cp0",, 0) ; Search from right
ApiImp := FoundPtr ? StrGet(FoundPtr, "cp0") : ""
}
If ( StrLen(ApiImp) )
{
IsUnicode := ( ApiImp="RegisterClassExW" )
Encoding := ( IsUnicode ? "utf-16" : "cp0" )
Len := ( Exp ? Exp : Imp ) - pMappedAddress
FoundPtr := InBin(pMappedAddress, Len, "AHK_PlayMe mode", Encoding,, 0) ; Search from right
}
If ( FoundPtr )
{
Len := ( Exp ? Exp : Imp ) - FoundPtr
FoundPtr := InBin(FoundPtr, Len, "AutoHotkey v", Encoding) ; Search from left
AhkVersion := FoundPtr ? StrGet(FoundPtr, Encoding) : 0
AhkVersion := StrReplace(AhkVersion, "AutoHotkey v")
}
If ( AhkVersion )
{
hModule := pMappedAddress + 1 ; Skan-Hack / Equivalent of LOAD_LIBRARY_AS_DATAFILE
hMem := RegisterCallback(A_ThisFunc, "F", 4)
RT_RCDATA := 10
DllCall("Kernel32.dll\EnumResourceNames", "Ptr",hModule, "Ptr",RT_RCDATA, "Ptr",hMem, "Int",0)
hMem := DllCall("Kernel32.dll\GlobalFree", "Ptr",hMem, "Ptr")
RCDATA := RTrim(_, ",")
RT_VERSION := 16
If ( n := DllCall("Kernel32.dll\FindResource", "Ptr",hModule, "Ptr",1, "Ptr",RT_VERSION, "Ptr") )
If ( n := DllCall("Kernel32.dll\LoadResource", "Ptr",hModule, "Ptr",n, "Ptr" ) )
{
Len := NumGet(n+0, "UShort") ; VS_VERSIONINFO -> wLength
FoundPtr := InBin(n, Len, "ProductVersion", "utf-16",,0) ; Search from right
ProVersion := FoundPtr ? StrGet(FoundPtr+30, "utf-16") : ""
}
hFile := NumGet(LOADED_IMAGE, A_PtrSize)
FileSize := FileOpen(hFile, "h").Length
}
DllCall("ImageHlp.dll\UnMapAndLoad", "Ptr",&LOADED_IMAGE)
DllCall("Kernel32.dll\FreeLibrary", "Ptr",hImagehlp)
n := VarSetCapacity(_, VarSetCapacity(_, 128) * 0) ; Empty static vars
If ( AhkVersion=0 )
{
DllCall("Kernel32.dll\SetLastError", "Int",-1)
Return % ("", ErrorLevel := "Not a AHK PE file.")
}
Else DllCall("Kernel32.dll\SetLastError", "Int",0)
Return ( { "AhkVersion": AhkVersion, "IsDLL": IsDLL, "IsFork": (Exp ? "H" : 0), "IsUnicode": IsUnicode
,"FileSize": FileSize, "ProVersion": ProVersion, "PtrSize": PtrSize, "RCDATA": RCDATA }
, ErrorLevel := "" )
}