C++外部程序修改exe文件属性信息

8 篇文章 0 订阅

Windows平台可执行文件(execute文件)属性中会有版本信息,包含文件说明、文件版本、版权等信息。本文主要目的是将设置版本信息的方法公开化。

首先我们要清楚Windows下的可执行文件格式属于PE文件格式标准,PE文件标准支持内嵌资源,就是将一个外部文件内嵌到可执行文件中,这样程序启动时只需从自身内部找到这块资源加载就可以了,而不需依赖其他外部的磁盘文件。

PE文件支持的内嵌资源都有两个必须的标识:一个是资源类型,一个是资源名称。因此只要知道内嵌资源的这两个标识就能找到对应的资源。可执行文件的版本信息就是以内嵌资源的方式保存在文件中。

  • 资源类型和资源名称可以是数字也可以是字符串。
  • 系统已知资源都是以数字来命名的(比如图标资源、版本资源、Manifest资源)。
  • 建议自定义资源都用字符串来命名,避免与系统资源冲突。

微软提供了一组通用API接口,用来获取和设置可执行文件的内嵌资源,请看下面详细介绍。

版本数据获取

获取PE文件的版本信息数据有两种获取方法:

  1. 通用内嵌资源获取方式
  2. 专用API获取方式

通用内嵌资源获取方式

通过下面接口可以获取Windows可执行文件中的任何通用资源。

#include <io.h>
#include <string>

std::string GetPEResource(const char* exepath, const char* type,
                          const char* name, int language = 0)
{
    std::string r = "";

    if (!exepath)
        return r;

    //判定文件是否存在
    if (_access(exepath, 0) != 0)
        return r;

    //加载可执行文件
    HMODULE hexe = LoadLibrary(exepath);
    if (!hexe)
        return r;

    //查找资源
    HRSRC src = FindResourceEx(hexe, type, name, language);
    if (src)
    {
        HGLOBAL glb = LoadResource(hexe, src);
        int sz = SizeofResource(hexe, src);
        r = std::string((char *)LockResource(glb), sz);
        UnlockResource(glb);
        FreeResource(glb);
    }

    //释放可执行文件
    FreeLibrary(hexe);

    return r; //返回一个完整的、标准的版本信息数据
}

通过对 GetPEResource 接口进行简单调用即可获取PE文件中的版本数据。
文件的版本信息以内嵌资源的方式保存在可执行文件中,它的资源类型是RT_VERSION(16),资源名称是 MAKEINTRESOURCE(VS_VERSION_INFO)(1)。

std::string rc = GetPEResource( "C:\\测试文件.exe", RT_VERSION, MAKEINTRESOURCE(VS_VERSION_INFO), 0);

专用API获取方式

微软提供了另外一种专门用于获取文件版本信息的API接口,示例代码如下:

#include <io.h>
#include <string>

std::string GetPEVersionInfo(const char* exepath)
{
    std::string r = "";

    if (!exepath)
        return r;

    if (_access(exepath, 0) != 0)
        return r;

    DWORD sz = GetFileVersionInfoSize(exepath, 0);
    r.resize(sz, 0);
    return GetFileVersionInfo(exepath, 0, sz, (void*)r.c_str()) ? r : "";
}

通过调用此接口,也可正常获取文件中的版本信息数据。但此接口返回的缓冲区是自带备份的,并非标准版本数据。举个例子:假如某exe文件的版本数据大小是N字节,那么调用 GetPEVersionInfo 接口返回的缓冲区大小则是N * 2 + 4字节,前N字节数据为标准的版本数据,中间4字节用 ‘F’ ‘E’ ‘2’ 'X’字符填充,后N个字节用0填充。

版本数据解析

版本数据是格式是一个名叫 VS_VERSIONINFO 的结构体,在msdn上可搜到这个结构体的相关描述。该结构可以认为由三部分构成。
第一部分是一个 VS_FIXEDFILEINFO 对象,包含简要版本信息。
第二部分是一个 VarFileInfo 对象,该部分可能不存在。
第三部分是一个 StringFileInfo 对象,该部分可能不存在。

