cwinapiversioninfo

Programmatically change VersionInfo of a foreign DLL


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?)


Solution

  • 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;
    }