0

I am trying to programmatically change the VersionInfo attributes of a DLL file. I used this article as reference.

#include <iostream>
#include <stdio.h>
#include <windows.h>

int main(int argc, char** argv) {
    LPCTSTR lpszFile = "E:\\_test\\rand_test\\test.dll";
    DWORD   dwHandle,
            dwSize;

    struct {
        WORD wLanguage;
        WORD wCodePage;
    } *lpTranslate;

    // determine the size of the resource information
    dwSize = GetFileVersionInfoSize(lpszFile, &dwHandle);
    if (0 < dwSize)
    {
        unsigned char* lpBuffer = (unsigned char*) malloc(dwSize);

        // Get whole VersionInfo resource/structure 
        GetFileVersionInfo(lpszFile, 0, dwSize, lpBuffer);

        char strSubBlock[37]; // fits "\\StringFileInfo\\xxxxxxxx\\CompanyName\0"
        LPTSTR pValueBuffer;

        HANDLE hResource = BeginUpdateResource(lpszFile, FALSE);
        if (NULL != hResource)
        {
            UINT uTemp;

            // get the language information
            if (!VerQueryValue(lpBuffer, "\\VarFileInfo\\Translation", (LPVOID *) &lpTranslate, &uTemp) != FALSE)
            {
                printf("Error 1\n");
                return 1;
            }

            sprintf(strSubBlock, "\\StringFileInfo\\%04x%04x\\CompanyName", lpTranslate->wLanguage, lpTranslate->wCodePage);

            if (!VerQueryValue(lpBuffer, (LPTSTR) ((LPCTSTR) strSubBlock), (LPVOID *) &pValueBuffer, &uTemp)) {
                printf("Error 2\n");
                return 1;
            }
            // PROBLEM!!!
            // (pValueBuffer-lpBuffer) is 0x438 (longer than the Versioninfo resource!) but should be 0xB8
            // so, pValueBuffer does not point to the actual company name.

            ZeroMemory(pValueBuffer, strlen(pValueBuffer) * sizeof(TCHAR));
            strcpy(pValueBuffer, "My Company, Inc."); // String may only become smaller or equal, never bigger than strlen(pValueBuffer)

            if (UpdateResource(hResource,
                               RT_VERSION,
                               MAKEINTRESOURCE(VS_VERSION_INFO),
                               lpTranslate->wLanguage, // or 0
                               lpBuffer,
                               dwSize) != FALSE)
            {
                EndUpdateResource(hResource, FALSE);
            }
        }
        free(lpBuffer);
    }

    return 0;       
}

I think I have understood everything the code does. The plan is to read the Versioninfo block, then find the position where e.g. the CompanyName lies using VerQueryValue, then change the data, and then write it back using UpdateResource.

But there is a problem: VerQueryValue should output the position where the CompanyName string lies. But instead, it gives a pointer location, which is a few hundred bytes away, so it points somewhere outside the VersionInfo structure.

What am I doing wrong and how can I make it work?

(Also, does anybody know if there is a more elegant way to do this task, maybe even remove the limitation that the string has to be smaller or equal to the original?)

Daniel Marschall
  • 3,739
  • 2
  • 28
  • 67
  • You can certainly deconstruct the PE file, and reconstruct it with a new version resource. The PE file format is documented, and there are many ope source programs with code showing how to work with them. – David Heffernan Dec 24 '18 at 18:37
  • I tested your code and `VerQueryValue` gave correct CompanyName. So maybe there is a problem with your `test.dll`. – Afshin Dec 24 '18 at 18:50
  • 3
    You are going about this all wrong. `GetFileVersionInfoSize()` gives you the size of the memory buffer that `GetFileVersionInfo()` populates, not the raw resource. That includes hookups that are not in the raw resource data, especially if you are using the ANSI version of the API. `VerQueryValue()` returns pointers in your memory buffer, not in the raw resource. For what you want to do, you need to parse the raw resource data directly, then allocate a completely new version resource with modifications applied. You can't use the versioning API for this task. – Remy Lebeau Dec 24 '18 at 19:03
  • @DavidHeffernan Why do I need to deconstruct the PE file? The BeginUpdateResource, UpdateResource and EndUpdateResource APIs are very reliable, so my "only" problem is to modify the VersionInfo structure, which is completely independent of the rest of the PE. – Daniel Marschall Dec 24 '18 at 19:09
  • @Remy Lebeau Thank you for the hints. I will look further into it. I begin to think that CodeProject.com is sometimes a bad source of reference code... – Daniel Marschall Dec 24 '18 at 19:09
  • Because you want to increase the size of the version resource. That's the way I see it. If you'd rather do it your way, fine, go ahead. – David Heffernan Dec 24 '18 at 21:36
  • Version resource binary format is somewhat complicated. I would look for a library that already handles parsing and construction. For C# I have successfully used [Vestris Resource Lib](https://github.com/resourcelib/resourcelib), there is propably something like this for C++ too. – zett42 Dec 24 '18 at 22:16