VS_FIXEDFILEINFO 对象存放在 VS_VERSIONINFO 结构中的 Value 字段中,
VarFileInfo 对象和 StringFileInfo 对象(若存在的话)存放在 VS_VERSIONINFO 结构中的 Children 字段中。

typedef struct {
  WORD             wLength; // VS_VERSIONINFO 结构的长度(以字节为单位)
  WORD             wValueLength; // Value成员的长度(以字节为单位)。 如果没有与当前版本结构关联的 Value 成员,则此值为零。
  WORD             wType; // 版本资源中的数据的类型。 如果版本资源为文本数据,则此成员为 1; 如果版本资源为二进制数据,则为0。
  WCHAR            szKey; // 标识字符串: “VS_VERSION_INFO”
  WORD             Padding1; // 包含在32位边界上 对齐 Value 成员所需的 0 , 使后面字段按4字节对齐。
  VS_FIXEDFILEINFO Value; // 与此 VS_VERSIONINFO 结构关联的数据。 wValueLength 成员指定此成员的长度;如果 wValueLength 为零,则不存在此成员。
  WORD             Padding2; // 在32位边界上对齐 Children 成员所需的 0 。 这些字节不包含在 wValueLength 中。 
  WORD             Children; // 零个或一个 StringFileInfo 结构的数组,以及零个或一个 VarFileInfo 结构。
} VS_VERSIONINFO;
typedef struct tagVS_FIXEDFILEINFO {  
  DWORD dwSignature; // 文件填充信息标识,固定值0xFEEF04BD 
  DWORD dwStrucVersion; // 该结构的32位二进制版本号,高16位是主版本号,低16位是副版本号  
  DWORD dwFileVersionMS; // 该文件二进制版本号的高32bits  
  DWORD dwFileVersionLS; // 该文件二进制版本号的低32bits  
  DWORD dwProductVersionMS; // 发布该文件的产品二进制版本号高32bits  
  DWORD dwProductVersionLS; // 发布该文件的产品二进制版本号低32bits  
  DWORD dwFileFlagsMask; // 比特掩码,标志dwFileFlags的有效位  
  DWORD dwFileFlags; 
  //VS_FF_DEBUG---该文件包含调试信息或是由调试版编译的  
  //VS_FF_INFOINFERRED---文件的版本结构是动态创建的,因此,该结构中有的成员是空的或不正确的  
  //VS_FF_PATCHED---该文件被修改过  
  //VS_FF_PRERELEASE---该文件是开发版,不是商业发布版  
  //VS_FF_PRIVATEBUILD---该文件不是由标准发布步骤构建的  
  //VS_FF_SPECIALBUILD---该文件是由标准发布步骤构建的,但是相同版本号文件的变种  
  DWORD dwFileOS; // 用该文件的操作系统
  DWORD dwFileType; 
  //文件类型:
  // VFT_APP---文件包含一个应用程序  
  // VFT_DLL---文件包含一个DLL  
  // VFT_DRV---文件包含一个设备驱动  
  // VFT_FONT---文件包含一个字体文件  
  // VFT_STATIC_LIB---文件包含一个静态链接库  
  // VFT_UNKNOWN---文件类型未知  
  // VFT_VXD---文件包含一个虚拟设备  
  DWORD dwFileSubtype; // 文件的子类型,由dwFileType决定  
  DWORD dwFileDateMS; // 二进制文件创建日期和时间戳的高32bits  
  DWORD dwFileDateLS; // 二进制文件创建日期和时间戳的低32bits  
} VS_FIXEDFILEINFO;
typedef struct {
  WORD  wLength; // 整个 VarFileInfo 块的长度(以字节为单位,包括 Children 成员指示 的所有 结构)。
  WORD  wValueLength; // 此成员始终等于零。
  WORD  wType; // 版本资源中的数据的类型。 如果版本资源为文本数据,则此成员为 1; 如果版本资源为二进制数据,则为0。
  WCHAR szKey; // Unicode 字符串 L"VarFileInfo"。
  WORD  Padding; // 在32位边界上对齐 Children 成员所需的 0 , 使后面字段按4字节对齐。
  Var   Children; // 通常包含应用程序或 DLL 支持的语言列表信息。
} VarFileInfo;

