dummzeuch 1505 Posted September 6, 2020 The usual way to get version information for a file goes like this: type TEXEVersionData = record CompanyName, FileDescription, FileVersion, InternalName, LegalCopyRight, LegalTradeMarks, OriginalFilename, ProductName, ProductVersion, Comments, PrivateBuild, SpecialBuild: string; end; function TCustomFileInfo.ReadVersionData: TEXEVersionData; type PLandCodepage = ^TLandCodepage; TLandCodepage = record wLanguage, wCodePage: Word; end; var Dummy: Cardinal; Len: Cardinal; Buf: array of Byte; pntr: Pointer; lang: string; begin Len := GetFileVersionInfoSize(PChar(Filename), Dummy); if Len = 0 then RaiseLastOSError; SetLength(Buf, Len); if not GetFileVersionInfo(PChar(Filename), 0, Len, @Buf[0]) then RaiseLastOSError; if not VerQueryValue(Buf, '\VarFileInfo\Translation\', pntr, Len) then RaiseLastOSError; lang := Format('%.4x%.4x', [PLandCodepage(pntr)^.wLanguage, PLandCodepage(pntr)^.wCodePage]); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\CompanyName'), pntr, Len) { and (@len <> nil)} then Result.CompanyName := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\FileDescription'), pntr, Len) { and (@len <> nil)} then Result.FileDescription := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\FileVersion'), pntr, Len) { and (@len <> nil)} then Result.FileVersion := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\InternalName'), pntr, Len) { and (@len <> nil)} then Result.InternalName := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\LegalCopyright'), pntr, Len) { and (@len <> nil)} then Result.LegalCopyRight := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\LegalTrademarks'), pntr, Len) { and (@len <> nil)} then Result.LegalTradeMarks := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\OriginalFileName'), pntr, Len) { and (@len <> nil)} then Result.OriginalFilename := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\ProductName'), pntr, Len) { and (@len <> nil)} then Result.ProductName := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\ProductVersion'), pntr, Len) { and (@len <> nil)} then Result.ProductVersion := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\Comments'), pntr, Len) { and (@len <> nil)} then Result.Comments := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\PrivateBuild'), pntr, Len) { and (@len <> nil)} then Result.PrivateBuild := PChar(pntr); if VerQueryValue(@Buf[0], PChar('\StringFileInfo\' + lang + '\SpecialBuild'), pntr, Len) { and (@len <> nil)} then Result.SpecialBuild := PChar(pntr); end; (This is based on http://stackoverflow.com/a/5539411/49925 ) This only handles the case of known strings. But you can put any additional strings into it, e.g.: LANGUAGE LANG_ENGLISH,SUBLANG_ENGLISH_US 1 VERSIONINFO LOADONCALL MOVEABLE DISCARDABLE IMPURE FILEVERSION 1, 3, 6, 577 PRODUCTVERSION 1, 3, 6, 577 FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEOS VOS__WINDOWS32 FILETYPE VFT_APP { BLOCK "StringFileInfo" { BLOCK "040904E4" { VALUE "CompanyName", "dummzeuch.de\000" VALUE "FileDescription", "dzlib PrepBuild commandline and IDE prebuild tool\000" VALUE "FileVersion", "1.3.6.577\000" VALUE "InternalName", "PrepBuild\000" VALUE "LegalCopyright", "Copyright 2002-2020 by Thomas Mueller\000" VALUE "LegalTrademarks", "\000" VALUE "OriginalFilename", "PrepBuild.exe\000" VALUE "ProductName", "PrepBuild\000" VALUE "ProductVersion", "2020-05-31\000" VALUE "Comments", "<svn range=\"87:92\" modified=\"yes\" url=\"https://svn.osdn.net/svnroot/dzprepbuild/trunk\" />\000" VALUE "Revision", "\000" VALUE "BuildDateTime", "2020-05-31\000" } } BLOCK "VarFileInfo" { VALUE "Translation", 1033, 1252 } } This contains two additional strings: Revision (which is empty) and BuildDateTime. There could be many more. But, how do I get these strings without knowing their name? VerQueryValue requires me to pass it the name of the string I want to read, so that's not a solution. Share this post Link to post
Anders Melander 1782 Posted September 6, 2020 As far as I remember there's no API for that. I think you will have to parse the resource block yourself. If you're lucky the memory layout is the same as the RES layout but even then many of the structures in the VERSIONINFO RES format are undocumented or very poorly documented. The only suggestion I have is to have a look at the source of Collin Willson's old resource editor and see how he did it (with regard to the RES format). 1 Share this post Link to post
dummzeuch 1505 Posted September 6, 2020 There apparently is an official documentation for the VS_VERSIONINFO structure. Which then references the VarFileInfo structure, which in turn refers to the Var Structure, and the StringFileInfo structure, which in turn refers to the StringTable structure, which then refers to the String structure. Not simple but doable. I also found some C++ code to read the VS_VERSIONINFO structure on StackOverflow https://stackoverflow.com/a/43229358/49925 I'll see where it that gets me. Share this post Link to post
dummzeuch 1505 Posted September 6, 2020 Got it to work. Now the GExperts PEInfo tool also shows all strings in all languages stored in the version info resource which includes the non-standard once like BuildDateTime: 1 Share this post Link to post
aehimself 396 Posted September 6, 2020 7 minutes ago, dummzeuch said: Got it to work. Now the GExperts PEInfo tool also shows all strings in all languages stored in the version info resource which includes the non-standard once like BuildDateTime: Nice job. I think soon I'll need something like this in one of my applications - at least I'll know where to look 🙂 Share this post Link to post
dummzeuch 1505 Posted September 6, 2020 There are some odd entries in the resources of BDS.EXE and BDS.fr: And some other executables do not have proper version info: So they do not display anything when read the usual way (like e.g. in the Properties dialog in Windows). Older Delphi versions still refer to Appmethod in the translations (this is XE8): (I definitely had too much time on my hands today. 😉 1 1 Share this post Link to post