1 Answers1

1

version resource this is serialized tree. if you want modify it - you need deserialize it to tree structure in memory, modify node, and serialize to new memory.

despite in msdn defined several Version Information Structures, really all it have common format

struct RsrcHeader 
{
    WORD  wLength; 
    WORD  wValueLength; 
    WORD  wType; 
    WCHAR szKey[];
    // aligned on 4*n
    // BYTE Value[wValueLength];  // if (wType == 0)
    // or
    // WCHAR Value[wValueLength]; // if (wType == 1)
    // every element aligned on 4*n
    // RsrcHeader childs[];
};

so possible write common parse and serialize procedures

#if DBG
#define DBG_OPT(x) _CRT_UNPARENTHESIZE(x)
#else
#define DBG_OPT(x)
#endif

class RsrcNode
{
    struct RsrcHeader 
    {
        WORD  wLength; 
        WORD  wValueLength; 
        WORD  wType; 
        WCHAR szKey[];
    };

    C_ASSERT(sizeof(RsrcHeader) == 6);

    RsrcNode* _first, *_next;
    PCWSTR _name;
    const void* _pvValue;
    ULONG _cbValue;
    WORD  _wValueLength; 
    WORD  _wType; 

    DBG_OPT((PCSTR _prefix)); // only for debug output

public:

    bool ParseResourse(PVOID buf, ULONG size, ULONG* pLength, PCSTR prefix);

    RsrcNode(DBG_OPT((PCSTR prefix = "")))
        : _next(0), _first(0) DBG_OPT((, _prefix(prefix)))
    {
    }

    ~RsrcNode();

    bool IsStringValue() const
    {
        return _wType;
    }

    const void* getValue(ULONG& cb)
    {
        cb = _cbValue;
        return _pvValue;
    }

    void setValue(const void* pv, ULONG cb)
    {
        _pvValue = pv, _cbValue = cb;
        _wValueLength = (WORD)(_wType ? cb / sizeof(WCHAR) : cb);
    }

    RsrcNode* find(const PCWSTR strings[], ULONG n);

    ULONG GetSize() const;

    PVOID Store(PVOID buf, ULONG* pcb) const;
};

bool RsrcNode::ParseResourse(PVOID buf, ULONG size, ULONG* pLength, PCSTR prefix)
{
    union {
        PVOID pv;
        RsrcHeader* ph;
        ULONG_PTR up;
        PCWSTR sz;
    };

    pv = buf;

    if (size < sizeof(RsrcHeader) || (up & 3))
    {
        return false;
    }

    WORD wType = ph->wType;
    ULONG wValueLength = ph->wValueLength, wLength = ph->wLength;
    ULONG cbValue = 0;

    switch (wType)
    {
    case 1:
        cbValue = wValueLength * sizeof(WCHAR);
        break;
    case 0:
        cbValue = wValueLength;
        break;
    default:
        return false;
    }

    *pLength = wLength;

    if (wLength > size || wLength < sizeof(RsrcHeader) || cbValue >= (wLength -= sizeof(RsrcHeader)))
    {
        return false;
    }

    wLength -= cbValue;

    sz = ph->szKey, _name = sz;

    do 
    {
        if (wLength < sizeof(WCHAR))
        {
            return false;
        }

        wLength -= sizeof(WCHAR);

    } while (*sz++);

    DbgPrint("%s%S {\n", prefix, _name);

    if (up & 3)
    {
        if (wLength < 2)
        {
            return false;
        }
        up += 2, wLength -= 2;
    }

    _wType = wType, _wValueLength = (WORD)wValueLength, _cbValue = cbValue, _pvValue = pv;

    if (wValueLength && wType)
    {
        if (sz[wValueLength - 1])
        {
            return false;
        }
        DbgPrint("%s\t%S\n", prefix, sz);
    }

    if (wLength)
    {
        if (!*--prefix) return false;

        up += wValueLength;

        do 
        {
            if (up & 3)
            {
                if (wLength < 2)
                {
                    return false;
                }

                up += 2;

                if (!(wLength -= 2))
                {
                    break;
                }
            }

            if (RsrcNode* node = new RsrcNode(DBG_OPT((prefix))))
            {
                node->_next = _first, _first = node;

                if (node->ParseResourse(ph, wLength, &size, prefix))
                {
                    continue;
                }
            }

            return false;

        } while (up += size, wLength -= size);

        prefix++;
    }

    DbgPrint("%s}\n", prefix);

    return true;
}

