替换 EXE 和 DLL 文件中的图标资源






4.86/5 (18投票s)
一个简单的例子,说明如何读取 *.ico 文件以及如何替换图标资源。
引言
本文旨在解释与 ICO 文件和图标资源相关的结构。代码示例将演示这些结构的使用以及如何将这些结构嵌入到 DLL 文件中。
背景
我开始开发自己的资源破解器,因为我需要自动化 DLL 文件中的字符串和图标替换。我开始在 MSDN 上阅读有关使用库以及处理资源的基本函数的文章。
我了解到枚举资源和打开资源并不困难,但当你真正想对该资源做些什么,例如修改或替换它时,问题就出现了。
最有用的文章是:《二进制资源格式》。在这里,我找到了关于处理 OpenResource
函数返回的字节数组所需的大部分信息。
要了解我使用的不同函数的工作原理,您应该查看 MSDN:《资源参考》。
使用代码
我将首先解释与 ICO 文件和图标资源相关的结构。ICO 文件是一个按照一些简单规则排列的字节数组。ICO 文件由一个放置在文件开头的 ICONDIR
结构组成。
typedef struct
{
WORD idReserved; // Reserved
WORD idType; // resource type (1 for icons)
WORD idCount; // how many images?
LPICONDIRENTRY idEntries; // the entries for each image
} ICONDIR, *LPICONDIR;
idReserved
始终为 0。idType
对于图标文件为 1。idCount
是 ICO 文件包含的图像数量。idEntries
是一个指向idCount
结构的指针,这些结构描述了文件中的图像(这些结构紧跟在ICONDIR
之后写入文件)。这些结构的类型是ICONDIRENTRY
。
typedef struct
{
BYTE bWidth; // Width of the image
BYTE bHeight; // Height of the image (times 2)
BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
BYTE bReserved; // Reserved
WORD wPlanes; // Color Planes
WORD wBitCount; // Bits per pixel
DWORD dwBytesInRes; // how many bytes in this resource?
DWORD dwImageOffset; // where in the file is this image
} ICONDIRENTRY, *LPICONDIRENTRY;
dwBytesInRes
表示图像的总大小,即表示实际图像的ICONIMAGE
结构的总大小。dwImageOffset
表示 ICO 文件中可以找到该图像的ICONIMAGE
结构的偏移量。
我还没有真正尝试通过修改 ICONIMAGE
字段来改变图像的外观,所以我只能说图像具有以下结构
BITMAPINFOHEADER icHeader;
RGBQUAD icColors[1];
BYTE icXOR[1];
BYTE icAND[1];
并且 BITMAPINFOHEADER
具有以下字段,但只使用了 Size
、Width
、Height
、Planes
、BitCount
和 SizeImage
。其余为 0
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
在我的程序中,我定义了以下结构
typedef struct
{
UINT Width, Height, Colors; // Width, Height and bpp
LPBYTE lpBits; // ptr to DIB bits
DWORD dwNumBytes; // how many bytes?
LPBITMAPINFO lpbi; // ptr to header
LPBYTE lpXOR; // ptr to XOR image bits
LPBYTE lpAND; // ptr to AND image bits
} ICONIMAGE, *LPICONIMAGE;
要了解更多关于如何使用此结构的信息,您应该查看此源代码:《ICONS.c》。
那么现在,让我们总结一下如何读取 ICO 文件
首先,读取 ICONDIR
结构
ReadFile( hFile1, &(pIconDir->idReserved), sizeof( WORD ), &dwBytesRead, NULL );
ReadFile( hFile1, &(pIconDir->idType), sizeof( WORD ), &dwBytesRead, NULL );
ReadFile( hFile1, &(pIconDir->idCount), sizeof( WORD ), &dwBytesRead, NULL );
接下来,读取 idCount
和 ICONDIRENTRY
结构
for(i=0;i<pIconDir->idCount;i++)
{
ReadFile( hFile1, &pIconDir->idEntries[i], sizeof(ICONDIRENTRY),
&dwBytesRead, NULL );
}
现在,对于每个 ICONDIRENTRY
,找到 ICO 文件中表示实际图像(漂亮的彩色像素)的关联 ICONIMAGE
结构,并读取它。
for(i=0;i<pIconDir->idCount;i++)
{
pIconImage = (LPICONIMAGE)malloc( pIconDir->idEntries[i].dwBytesInRes );
SetFilePointer( hFile1, pIconDir->idEntries[i].dwImageOffset,NULL, FILE_BEGIN );
ReadFile( hFile1, pIconImage, pIconDir->idEntries[i].dwBytesInRes,
&dwBytesRead, NULL);
arrayIconImage[i]=(LPICONIMAGE)malloc( pIconDir->idEntries[i].dwBytesInRes );
memcpy( arrayIconImage[i],pIconImage, pIconDir->idEntries[i].dwBytesInRes );
free(pIconImage);
}
现在,您拥有了表示 ICO 文件的所有结构。
DLL 文件中使用的结构与我上面描述的几乎相同。所以,既然您知道 ICO 文件的结构,修改 DLL 中的图标资源将非常容易。
让我们看看我用于更新 DLL 的结构。我只会指出与上述结构之间的区别
typedef struct
{
WORD idReserved; // Reserved
WORD idType; // resource type (1 for icons)
WORD idCount; // how many images?
LPMEMICONDIRENTRY idEntries; // the entries for each image
} MEMICONDIR, *LPMEMICONDIR;
MEMICONDIR
与 ICONDIR
相同。
typedef struct
{
BYTE bWidth; // Width of the image
BYTE bHeight; // Height of the image (times 2)
BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
BYTE bReserved; // Reserved
WORD wPlanes; // Color Planes
WORD wBitCount; // Bits per pixel
DWORD dwBytesInRes; // how many bytes in this resource?
WORD nID; // the ID
} MEMICONDIRENTRY, *LPMEMICONDIRENTRY;
正如您所看到的,对于资源条目,我们不再需要 dwImageOffset
字段。原因是我们可以使用捆绑 ID(nID
)在 DLL 中查找或更新图像资源。FindResource
和 UpdateResource
需要知道资源的类型(lbType
)、语言(langId
)和捆绑 ID(lpName
)。
ICO 资源在 DLL 中的保存方式如下
RT_GROUP_ICON
资源包含 MEMICONDIR
和 MEMICONDIRENTRY
结构。RT_GROUP_ICON
是 VS 中的资源编辑器用来显示图标的内容。如果这些资源不存在,您就无法在资源编辑器中看到图标资源。
对于每个 MEMICONDIRENTRY
,都有一个 RT_ICON
资源,其捆绑 ID 由 MEMICONDIRENTRY
结构中的 nID
字段标识。
现在,我们可以使用在 ICO 文件中找到的结构来替换 DLL 中的图标资源(我们知道要替换的资源的捆绑 ID(lpName
)、语言(langID
)和类型 RT_GROUP_ICON
)。
hUi = LoadLibraryExA(lpFileName,NULL,
DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
HRSRC hRsrc = FindResourceEx(hUi, RT_GROUP_ICON, lpName,langId);
HGLOBAL hGlobal = LoadResource( hUi, hRsrc );
test1 =(BYTE*) LockResource( hGlobal );
首先,我们使用 test1
读取 DLL 中已存在的资源。我们构建 lpGrpIconDir
以保存最初使用的 RT_ICON
捆绑 ID。
LPMEMICONDIR lpGrpIconDir=new MEMICONDIR;
lpGrpIconDir->idReserved=pIconDir->idReserved;
lpGrpIconDir->idType=pIconDir->idType;
lpGrpIconDir->idCount=pIconDir->idCount;
cbRes=3*sizeof(WORD)+lpGrpIconDir->idCount*sizeof(MEMICONDIRENTRY);
test=new BYTE[cbRes];
temp=test;
CopyMemory(test,&lpGrpIconDir->idReserved,sizeof(WORD));
test=test+sizeof(WORD);
CopyMemory(test,&lpGrpIconDir->idType,sizeof(WORD));
test=test+sizeof(WORD);
CopyMemory(test,&lpGrpIconDir->idCount,sizeof(WORD));
test=test+sizeof(WORD);
lpGrpIconDir->idEntries=new MEMICONDIRENTRY[lpGrpIconDir->idCount];
for(i=0;i<lpGrpIconDir->idCount;i++)
{
lpGrpIconDir->idEntries[i].bWidth=pIconDir->idEntries[i].bWidth;
CopyMemory(test,&lpGrpIconDir->idEntries[i].bWidth,sizeof(BYTE));
test=test+sizeof(BYTE);
lpGrpIconDir->idEntries[i].bHeight=pIconDir->idEntries[i].bHeight;
CopyMemory(test,&lpGrpIconDir->idEntries[i].bHeight,sizeof(BYTE));
test=test+sizeof(BYTE);
lpGrpIconDir->idEntries[i].bColorCount=pIconDir->idEntries[i].bColorCount;
CopyMemory(test,&lpGrpIconDir->idEntries[i].bColorCount,sizeof(BYTE));
test=test+sizeof(BYTE);
lpGrpIconDir->idEntries[i].bReserved=pIconDir->idEntries[i].bReserved;
CopyMemory(test,&lpGrpIconDir->idEntries[i].bReserved,sizeof(BYTE));
test=test+sizeof(BYTE);
lpGrpIconDir->idEntries[i].wPlanes=pIconDir->idEntries[i].wPlanes;
CopyMemory(test,&lpGrpIconDir->idEntries[i].wPlanes,sizeof(WORD));
test=test+sizeof(WORD);
lpGrpIconDir->idEntries[i].wBitCount=pIconDir->idEntries[i].wBitCount;
CopyMemory(test,&lpGrpIconDir->idEntries[i].wBitCount,sizeof(WORD));
test=test+sizeof(WORD);
lpGrpIconDir->idEntries[i].dwBytesInRes=pIconDir->idEntries[i].dwBytesInRes;
CopyMemory(test,&lpGrpIconDir->idEntries[i].dwBytesInRes,sizeof(DWORD));
test=test+sizeof(DWORD);
if(i<lpInitGrpIconDir->idCount) //nu am depasit numarul initial de RT_ICON
lpGrpIconDir->idEntries[i].nID=lpInitGrpIconDir->idEntries[i].nID;
else
{
nMaxID++;
lpGrpIconDir->idEntries[i].nID=nMaxID;
}
CopyMemory(test,&lpGrpIconDir->idEntries[i].nID,sizeof(WORD));
test=test+sizeof(WORD);
}
我们构建了更新资源所需的字节数组。UpdateResource
函数必须接收一个指向数组的指针,该数组的构建方式与上述描述完全相同。如果新的图标资源包含的图像比初始资源多,我们将分配新的捆绑 ID。如果它包含的图像少,我们将删除不再使用的 RT_ICON
资源。
hUpdate = BeginUpdateResourceA(lpFileName, FALSE);
res=UpdateResource(hUpdate,RT_GROUP_ICON,lpName,langId,temp,cbRes);
for(i=0;i<lpGrpIconDir->idCount;i++)
{
res=UpdateResource(hUpdate,RT_ICON,MAKEINTRESOURCE(lpGrpIconDir->idEntries[i].nID),
langId,pIconImage[i], lpGrpIconDir->idEntries[i].dwBytesInRes);
}
如果存在未被更新的 RT_GROUP_ICON
引用的 RT_ICON
资源,我们将删除它们
for(i=lpGrpIconDir->idCount;i<lpInitGrpIconDir->idCount;i++)
{
res=UpdateResource(hUpdate,RT_ICON,
MAKEINTRESOURCE(lpInitGrpIconDir->idEntries[i].nID),langId,"",0);
}
以下是一些我们必须在 MSDN 上阅读的函数以及我在程序中使用的函数
LoadLibraryExA(lpSrcFileName,NULL,
DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
这用于获取 DLL 文件的句柄。(我的示例仅当 DLL 未被 VS 中的资源编辑器打开时才有效。)
HRSRC hRsrc = FindResourceEx(hUi, RT_GROUP_ICON, lpName,langId);
HGLOBAL hGlobal = LoadResource( hUi, hRsrc );
LockResource( hGlobal );
这些用于将特定资源加载到内存中,作为指向字节数组的指针。
hUpdate = BeginUpdateResourceA(lpFileName, FALSE);
res=UpdateResource(hUpdate,RT_GROUP_ICON,lpName,langId,temp,cbRes);
EndUpdateResource(hUpdate,FALSE);
您需要这些函数来更新特定资源。请记住,UpdateResources
必须提供一个字节数组,其格式如《二进制资源格式》一文中所述。
EnumResourceTypes(hui,(ENUMRESTYPEPROC)EnumTypesFunc,0)
此函数枚举 DLL 中的所有资源类型。EnumTypesFunc
是我的回调函数,它获取实际的资源类型并调用 EnumNamesFunc()
。
EnumResourceNames((HINSTANCE)hModule,lpType,(ENUMRESNAMEPROC)EnumNamesFunc,0)
此函数枚举所有资源捆绑名称。我的回调函数是
BOOL EnumNamesFunc(HANDLE hModule, LPCTSTR lpType, LPTSTR lpName, LONG lParam)
回调函数将接收资源的类型和名称。名称表示捆绑 ID。每个资源类型可以有一个或多个由唯一捆绑 ID 标识的资源。
EnumResourceLanguages((HMODULE)hModule,lpType,lpName,
(ENUMRESLANGPROC)EnumLangsFunc,0);
此函数枚举由其类型(lpType
)和捆绑 ID(lpName
)标识的资源可用的所有语言
LPICONIMAGE* ExtractIcoFromFile(LPSTR filename,LPICONDIR pIconDir)
此函数打开一个 *.ico 文件并读取 ICONDIR
和 ICONIMAGE
结构中的数据
BOOL ReplaceIconResource(LPSTR lpFileName, LPCTSTR lpName,
UINT langId, LPICONDIR pIconDir,LPICONIMAGE* pIconImage)
此函数使用 ICONDIR
和 ICONIMAGE
结构替换由捆绑 ID(lpName
)和语言 ID(langId
)标识的图标资源。
用法
我给出的示例程序必须接收以下参数:要替换的 DLL 文件中 RT_GROUP_ICON
(对于 test.dll 尝试 105)资源的 ID,以及 ICO 文件的名称(如果文件不在您的项目文件夹中,请记住给出正确的路径)。
其他项目
我正在从事的另一个项目包括一个关于 Web 界面的博客和网站。目前,文章只提供罗马尼亚语版本,但很快将有更多关于 Web 技术和 Web 界面设计的文章和教程,也将提供英语版本。
请访问我的博客:Interfete Web。