Программно изменить VersionInfo сторонней DLL

Я пытаюсь программно изменить атрибуты VersionInfo файла DLL. Я использовал эту статью в качестве справки.

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

Я думаю, что понял все, что делает код. План состоит в том, чтобы прочитать блок Versioninfo, а затем найти позицию, где, например. CompanyName лежит с помощью VerQueryValue, затем измените данные, а затем запишите их обратно с помощью UpdateResource.

Но есть проблема: VerQueryValue должно выводить позицию, в которой лежит строка CompanyName. Но вместо этого он дает местоположение указателя, которое находится в нескольких сотнях байтов, поэтому он указывает куда-то за пределы структуры VersionInfo.

Что я делаю неправильно и как я могу заставить его работать?

(Кроме того, кто-нибудь знает, есть ли более элегантный способ выполнить эту задачу, может быть, даже снять ограничение на то, что строка должна быть меньше или равна оригиналу?)


person Daniel Marschall    schedule 24.12.2018    source источник
comment
Вы, безусловно, можете разобрать PE-файл и восстановить его с помощью ресурса новой версии. Формат файла PE задокументирован, и существует множество программ с открытым исходным кодом, код которых показывает, как с ними работать.   -  person David Heffernan    schedule 24.12.2018
comment
Я протестировал ваш код и VerQueryValue дал правильное название компании. Так что, возможно, есть проблема с вашим test.dll.   -  person Afshin    schedule 24.12.2018
comment
Вы все неправильно делаете. GetFileVersionInfoSize() дает вам размер буфера памяти, который заполняет GetFileVersionInfo(), а не необработанный ресурс. Это включает подключения, которых нет в необработанных данных ресурсов, особенно если вы используете версию API ANSI. VerQueryValue() возвращает указатели в вашем буфере памяти, а не в необработанном ресурсе. Для того, что вы хотите сделать, вам нужно напрямую проанализировать необработанные данные ресурса, а затем выделить ресурс совершенно новой версии с примененными изменениями. Для этой задачи нельзя использовать API управления версиями.   -  person Remy Lebeau    schedule 24.12.2018
comment
@DavidHeffernan Зачем мне разбирать PE-файл? API BeginUpdateResource, UpdateResource и EndUpdateResource очень надежны, поэтому моя единственная проблема — изменить структуру VersionInfo, которая полностью независима от остальной части PE.   -  person Daniel Marschall    schedule 24.12.2018
comment
@Remy Lebeau Спасибо за подсказки. Я посмотрю дальше. Я начинаю думать, что CodeProject.com иногда является плохим источником эталонного кода...   -  person Daniel Marschall    schedule 24.12.2018
comment
Потому что вы хотите увеличить размер ресурса версии. Вот как я это вижу. Если вы предпочитаете делать это по-своему, ладно, вперед.   -  person David Heffernan    schedule 25.12.2018
comment
Бинарный формат ресурса версии несколько сложен. Я бы искал библиотеку, которая уже обрабатывает синтаксический анализ и построение. Для C# я успешно использовал Vestris Resource Lib, возможно, что-то подобное есть и для C++.   -  person zett42    schedule 25.12.2018


Ответы (1)


Ресурс версии это сериализованное дерево. если вы хотите изменить его - вам нужно десериализовать его в древовидную структуру в памяти, изменить узел и сериализовать в новую память.

несмотря на то, что в msdn определено несколько структур информации о версии< /em>, действительно все они имеют общий формат

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;
}
person RbMm    schedule 25.12.2018
comment
Это один из лучших и обширных ответов, которые я когда-либо получал. Большое вам спасибо за вашу помощь! Аннотация №1: Вчера я уже читал документы MSDN, но, на мой взгляд, многие вещи были запутанными и неоднозначными, например. если valuesize содержит заполнение или нет, и является ли 32-битное выравнивание обязательным или нет, и т. д. Аннотация № 2: я перевел ваш код на C, на случай, если он понадобится другим читателям: pastebin.com/aw7CZ6Ci - person Daniel Marschall; 25.12.2018
comment
@DanielMarschall - да, на мой взгляд, документация о структурах версий не самая лучшая в msdn. по моему мнению, можно описать одну общую структуру (я называю ее RsrcHeader. значение, если оно присутствует, и каждый элемент дочернего массива должен быть выровнен по 4 * x байтам. поэтому возможно 2 байта перед ним. также не очень ясно сказано о wValueLength - это имеет длину в байтах, если wType == 0, или количество wchar_t, если wType == 1 - person RbMm; 25.12.2018
comment
@DanielMarschall - в своем FreeNode вы можете добавить printf("%s%S\n", node->_prefix, node->_name); перед free(node); или просто удалить PCSTR _prefix; из struct RsrcNode. он используется только для вывода отладки при удалении, для видимого просмотра правильной свободной памяти. не нужен код выпуска - person RbMm; 25.12.2018