RsrcNode::~RsrcNode()
{
    if (RsrcNode* next = _first)
    {
        do 
        {
            RsrcNode* cur = next;
            next = next->_next;
            delete cur;
        } while (next);
    }

    DBG_OPT((DbgPrint("%s%S\n", _prefix, _name)));
}

RsrcNode* RsrcNode::find(const PCWSTR strings[], ULONG n)
{
    PCWSTR str = *strings++;

    if (!str || !wcscmp(str, _name))
    {
        if (!--n)
        {
            return this;
        }

        if (RsrcNode* next = _first)
        {
            do 
            {
                if (RsrcNode* p = next->find(strings, n))
                {
                    return p;
                }
            } while (next = next->_next);
        }
    }

    return 0;
}

ULONG RsrcNode::GetSize() const
{
    ULONG size = sizeof(RsrcHeader) + (1 + (ULONG)wcslen(_name)) * sizeof(WCHAR);

    if (_cbValue)
    {
        size = ((size + 3) & ~3) + _cbValue;
    }

    if (RsrcNode* next = _first)
    {
        do 
        {
            size = ((size + 3) & ~3) + next->GetSize();
        } while (next = next->_next);
    }

    return size;
}

PVOID RsrcNode::Store(PVOID buf, ULONG* pcb) const
{
    union {
        RsrcHeader* ph;
        ULONG_PTR up;
        PVOID pv;
    };

    pv = buf;

    ph->wType = _wType;
    ph->wValueLength = _wValueLength;

    ULONG size = (1 + (ULONG)wcslen(_name)) * sizeof(WCHAR), cb;

    memcpy(ph->szKey, _name, size);

    up += (size += sizeof(RsrcHeader));

    if (_cbValue)
    {
        up = (up + 3) & ~3;
        memcpy(pv, _pvValue, _cbValue);
        up += _cbValue;
        size = ((size + 3) & ~3) + _cbValue;
    }

    if (RsrcNode* next = _first)
    {
        do 
        {
            up = (up + 3) & ~3;
            pv = next->Store(pv, &cb);
            size = ((size + 3) & ~3) + cb;
        } while (next = next->_next);
    }

    reinterpret_cast<RsrcHeader*>(buf)->wLength = (WORD)size;

    *pcb = size;

    return pv;
}

with this helper structure we can update version in next way:

#include "VerHlp.h"

