以编程方式更改外部 DLL 的 VersionInfo

Programmatically change VersionInfo of a foreign DLL

我正在尝试以编程方式更改 DLL 文件的 VersionInfo 属性。我使用 this article 作为参考。

#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[=10=]"
        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;       
}

我想我已经理解代码所做的一切。计划是读取 Versioninfo 块,然后找到位置,例如CompanyName 使用 VerQueryValue,然后更改数据,然后使用 UpdateResource 将其写回。

但是有一个问题:VerQueryValue应该输出CompanyName字符串所在的位置。但是相反,它给出了一个指针位置,距离几百个字节,所以它指向 VersionInfo 结构之外的某个地方。

我哪里做错了,我该如何让它发挥作用?

(另外,有人知道是否有更优雅的方法来完成这项任务,甚至可以消除字符串必须小于或等于原始字符串的限制吗?)

版本资源这是序列化树。如果你想修改它 - 你需要将它反序列化为内存中的树结构,修改节点,然后序列化到新内存。

尽管在msdn中定义了几个Version Information Structures,但实际上它们都有共同的格式

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[];
};

所以可以编写通用的解析和序列化程序

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

有了这个辅助结构,我们可以通过以下方式更新版本:

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