1

I'm trying to retrieve a file description from a PE file using the following code:

//This code was simplified &
//most error checks were removed for brevity

BYTE* pData = new BYTE[4096];
LPCTSTR path = L"C:\\Windows\\system32\\Speech\\Engines\\TTS\\MSTTSEngine.dll";
if(::GetFileVersionInfo(path, NULL, 4096, pData))
{
    struct LANGANDCODEPAGE
    {
        WORD wLanguage;
        WORD wCodePage;
    } *lpTranslate = NULL;

    UINT cbTranslate;
    if(VerQueryValue(pData,  L"\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &cbTranslate))
    {
        CString strBlock;
        strBlock.Format(L"\\StringFileInfo\\%04x%04x\\FileDescription", 
            lpTranslate[0].wLanguage,
            lpTranslate[0].wCodePage
            );

        UINT dwProdLn = 0;
        VOID* lpBufferName = NULL;
        if(VerQueryValue(pData, strBlock, &lpBufferName, &dwProdLn))
        {
            TRACE(L"Description: %s", lpBufferName);
        }
        else
        {
            TRACE(L"Error=%d", ::GetLastError());
        }
    }

    delete[] pData;
}

That specific file (here's the copy if you don't have it on your Windows 10) has the string table encoded with wLanguage being 0 and wCodePage being 1200. In that case VerQueryValue fails with error code ERROR_RESOURCE_TYPE_NOT_FOUND. But I know that that file has "file description" property when I check it in File Explorer:

enter image description here

So what am I doing wrong in my code above?

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • @RemyLebeau: thank you for locking up this question with that useless answer. That proposed heuristic guessing is not better than just doing this: `for(DWORD langID=0; langID<=0xFFFF; langID++){do_guessing_here();}`. For anyone who wants to see a real solution, there are actually two proposed below. (Another solution, very cool and different one is proposed in the comments to that answer by RbMm.) – c00000fd Jan 29 '19 at 00:48

1 Answers1

4

Looking at MSTTSEngine.dll using Resource Hacker, there is an inconsistency in the resource data. Language ID defined by VarFileInfo\Translation is 0x0000, whereas StringFileInfo defines 0x0409. Only the CodePage value matches.

BLOCK "StringFileInfo"
{
    BLOCK "040904b0"
    //     ^^^^ -> Problem
    {
        VALUE "CompanyName", "Microsoft Corporation"
        VALUE "FileDescription", "Microsoft TTS Engine (Desktop)"
        // [...]
    }
}

BLOCK "VarFileInfo"
{
    VALUE "Translation", 0x0000 0x04B0
    //                     ^^^^ -> Problem
}

So your code tries to read StringFileInfo\000004B0, which doesn't exist. Unfortunately such cases of broken version resources are not handled well by VerQueryValue, because there is no way to enumerate the StringFileInfo blocks independently from VarFileInfo.

Solution

There is an alternative way to get version resource information using the shell property API. I have given an example in this answer. On my machine, it correctly reads the file description from MSTTSEngine.dll.

zett42
  • 25,437
  • 3
  • 35
  • 72
  • A [similar case](https://stackoverflow.com/q/37953472/7571258), just to link these questions together. – zett42 Jan 28 '19 at 11:09
  • 2
    possible and yourself write small class for parse version resource to tree structure and use - https://pastebin.com/GuHxqa45 – RbMm Jan 28 '19 at 16:01
  • @RbMm: wow, that's a very cool low-level approach. Thanks for sharing! One question though, I'm trying to check your parser code, where did you take that format of the version resource from? – c00000fd Jan 28 '19 at 23:09
  • @c00000fd - this is documented https://learn.microsoft.com/en-us/windows/desktop/menurc/version-information-structures code is of course working. i only generalize information from msdn. realy if look more close - all items have the common format – RbMm Jan 28 '19 at 23:13
  • @c00000fd - also this code can be used for modify resourse nodes, and store it via `RsrcNode::Store` and than use in call `UpdateResource` – RbMm Jan 28 '19 at 23:18
  • @RbMm: yeah, thanks. I can see that. I was also hoping to eliminate those `FindResourceW`, `LoadResource`, `SizeofResource`, and `LockResource` calls and just parse PE structure directly as `RtlImageNtHeader` does. What I'm curious is how to get base-address from `HMODULE` that `LoadLibraryEx` returns? It seems like it generates `HMODULE` by adding 2 bytes to the base-address with `LOAD_LIBRARY_AS_IMAGE_RESOURCE` flag and 1 byte with `LOAD_LIBRARY_AS_DATAFILE` flag. Is it documented anywhere? – c00000fd Jan 29 '19 at 00:43
  • @c00000fd -but `PAGE_ALIGN(hmod)` and is base-address from. [`#define LDR_IS_IMAGEMAPPING(handle)`](https://learn.microsoft.com/en-us/windows/desktop/api/libloaderapi/nf-libloaderapi-loadlibraryexa) but however if you parse pe yourself this will be not much more better (if at all) compare `FindResourceW`. the best of course use `LdrFindResource_U / LdrAccessResource` pair. not need separate SizeofResource, and LockResource here – RbMm Jan 29 '19 at 00:55
  • @RbMm: well, I'm doing it from the user land. So thanks. I missed that macro. I was basically doing this instead `(DWORD_PTR&)lpBaseAddress &= ~0xFFF;` which is the same thing. – c00000fd Jan 29 '19 at 01:43
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/187456/discussion-between-rbmm-and-c00000fd). – RbMm Jan 29 '19 at 01:54