如何动态修改win PE文件图标?

11-26

PE文件的图标存储在资源文件中,而操作资源要用到的API函数就是UpdateResource

首先我们需要先了解一下ICO格式,参考资料:http://www.moon-soft.com/program/FORMAT/windows/icons.htm

ICO格式不复杂,就是由数据头、数据目录、数据三个部分组成

一个.ico文件中可能含有若干个图标,我们需要将数据目录和数据解析出来。简单地写了个单元

1 unit Icons;
2
3 interface
4
5 uses
6 Winapi.Windows, System.SysUtils, System.Classes;
7
8 type
9
10 { 用于ICO图标文件 }
11
12 TIconDirEntry = packed record
13 bWidth: Byte;
14 bHeight: Byte;
15 bColorCount: Byte;
16 bReserved: Byte;
17 wPlanes: Word;
18 wBitCount: Word;
19 dwBytesInRes: DWORD;
20 dwImageOffset: DWORD;
21 end;
22 PIconDirEntry = ^TIconDirEntry;
23
24 TIconDir = packed record
25 idReserved: Word;
26 idType: Word;
27 idCount: Word;
28 idEntries: array [0..0] of TIconDirEntry;
29 end;
30 PIconDir = ^TIconDir;
31
32 { 用于PE文件中的图标 }
33
34 TGroupIconDirEntry = packed record
35 bWidth: Byte;
36 bHeight: Byte;
37 bColorCount: Byte;
38 bReserved: Byte;
39 wPlanes: Word;
40 wBitCount: Word;
41 dwBytesInRes: DWORD;
42 nID: Word;
43 end;
44
45 TGroupIconDir = packed record
46 idReserved: Word;
47 idType: Word;
48 idCount: Word;
49 idEntries: array [0 .. 0] of TGroupIconDirEntry;
50 end;
51 PGroupIconDir = ^TGroupIconDir;
52
53 { ICO图标文件 }
54 TIcoFile = class
55 public
56 IconStream: TMemoryStream;
57 IconDir: PIconDir;
58 IconDirSize: DWORD;
59
60 constructor Create; overload;
61 constructor Create(const FileName: string); overload;
62 destructor Destroy; override;
63
64 // 加载ICO数据
65 procedure LoadFromFile(const FileName: string);
66 procedure LoadFromStream(Stream: TStream);
67 end;
68
69 implementation
70
71 { TIcoFile }
72
73 constructor TIcoFile.Create;
74 begin
75 IconStream := TMemoryStream.Create;
76 IconDir := nil;
77 IconDirSize := 0;
78 end;
79
80 constructor TIcoFile.Create(const FileName: string);
81 begin
82 Create;
83 LoadFromFile(FileName);
84 end;
85
86 destructor TIcoFile.Destroy;
87 begin
88 FreeMem(IconDir);
89 FreeAndNil(IconStream);
90 inherited;
91 end;
92
93 procedure TIcoFile.LoadFromFile(const FileName: string);
94 var
95 MS: TMemoryStream;
96 begin
97 MS := TMemoryStream.Create;
98 try
99 MS.LoadFromFile(FileName);
100 LoadFromStream(MS);
101 finally
102 FreeAndNil(MS);
103 end;
104 end;
105
106 procedure TIcoFile.LoadFromStream(Stream: TStream);
107 var
108 Dir: TIconDir;
109 begin
110 Stream.Position := 0;
111 IconStream.Clear;
112 IconStream.CopyFrom(Stream, Stream.Size);
113
114 IconStream.Position := 0;
115 IconStream.ReadBuffer(Dir, SizeOf(Dir));
116
117 FreeMem(IconDir);
118 IconDirSize := SizeOf(TIconDirEntry) * (Dir.idCount - 1) + SizeOf(TIconDir);
119 IconDir := AllocMem(IconDirSize);
120 IconStream.Position := 0;
121 IconStream.ReadBuffer(IconDir^, IconDirSize);
122 end;
123
124 end.

这里要注意一个问题,ICO文件中的TIconDirEntry结构和PE文件中的TGroupIconDirEntry结构是不同的

不同处在最后一个参数,ICO文件中表示的是图像数据偏移地址,是DWORD类型。而PE文件中表示的是图像数据的索引,是WORD类型,少了2个字节

替换图标需要写入2个部分:RT_GROUP_ICON 和 RT_ICON

RT_GROUP_ICON也就是ICO的数据头部分,RT_ICON就是图像数据部分了

数据部分直接写入即可,不用转换什么的。但是数据头需要稍微处理一下,因为前面说了,这个替换过程是把ICO文件写到PE文件中

而他们数据头部分结构略有不同(PE文件中每个数据头比起ICO数据头要少2字节),只用把这里处理下就行了

最后写个函数就可以替换PE文件图标了