BOOL UpdateVersion(PVOID pvVersion, ULONG cbVersion, PVOID& pvNewVersion, ULONG& cbNewVersion)
{
    BOOL fOk = FALSE;

    char prefix[16];
    memset(prefix, '\t', sizeof(prefix));
    prefix[RTL_NUMBER_OF(prefix) - 1] = 0;
    *prefix = 0;

    if (RsrcNode* node = new RsrcNode)
    {
        if (node->ParseResourse(pvVersion, cbVersion, &cbVersion, prefix + RTL_NUMBER_OF(prefix) - 1))
        {
            static const PCWSTR str[] = {
                L"VS_VERSION_INFO", L"StringFileInfo", 0, L"CompanyName"
            };

            if (RsrcNode *p = node->find(str, RTL_NUMBER_OF(str)))
            {
                if (p->IsStringValue())
                {
                    ULONG cb;
                    const void* pvCompanyName = p->getValue(cb);
                    DbgPrint("CompanyName: %S\n", pvCompanyName);

                    static WCHAR CompanyName[] = L"[ New Company Name ]";

                    if (cb != sizeof(CompanyName) || 
                        memcmp(pvCompanyName, CompanyName, sizeof(CompanyName)))
                    {
                        p->setValue(CompanyName, sizeof(CompanyName));

                        cbVersion = node->GetSize();

                        if (pvVersion = LocalAlloc(0, cbVersion))
                        {
                            node->Store(pvVersion, &cbNewVersion);
                            pvNewVersion = pvVersion;
                            fOk = TRUE;
                        }
                    }
                }
            }
        }
        delete node;
    }

    return fOk;
}

struct EnumVerData 
{
    HANDLE hUpdate;
    BOOL fDiscard;
};

BOOL CALLBACK EnumResLangProc(HMODULE hModule,
                              PCWSTR lpszType,
                              PCWSTR lpszName,
                              WORD wIDLanguage,
                              EnumVerData* Ctx
                              )
{
    if (HRSRC hResInfo = FindResourceExW(hModule, lpszType, lpszName, wIDLanguage))
    {
        if (HGLOBAL hg = LoadResource(hModule, hResInfo))
        {
            if (ULONG size = SizeofResource(hModule, hResInfo))
            {
                if (PVOID pv = LockResource(hg))
                {
                    if (UpdateVersion(pv, size, pv, size))
                    {
                        if (UpdateResource(Ctx->hUpdate, lpszType, lpszName, wIDLanguage, pv, size))
                        {
                            Ctx->fDiscard = FALSE;
                        }

                        LocalFree(pv);
                    }
                }
            }
        }
    }

    return TRUE;
}

ULONG UpdateVersion(PCWSTR FileName)
{
    ULONG dwError = NOERROR;

    EnumVerData ctx;

    if (ctx.hUpdate = BeginUpdateResource(FileName, FALSE))
    {
        ctx.fDiscard = TRUE;

        if (HMODULE hmod = LoadLibraryExW(FileName, 0, LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE))
        {
            if (!EnumResourceLanguages(hmod, RT_VERSION, 
                MAKEINTRESOURCE(VS_VERSION_INFO), 
                (ENUMRESLANGPROCW)EnumResLangProc, (LONG_PTR)&ctx))
            {
                dwError = GetLastError();
            }

            FreeLibrary(hmod);
        }
        else
        {
            dwError = GetLastError();
        }

        if (!dwError && !EndUpdateResourceW(ctx.hUpdate, ctx.fDiscard))
        {
            dwError = GetLastError();
        }
    }
    else
    {
        dwError = GetLastError();
    }

    return dwError;
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • This is one of the best and extensively answers I ever received. Thank you so much for your help! **Annotation #1:** Yesterday, I already had read the MSDN docs, but in my opinion a lot of things were confusing and ambiguous, e.g. if the valuesize contains the padding or not, and if the 32bit alignment is mandatory or not, etc. **Annotation #2:** I have translated your code to C, in case other readers need it: https://pastebin.com/aw7CZ6Ci – Daniel Marschall Dec 25 '18 at 15:04
  • @DanielMarschall - yes, for my look documentation about version structs not the best in msdn. from my opinion possible describe one common structure (i name it `RsrcHeader`. the value if present and every element of child array must be aligned on 4*x bytes. so possible 2 bytes pad before it. also not very clear stated about `wValueLength` - this is of byte length, if `wType == 0` or count of `wchar_t` if `wType == 1` – RbMm Dec 25 '18 at 15:35
  • @DanielMarschall - in your `FreeNode` you can add `printf("%s%S\n", node->_prefix, node->_name);` before `free(node);` or simply remove `PCSTR _prefix;` from `struct RsrcNode`. it used only for debug output on delete, for visible view correct free memory. not need in release code – RbMm Dec 25 '18 at 15:44