Introduction
The purpose of this article is to explain the structures associated with an ICO file and an icon resource. The code examples will demonstrate the use of those structures and how to embed the structures in a DLL file.
Background
I started to work on my own resource hacker because I needed to automate string and icon replacement in a DLL file. I started reading on MSDN about working with libraries and the basic functions for working with resources.
I learned that enumerating resources and opening resources isn't so difficult, but the problem arises when you actually want to do something with that resource, like modifying or replacing it.
The most useful article was: Binary resource formats. Here, I found most of what I needed to know about working with the array of bytes that the OpenResource
function returned.
To learn how different functions that I used work, you should check out MSDN: Resource Reference.
Using the code
I will start by explaining the structures associated with an ICO file and with an icon resource. The ICO file is an array of bytes arranged after some simple rules. The ICO file consist of an ICONDIR
structure that is placed at the beginning of the file:
typedef struct
{
WORD idReserved;
WORD idType;
WORD idCount;
LPICONDIRENTRY idEntries;
} ICONDIR, *LPICONDIR;
idReserved
is always 0.
idType
is 1 for icon files.
idCount
is the number of images that the ICO file contains.
idEntries
is a pointer to idCount
structures describing the images in the file (the structures are written in the file immediately after ICONDIR
). The type of the structures is ICONDIRENTRY
.
typedef struct
{
BYTE bWidth;
BYTE bHeight;
BYTE bColorCount;
BYTE bReserved;
WORD wPlanes;
WORD wBitCount;
DWORD dwBytesInRes;
DWORD dwImageOffset;
} ICONDIRENTRY, *LPICONDIRENTRY;
dwBytesInRes
represents the total size of the image, that is the total size of the ICONIMAGE
structure that represents the actual image.
dwImageOffset
represents the offset within the ICO file where the ICONIMAGE
structure for that image can be found.
I haven't actually tried to modify the way an image looks by playing with the ICONIMAGE
fields, so I can only say that an image has the following structure:
BITMAPINFOHEADER icHeader;
RGBQUAD icColors[1];
BYTE icXOR[1];
BYTE icAND[1];
and the BITMAPINFOHEADER
has the following fields, but only the Size
, Width
, Height
, Planes
, BitCount
, and SizeImage
are used. The rest are 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;
In my program, I defined the following structure:
typedef struct
{
UINT Width, Height, Colors;
LPBYTE lpBits;
DWORD dwNumBytes;
LPBITMAPINFO lpbi;
LPBYTE lpXOR;
LPBYTE lpAND;
} ICONIMAGE, *LPICONIMAGE;
To learn more about how to use this structure, you should check out this source code: ICONS.c.
So now, let's summarize how to read the ICO file:
First, read the ICONDIR
structure:
ReadFile( hFile1, &(pIconDir->idReserved), sizeof( WORD ), &dwBytesRead, NULL );
ReadFile( hFile1, &(pIconDir->idType), sizeof( WORD ), &dwBytesRead, NULL );
ReadFile( hFile1, &(pIconDir->idCount), sizeof( WORD ), &dwBytesRead, NULL );
Next, read the idCount
and ICONDIRENTRY
structures:
for(i=0;i<pIconDir->idCount;i++)
{
ReadFile( hFile1, &pIconDir->idEntries[i], sizeof(ICONDIRENTRY),
&dwBytesRead, NULL );
}
Now, for each ICONDIRENTRY
, find the associated ICONIMAGE
structure that represents the actual image (the nice colored pixels) in the ICO file, and read it.
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);
}
Now, you have all the structures that represent the ICO file.
The structures used in a DLL file are almost the same as the ones I described above. So, now that you know how the ICO file is structured, modifying the icon resources in the DLL will be pretty easy.
Let's see the structures I use for updating the DLL. I will only point out the difference between this and the structures above:
typedef struct
{
WORD idReserved;
WORD idType;
WORD idCount;
LPMEMICONDIRENTRY idEntries;
} MEMICONDIR, *LPMEMICONDIR;
MEMICONDIR
is the same as ICONDIR
.
typedef struct
{
BYTE bWidth;
BYTE bHeight;
BYTE bColorCount;
BYTE bReserved;
WORD wPlanes;
WORD wBitCount;
DWORD dwBytesInRes;
WORD nID;
} MEMICONDIRENTRY, *LPMEMICONDIRENTRY;
As you can see, for a resource entry, we don't need the dwImageOffset
field anymore. The reason is that we can find or update an image resource in a DLL using the bundle ID (nID
). The FindResource
and UpdateResource
need to know the type of the resource (lbType
), the language (langId
), and the bundle id (lpName
).
The way an ICO resource is saved in a DLL is as follows:
The RT_GROUP_ICON
resource contains the MEMICONDIR
and MEMICONDIRENTRY
structures. The RT_GROUP_ICON
is what the Resource Editor in VS uses to show you the icons. If these resources don't exist, you can't see the icon resources in the Resource Editor.
For each MEMICONDIRENTRY
, there is an RT_ICON
resource with the bundle ID identified by the nID
field in the MEMICONDIRENTRY
structure.
Now, we can replace the icon resource (we know the bundle ID (lpName
), the language (langID
), and the type RT_GROUP_ICON
of the resource we want to replace) in the DLL using the structures that we found in the ICO file:
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 );
First, we read the resource that exists in the DLL using test1
. We build the lpGrpIconDir
to save the RT_ICON
bundle IDs that are initially used.
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)
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);
}
We built the array of bytes that we'll need to update the resource. The UpdateResource
function has to receive a pointer to an array that is built exactly as described above. If the new icon resource contains more images than the initial resource, we'll attribute new bundle IDs. If it contains less images, we'll delete the RT_ICON
resources that aren't used anymore.
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);
}
If there are RT_ICON
resources that aren't referenced by the updated RT_GROUP_ICON
, we delete them:
for(i=lpGrpIconDir->idCount;i<lpInitGrpIconDir->idCount;i++)
{
res=UpdateResource(hUpdate,RT_ICON,
MAKEINTRESOURCE(lpInitGrpIconDir->idEntries[i].nID),langId,"",0);
}
Here are some functions that we'll have to read about on MSDN and the function I used in my program:
LoadLibraryExA(lpSrcFileName,NULL,
DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE);
This is used to get a handle to the DLL file. (My example works only if the DLL isn't opened by the resource editor in VS.)
HRSRC hRsrc = FindResourceEx(hUi, RT_GROUP_ICON, lpName,langId);
HGLOBAL hGlobal = LoadResource( hUi, hRsrc );
LockResource( hGlobal );
These are used to load a specific resource in the memory as a pointer to an array of bytes.
hUpdate = BeginUpdateResourceA(lpFileName, FALSE);
res=UpdateResource(hUpdate,RT_GROUP_ICON,lpName,langId,temp,cbRes);
EndUpdateResource(hUpdate,FALSE);
You need thes functions to update a specific resource. Remember that UpdateResources
must be given an array of bytes that has the format described in the Binary Resource Formats article.
EnumResourceTypes(hui,(ENUMRESTYPEPROC)EnumTypesFunc,0)
This function enumerates all the resource types in the DLL. EnumTypesFunc
is my callback function that gets the actual resource types and calls the EnumNamesFunc()
.
EnumResourceNames((HINSTANCE)hModule,lpType,(ENUMRESNAMEPROC)EnumNamesFunc,0)
This function enumerates all the resource bundle names. My callback function is:
BOOL EnumNamesFunc(HANDLE hModule, LPCTSTR lpType, LPTSTR lpName, LONG lParam)
The callback function will receive the type of the resource and the name. The name represents the bundle ID. Each resource type can have one or more resources identified by a unique bundle ID.
EnumResourceLanguages((HMODULE)hModule,lpType,lpName,
(ENUMRESLANGPROC)EnumLangsFunc,0);
This function enumerates all the languages available for a resource identified by its type (lpType
) and bundle ID (lpName
):
LPICONIMAGE* ExtractIcoFromFile(LPSTR filename,LPICONDIR pIconDir)
This function opens a *.ico file and reads the data in the ICONDIR
and ICONIMAGE
structures:
BOOL ReplaceIconResource(LPSTR lpFileName, LPCTSTR lpName,
UINT langId, LPICONDIR pIconDir,LPICONIMAGE* pIconImage)
This function uses the ICONDIR
and ICONIMAGE
structures to replace an icon resource identified by the bundle ID (lpName
) and the language ID (langId
).
Usage
The program that I gave as an example must receive the following parameters: the ID of the RT_GROUP_ICON
(try 105 for test.dll) resource you want to replace the name of in the DLL file, and the name of the ICO file (remember to give the correct path if the files are not in your projects folder).
Other Projects
Another project I'm working on consists of a blog and website about Web Interfaces. Currently, the articles appear only in Romanian, but pretty soon, there will be more articles and tutorials about Web technologies and Web interface design, also in English.
Please visit my blog at Interfete Web.