Win32_API修改EXE文件ICON图标

说明

EXE文件的ICON图标一般由数个图标组成,在更新ICON图标时需要逐一添加每个图标,再添加ICON_GROUP目录,可以使用ResourceHacker软件进行查看。

EXE文件中的图标数据结构和ICON图标文件结构很相似,唯一区别在于ICON图标的ICONDIRENTRY最后一个字段标注的是图像数据首地址,EXE文件的GRPICONDIRENTRY最后一个字段标注的是图标的ID。

代码

#include <windows.h>
#include <stdio.h>

#pragma pack(push, 2) //结构体需要双字节对齐

typedef struct
{
    WORD           idReserved;   // Reserved (must be 0)
    WORD           idType;       // Resource Type (1 for icons)
    WORD           idCount;      // How many images?
    // ICONDIRENTRY   idEntries[1]; // An entry for each image (idCount of 'em)
} ICONDIR;

typedef struct
{
    BYTE        bWidth;          // Width, in pixels, of the image
    BYTE        bHeight;         // Height, in pixels, of the image
    BYTE        bColorCount;     // Number of colors in image (0 if >=8bpp)
    BYTE        bReserved;       // Reserved ( must be 0)
    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;

typedef struct
{
    BITMAPINFOHEADER   icHeader;   // DIB header
    RGBQUAD         icColors[1];   // Color table
    BYTE            icXOR[1];      // DIB bits for XOR mask
    BYTE            icAND[1];      // DIB bits for AND mask
} ICONIMAGE;

typedef struct
{
    WORD            idReserved;   // Reserved (must be 0)
    WORD            idType;       // Resource type (1 for icons)
    WORD            idCount;      // How many images?
    // GRPICONDIRENTRY   idEntries[1]; // The entries for each image
} GRPICONDIR;

typedef struct
{
    BYTE   bWidth;               // Width, in pixels, of the image
    BYTE   bHeight;              // Height, in pixels, of the image
    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
} GRPICONDIRENTRY;

#pragma pack(pop)

int main(int argc, char *argv[]) {
    // if (argc != 3) {
    //     printf("Usage: %s <target_exe> <new_icon.ico>\n", argv[0]);
    //     return 1;
    // }
    //
    // const char *exePath = argv[1];
    // const char *iconPath = argv[2];

    char exePath[] = "pip.exe";
    char iconPath[] = "app.ico";

    // 打开图标文件
    HANDLE hIcon = CreateFile(iconPath, GENERIC_READ, 0, NULL,
                              OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hIcon == INVALID_HANDLE_VALUE) {
        printf("[%lu]Error opening icon file\n", GetLastError());
        return 1;
    }
    // 获取图标文件大小
    DWORD iconSize = GetFileSize(hIcon, NULL);
    if (iconSize == INVALID_FILE_SIZE) {
        printf("[%lu]Error getting icon file size\n", GetLastError());
        CloseHandle(hIcon);
        return 1;
    }
    // 读取图标文件
    BYTE* iconData = malloc(iconSize);
    DWORD bytesRead;
    if (!ReadFile(hIcon, iconData, iconSize, &bytesRead, NULL) || bytesRead != iconSize) {
        printf("[%lu]Error reading icon file\n", GetLastError());
        free(iconData);
        CloseHandle(hIcon);
        return 1;
    }
    CloseHandle(hIcon);

    // 解析图标文件目录
    ICONDIR* iconDir = (ICONDIR*)iconData;
    if (iconDir->idReserved != 0 || iconDir->idType != 1) {
        printf("Invalid icon file format\n");
        free(iconData);
        return 1;
    }

    // 计算图标组资源大小
    DWORD groupSize = sizeof(GRPICONDIR) + iconDir->idCount * sizeof(GRPICONDIRENTRY);
    BYTE* groupData = malloc(groupSize);
    GRPICONDIR* grpIconDir = (GRPICONDIR*)groupData;
    grpIconDir->idReserved = 0;
    grpIconDir->idType = 1; // RT_ICON
    grpIconDir->idCount = iconDir->idCount;

    // 创建图标组资源
    ICONDIRENTRY* iconEntries = (ICONDIRENTRY*)(iconData + sizeof(ICONDIR));
    GRPICONDIRENTRY* grpEntries = (GRPICONDIRENTRY*)(groupData + sizeof(GRPICONDIR));

    for (int i = 0; i < iconDir->idCount; i++) {
        grpEntries[i].bWidth = iconEntries[i].bWidth;
        grpEntries[i].bHeight = iconEntries[i].bHeight;
        grpEntries[i].bColorCount = iconEntries[i].bColorCount;
        grpEntries[i].bReserved = iconEntries[i].bReserved;
        grpEntries[i].wPlanes = iconEntries[i].wPlanes;
        grpEntries[i].wBitCount = iconEntries[i].wBitCount;
        grpEntries[i].dwBytesInRes = iconEntries[i].dwBytesInRes;
        grpEntries[i].nID = i + 1; // 资源ID从1开始
    }

    // 开始更新EXE资源
    HANDLE hUpdate = BeginUpdateResource(exePath, FALSE);
    if (hUpdate == NULL) {
        printf("BeginUpdateResource failed: %lu\n", GetLastError());
        free(iconData);
        free(groupData);
        return 1;
    }
    // 删除现有的图标组资源
    if (!UpdateResource(hUpdate, RT_GROUP_ICON, MAKEINTRESOURCE(101),
                        MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), NULL, 0)) {
            printf("[%lu]Warning: Failed to remove old icon group ID-101\n", GetLastError());

    }

    // 删除现有的图标资源
    for (int i = 1; i <= 7; i++) { // 删除ID 1-7的图标资源
        if (!UpdateResource(hUpdate, RT_ICON, MAKEINTRESOURCE(i),
                       MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), NULL, 0)) {
            printf("[%lu]Warning: Failed to remove old icon ID-%d\n", GetLastError(), i);
        };
    }

    // 添加新的图标组资源
    if (!UpdateResource(hUpdate, RT_GROUP_ICON, MAKEINTRESOURCE(101),
                        MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                        groupData, groupSize)) {
        printf("[%lu]UpdateResource (group icon) failed\n", GetLastError());
        free(iconData);
        free(groupData);
        EndUpdateResource(hUpdate, TRUE);
        return 1;
    }

    // 添加各个图标资源
    for (int i = 0; i < iconDir->idCount; i++) {
        DWORD offset = iconEntries[i].dwImageOffset;
        DWORD size = iconEntries[i].dwBytesInRes;

        if (offset + size > iconSize) {
            printf("Icon data out of bounds: entry %d\n", i);
            continue;
        }

        if (!UpdateResource(hUpdate, RT_ICON, MAKEINTRESOURCE(i + 1),
                            MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                            iconData + offset, size)) {
            printf("[%lu]UpdateResource (icon %d) failed\n", i, GetLastError());
        }
    }

    // 完成资源更新
    if (!EndUpdateResource(hUpdate, FALSE)) {
        printf("[%lu]EndUpdateResource failed\n", GetLastError());
        free(iconData);
        free(groupData);
        return 1;
    }

    printf("Icon group updated successfully!\n");
    free(iconData);
    free(groupData);
    return 0;
}