AhkPEinfo() : A function to test if an uncompressed PE binary belongs to AutoHotkey family.

Post your working scripts, libraries and tools for AHK v1.1 and older
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

AhkPEinfo() : A function to test if an uncompressed PE binary belongs to AutoHotkey family.

17 May 2021, 17:29

AhkPEinfo( PEfile ) : A function to test if an uncompressed PE binary belongs to AutoHotkey family.

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.
 
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
 
image.png
image.png (5.65 KiB) Viewed 841 times
 
 
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 := "" )
}
My Scripts and Functions: V1  V2
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

AhkPEinfo() updated to v0.14

19 May 2021, 10:32

AhkPEinfo() updated to v0.14

- Code tidied
- Better error handling.
- ProductVersion included in returned object.
- Updated notes in the post
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: AhkPEinfo( PEfile ) : A function to test if an uncompressed PE binary belongs to AutoHotkey family.

22 May 2021, 16:18

Can it extract the script files?
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

AhkPEinfo() : A function to test if an uncompressed PE binary belongs to AutoHotkey family.

23 May 2021, 01:53

robodesign wrote:
22 May 2021, 16:18
Can it extract the script files?
Its just a tool to ID a AHK binary.
You may proceed with whatever you want to do by checking its results.

In newer compiled scripts (1.1.34.00+) script will have resource Id 1 (RT_RCDATA) .
If a AHK compiled script (1.1.34.00+) has lots of user resources, no need to recompile when a newer AHK is released.
We can leave the resource section intact and just replace the binary and patch the PE headers.
I am working on a function to do that. AhkPEinfo() will be an important helper tool for that function.
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: AhkPEinfo() : A function to test if an uncompressed PE binary belongs to AutoHotkey family.

23 May 2021, 03:08

Thank you for the reply.

I had scripts I compiled and I no longer have the ahk file for (silly, I know, but true...). That's why I asked...

Best regards, Marius.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.
User avatar
SKAN
Posts: 1551
Joined: 29 Sep 2013, 16:58

Re: AhkPEinfo() : A function to test if an uncompressed PE binary belongs to AutoHotkey family.

23 May 2021, 03:16

robodesign wrote:
23 May 2021, 03:08
I had scripts I compiled and I no longer have the ahk file for (silly, I know, but true...). That's why I asked...
If you didn't compress the EXE,
you can extract the script with the following tool
http://www.angusj.com/resourcehacker
robodesign
Posts: 934
Joined: 30 Sep 2017, 03:59
Location: Romania
Contact:

Re: AhkPEinfo() : A function to test if an uncompressed PE binary belongs to AutoHotkey family.

23 May 2021, 13:27

Thank you , SKAN. I will use that next time.
-------------------------
KeyPress OSD v4: GitHub or forum. (presentation video)
Quick Picto Viewer: GitHub or forum.
AHK GDI+ expanded / compilation library (on GitHub)
My home page.

Return to “Scripts and Functions (v1)”

Who is online

Users browsing this forum: No registered users and 49 guests