typedef struct {
  WORD  wLength;
  WORD  wValueLength; // Value字段长度
  WORD  wType;
  WCHAR szKey; // 标识字符串: “Translation”
  WORD  Padding;
  DWORD Value; // 一个或多个值的数组,这些值是语言和代码页标识符对。
} Var;
typedef struct {
  WORD        wLength; // 整个 StringFileInfo 块的长度(以字节为单位)包括 Children 成员指示 的所有 结构。
  WORD        wValueLength; // 此成员始终等于零。
  WORD        wType;
  WCHAR       szKey; // Unicode 字符串 L"StringFileInfo"。
  WORD        Padding;
  StringTable Children; // 一个或多个 StringTable 结构的 数组。 每个 StringTable 结构的 szKey 成员指示用于显示该 StringTable 结构中文本的适当语言和代码页。
} StringFileInfo;

typedef struct {
  WORD   wLength;
  WORD   wValueLength; // 此成员始终等于零。
  WORD   wType;
  WCHAR  szKey; // 作为 Unicode 字符串存储的8位十六进制数。 四个最有效的数字表示语言标识符。 四个最小有效位表示为其设置数据格式的代码页。 每个 Microsoft Standard Language 标识符包含两部分:低序位10位指定主要语言,高序位6位指定子语言。 有关有效标识符的表,请参阅。
  WORD   Padding;
  String Children; // 一个或多个 String 结构的数组。
} StringTable;

typedef struct {
  WORD  wLength;
  WORD  wValueLength; // Value 成员的大小。
  WORD  wType;
  WCHAR szKey; // 任意 Unicode 字符串。 szKey 成员可以是以下一个或多个值。 这些值仅作为准则。如:“Comments” “CompanyName” “FileDescription” "FileVersion"
  WORD  Padding;
  WORD  Value; // 以零结尾的字符串,与 szKey 成员对应的值。
} String;

理解版本数据的格式后,可以按照它的规则将文件版本数据进行重组。

