Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Replacing ICON resources in EXE and DLL files

0.00/5 (No votes)
13 Nov 2008 2  
An easy example of how to read a *.ico file and how to replace an icon resource.

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;   // Reserved
    WORD            idType;       // resource type (1 for icons)
    WORD            idCount;      // how many images?
    LPICONDIRENTRY    idEntries; // the entries for each image
} 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;               // 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 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; // 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; 

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;   // Reserved
    WORD            idType;       // resource type (1 for icons)
    WORD            idCount;      // how many images?
    LPMEMICONDIRENTRY    idEntries; // the entries for each image
} MEMICONDIR, *LPMEMICONDIR;

MEMICONDIR is the same as 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;

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) //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);
}

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here