BOOL CUpdateVersionInfo::UpdatePEVersion(CString strFile, CString strCode, CString strValue)
{
	HMODULE hModule = LoadLibraryExW(strFile, NULL, DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
	if (hModule == NULL)
		return FALSE;

	HRSRC hRsrc = FindResourceExW(hModule, RT_VERSION, MAKEINTRESOURCEW(1), klangCN);
	if (hRsrc == NULL) 
		return FALSE;
	
	HGLOBAL hGlobal = LoadResource(hModule, hRsrc);
	if (hGlobal == NULL) 
		return FALSE;
	
	void* pData = LockResource(hGlobal);
	if (pData == NULL)
		return FALSE;
	
	DWORD size = SizeofResource(hModule, hRsrc);
	if (size == 0) 
		return FALSE;

	// 获取 VS_FIXEDFILEINFO
	auto pVersionInfo = reinterpret_cast<const VS_VERSIONINFO*>(pData);
	VS_FIXEDFILEINFO pFixedFileInfo;
	if (pVersionInfo->Header.wValueLength > 0)
		pFixedFileInfo = pVersionInfo->Value;

	if (pFixedFileInfo.dwSignature != SIGNATURE)
	{
		pFixedFileInfo = { 0 };
		pFixedFileInfo.dwSignature = SIGNATURE;
		pFixedFileInfo.dwFileType = VFT_APP;
	}
	/

	// 确定 Value or Children 的位置
	const BYTE* fixedFileInfoEndOffset = reinterpret_cast<const BYTE*>(&pVersionInfo->szKey) + (wcslen(pVersionInfo->szKey) + 1) * sizeof(WCHAR) + pVersionInfo->Header.wValueLength;
	const BYTE* pVersionInfoChildren = reinterpret_cast<const BYTE*>(Round(reinterpret_cast<ptrdiff_t>(fixedFileInfoEndOffset)));
	size_t versionInfoChildrenOffset = pVersionInfoChildren - pData;
	size_t versionInfoChildrenSize = pVersionInfo->Header.wLength - versionInfoChildrenOffset;

	const auto childrenEndOffset = pVersionInfoChildren + versionInfoChildrenSize;
	const auto resourceEndOffset = static_cast<BYTE*>(pData) + size;
	/

	// 获取 StringFileInfo and VarFileInfo 
	std::vector<VersionStringTable> stringTables;
	std::vector<TRANSLATE> supportedTranslations;

	for (auto p = pVersionInfoChildren; p < childrenEndOffset && p < resourceEndOffset;) {
		auto pKey = reinterpret_cast<const VS_VERSION_STRING*>(p)->szKey;
		auto versionInfoChildData = GetChildrenData(p);
		if (wcscmp(pKey, L"StringFileInfo") == 0) {
			DeserializeVersionStringFileInfo(versionInfoChildData.first, versionInfoChildData.second, stringTables);
		}
		else if (wcscmp(pKey, L"VarFileInfo") == 0) {
			DeserializeVarFileInfo(versionInfoChildData.first, supportedTranslations);
		}
		p += Round(reinterpret_cast<const VS_VERSION_STRING*>(p)->Header.wLength);
	}

	if (stringTables.empty())
	{
		TRANSLATE translate = { klangCN, kCodePageCN };
		stringTables.push_back({ translate });
		supportedTranslations.push_back(translate);
	}
	

	if (strCode == "ProductVersion" || strCode == "FileVersion")
	{
		unsigned short ver[4] = { 0 };
		CStringArray sArray;
		this->SplitString(&sArray, strValue, _T("."));
		for (size_t i = 0; i < sArray.GetSize(); i++)
		{
			CStringA s = CStringTochar(sArray[i]);
			ver[i] = atoi(s);
		}
		if (strCode == "ProductVersion")
		{
			pFixedFileInfo.dwProductVersionMS = ver[0] << 16 | ver[1];
			pFixedFileInfo.dwProductVersionLS = ver[2] << 16 | ver[3];
		}
		if (strCode == "FileVersion")
		{
			pFixedFileInfo.dwFileVersionMS = ver[0] << 16 | ver[1];
			pFixedFileInfo.dwFileVersionLS = ver[2] << 16 | ver[3];
		}
	}

	for (auto j = stringTables.begin(); j != stringTables.end(); ++j) 
	{
		auto& stringPairs = j->strings;
		for (auto k = stringPairs.begin(); k != stringPairs.end(); ++k) 
		{
			if (k->first == strCode.AllocSysString())
			{
				k->second = strValue;
				goto stop;
			}
		}
		// Not found, append one for all tables.
		stringPairs.push_back(VersionString(strCode, strValue));
	}

stop:

	FreeLibrary(hModule);
	hModule = NULL;

	// 重组数据
	HANDLE hResource = BeginUpdateResource(strFile.AllocSysString(), FALSE);
	if (NULL == hResource)
		return FALSE;

	VersionStampValue versionInfo;
	versionInfo.key = L"VS_VERSION_INFO";
	versionInfo.type = 0;
	auto fixedsize = sizeof(VS_FIXEDFILEINFO);
	versionInfo.valueLength = fixedsize;

	auto& dst = versionInfo.value;
	dst.resize(fixedsize);

	memcpy(&dst[0], &pFixedFileInfo, fixedsize);

	{
		VersionStampValue stringFileInfo;
		stringFileInfo.key = L"StringFileInfo";
		stringFileInfo.type = 1;
		stringFileInfo.valueLength = 0;

		for (const auto& iTable : stringTables) {
			VersionStampValue stringTableRaw;
			stringTableRaw.type = 1;
			stringTableRaw.valueLength = 0;

			{
				auto& translate = iTable.encoding;
				std::wstringstream ss;
				ss << std::hex << std::setw(8) << std::setfill(L'0') << (translate.wLanguage << 16 | translate.wCodePage);
				stringTableRaw.key = ss.str();
			}

			for (const auto& iString : iTable.strings) {
				const auto& stringValue = iString.second;
				auto strLenNullTerminated = stringValue.length() + 1;

				VersionStampValue stringRaw;
				stringRaw.type = 1;
				stringRaw.key = iString.first;
				stringRaw.valueLength = strLenNullTerminated;

				auto size = strLenNullTerminated * sizeof(WCHAR);
				auto& dst = stringRaw.value;
				dst.resize(size);

				auto src = stringValue.c_str();

				memcpy(&dst[0], src, size);

				stringTableRaw.children.push_back(std::move(stringRaw));
			}

			stringFileInfo.children.push_back(std::move(stringTableRaw));
		}

		versionInfo.children.push_back(std::move(stringFileInfo));
	}

	{
		VersionStampValue varFileInfo;
		varFileInfo.key = L"VarFileInfo";
		varFileInfo.type = 1;
		varFileInfo.valueLength = 0;

		{
			VersionStampValue varRaw;
			varRaw.key = L"Translation";
			varRaw.type = 0;

			{
				auto newValueSize = sizeof(DWORD);
				auto& dst = varRaw.value;
				dst.resize(supportedTranslations.size() * newValueSize);

				for (auto iVar = 0; iVar < supportedTranslations.size(); ++iVar) {
					auto& translate = supportedTranslations[iVar];
					auto var = DWORD(translate.wCodePage) << 16 | translate.wLanguage;
					memcpy(&dst[iVar * newValueSize], &var, newValueSize);
				}

				varRaw.valueLength = varRaw.value.size();
			}

			varFileInfo.children.push_back(std::move(varRaw));
		}

		versionInfo.children.push_back(std::move(varFileInfo));
	}

	std::vector<BYTE> out = std::move(versionInfo.Serialize());

	// 更新数据
	if (!UpdateResourceW(hResource, RT_VERSION, MAKEINTRESOURCEW(1), klangCN,
		&out[0], static_cast<DWORD>(out.size()))) {
		return FALSE;
	}

	BOOL bResult = EndUpdateResourceW(hResource, FALSE);
	return bResult ? TRUE : FALSE;
}

通过重组数据得到一个内存块,将这些数据呈现到exe可执行文件的属性中,就需将此数据注入到文件中

#include <string>

bool SetPEResource(const char* exepath, const char* type, const char* name, const std::string& value, int language = 0)
{
    HANDLE hexe = BeginUpdateResource(exepath, FALSE);
    if (!hexe)
        return false;

    BOOL r = UpdateResource(hexe, type, name, language,
        (LPVOID)value.c_str(), (DWORD)value.size());

    BOOL er = EndUpdateResource(hexe, FALSE);

    return (r && er);
}
问题处理
  1. 无法解析的外部符号GetFileVersion…
    #pragma comment(lib, “version.lib”)

  2. BSTR
    https://blog.csdn.net/qq_21232339/article/details/51220592
    https://blog.csdn.net/shihuaguo/article/details/8541172

  3. 不进行数据重组的情况下修改,只能在原来的位数上修改,无法修改超过原本字段位数,若超过原本字段位数,后面的重要内容将会丢失

分享代码链接
COM接口以及测试程序 : https://download.csdn.net/download/sinat_38626955/85024858
rcedit开源 :
https://github.com/electron/rcedit
https://gitee.com/freeasm/rcedit.git

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值