如何修改可执行文件的图标(200分)

  • 主题发起人 主题发起人 djw
  • 开始时间 开始时间
D

djw

Unregistered / Unconfirmed
GUEST, unregistred user!
关于用程序方法修改可执行文件图标的讨论很多,讨论结果大多是简单却
没有例子的方法,即:使用流,或UpdateResource函数
偶然看到一篇关于“如何修改可执行文件的图标”的文章且带有例子函数
可惜是C++的,贴出大家看看是否有借鉴之处,同时请熟悉C的朋友改写成
Delphi格式的,让大家受益。
原文地址:http://www.csdn.net/develop/Read_Article.asp?Id=11618
示例函数地址:http://www.3155530.com/antghazi/download/ModifyIcon.zip

====================================
作者:AntGhazi/2001.12.14 主页:antghazi.yeah.net
在网上有很多关于PE文件格式的说明,讲得最多莫过于IMAGE_DOS_HEADER、IMAGE_NT_HEADERS、IMAGE_SECTION_HEADER、等。而对于节的介绍最多的,也莫过于函数引入引出节。而关于资源节.rsrc的介绍则少之又少。好了,废话少说。
PE文件格式如下:

对于PE的详细介绍在MSDN中也有,邹丹(www.zaodan.com)与罗大侠(asm.yeah.net)的主页上也有细详的介绍。这里我在修改ICON中的一种做法。讲解中所用到的语句并不全面,重要的是这个思路。最后面我会给出一个直修改资源的函数。
首先,我们需要两个可执行文件,并且已知这两个exe文件都有图标资源。
1、 peSource.exe (从此文件中提取图标)
2、 peDesc.exe (将图标写入此文件)
第二部,分别打开这两个文件,hFileSource设为只读,hFileDesc设为可写。
HANDLE hFileSource;
HANDLE hFileDesc;
打开后,大家最常用的莫过于文件映射,这里为方便与直观,我们直接把文件读到一个内存块中。
//先得到长度
DWORD dwSourceSize =::GetFileSize(hFileSource);
DWORD dwDescSize =::GetFileSize(hFileDesc);
DWORD byte_write=0;
//读取
char *pFileSource =new char[dwSourceSize];
char *pFileDesc =new char[dwDescSize];

::ReadFile(hFileSource,pFileSource,dwSourceSize,&byte_write,0);
::ReadFile(hFileDesc,pFileDesc,dwDescSize,&byte_write,0);

好了,现在我们已经分别将两个文件读入内存中。让我们先将pFileSource指到资源节的头部。Section的结构说明如下:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
通常情况,资源节的名称一般都为:.rsrc。目前我们只考虑这种情况。
IMAGE_DOS_HEADER *dosHeadA=(IMAGE_DOS_HEADER *)pFileSource; //DOS头
IMAGE_NT_HEADERS *ntHeadA=(IMAGE_NT_HEADERS *) (pFileSource + dosHeadA->e_lfanew); //NT头
IMAGE_SECTION_HEADER *secHeadA=(IMAGE_SECTION_HEADER *)((char *)ntHeadA+ sizeof(IMAGE_NT_HEADERS)); //第一个节的首地址

//循环找出.rsrc节
for(int i=0;i<ntHeadA->FileHeader .NumberOfSections ;i++,secHeadA++){
if(strcmp((char *)secHeadA->Name,".rsrc")==0){ //找到.rsrc节
break;
}
}
好了,现在我们已经找到.rsrc节表。根据节表,我们就可以找到资源的入口地址。
IMAGE_RESOURCE_DIRECTORY *dirResourceA=(IMAGE_RESOURCE_DIRECTORY *)((char *)pFileSource + secHeadA->PointerToRawData); //得到资源入口地址

到这里,我才开始讲到我们今天的目的----资源结构,下面有几个需要用到的结构与相关的解释:
// Resource Format.
//

//
// Resource directory consists of two counts, following by a variable length
// array of directory entries. The first count is the number of entries at
// beginning of the array that have actual names associated with each entry.
// The entries are in ascending order, case insensitive strings. The second
// count is the number of entries that immediately follow the named entries.
// This second count identifies the number of entries that have 16-bit integer
// Ids as their name. These entries are also sorted in ascending order.
//
// This structure allows fast lookup by either name or number, but for any
// given resource entry only one form of lookup is supported, not both.
// This is consistant with the syntax of the .RC file and the .RES file.
//

typedef struct _IMAGE_RESOURCE_DIRECTORY { //资源树结构
DWORD Characteristics; //标识此资源的类型
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries;
WORD NumberOfIdEntries; //此结构下还包函有的资源结构树,即:还有几个子树。
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]; //请注意这里,下面还会讲到。
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
此结构的其他解释请见VC的头文件winnt.h.
整个资源的结构就好像一棵树型,不同资源如:menu,icon,dialog,cursor等。都如同每根树枝,树枝的Characteristics会标识不同的资源类型,而每根树枝又会有子树枝。这样一直循环,直到IMAGE_RESOURCE_DIRECTORY的NumberOfIdEntries为零时才结束。通常情况,子树都为为三层。每一个子树的类型由IMAGE_RESOURCE_DIRECTORY中的Characteristics来标识。如:当第一层的Characteristics==3时,则说明此结构为ICON资源。Characteristics类型定义如下(可在winuser.h中找到):
/*
* Predefined Resource Types
*/
#define RT_CURSOR MAKEINTRESOURCE(1)
#define RT_BITMAP MAKEINTRESOURCE(2)
#define RT_ICON MAKEINTRESOURCE(3)
#define RT_MENU MAKEINTRESOURCE(4)
#define RT_DIALOG MAKEINTRESOURCE(5)
#define RT_STRING MAKEINTRESOURCE(6)
#define RT_FONTDIR MAKEINTRESOURCE(7)
#define RT_FONT MAKEINTRESOURCE(8)
#define RT_ACCELERATOR MAKEINTRESOURCE(9)
#define RT_RCDATA MAKEINTRESOURCE(10)
#define RT_MESSAGETABLE MAKEINTRESOURCE(11)

总结构如下(偷懒,copy而来):

好了,整个资源的结构已经弄清楚了。现在我们要做的就是得到每个子资源的入口地址。这里要用到的一个结构是:
// Each directory contains the 32-bit Name of the entry and an offset,
// relative to the beginning of the resource directory of the data associated
// with this directory entry. If the name of the entry is an actual text
// string instead of an integer Id, then the high order bit of the name field
// is set to one and the low order 31-bits are an offset, relative to the
// beginning of the resource directory of the string, which is of type
// IMAGE_RESOURCE_DIRECTORY_STRING. Otherwise the high bit is clear and the
// low-order 16-bits are the integer Id that identify this resource directory
// entry. If the directory entry is yet another resource directory (i.e. a
// subdirectory), then the high order bit of the offset field will be
// set to indicate this. Otherwise the high bit is clear and the offset
// field points to a resource data entry.
//

typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset:31;
DWORD NameIsString:1;
};
DWORD Name;
WORD Id;
};
union {
DWORD OffsetToData; //指向资源的入口址
struct {
DWORD OffsetToDirectory:31;
DWORD DataIsDirectory:1; //指向下一级目录的相对地址
};
};
}IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
上面对IMAGE_RESOURCE_DIRECTORY_ENTRY的解释也已经是非常清楚了。
结构中有两个成员:OffsetToData,DataIsDirectroy,当DiataIsDirectroy大于0时,则说明此结构还有下一级目录,否则,OffsetToData肯定不为0。那OffsetToData的值就是我们所得到的资源入口的RVA了。
那么,IMAGE_RESOURCE_DIRECTORY_ENTRY结构应该怎么得到呢?让我们再看一下,IMAGE_RESOURCE_DIRECTORY的结构说明吧。

typedef struct _IMAGE_RESOURCE_DIRECTORY { //资源树结构
DWORD Characteristics; //标识此资源的类型
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries;
WORD NumberOfIdEntries; // 、、//IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]; //紧跟在后面的就是 IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组,DirectoryEntries数组的个数实际上也就是NumberOfIdEntries.你也可以理解为
IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[NumberOfIdEntries];,
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
那这样一看来,IMAGE_RESOURCE_DIRECTORY_ENTRY的第一个地址等于父树地址加上IMAGE_RESOURCE_DIRECTORY结构的大小即可。
如:IMAGE_RESOURCE_DIRECTORY *dirTempB=(IMAGE_RESOURCE_DIRECTORY *)((char *)dirResourceB+entryResourceB->OffsetToDirectory);

最后一个是IMAGE_RESOURCE_DATA_ENTRY结构,比较简单,大家看一下就知道了。
// Each resource data entry describes a leaf node in the resource directory
// tree. It contains an offset, relative to the beginning of the resource
// directory of the data for the resource, a size field that gives the number
// of bytes of data at that offset, a CodePage that should be used when
// decoding code point values within the resource data. Typically for new
// applications the code page would be the unicode code page.
//

typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData;
DWORD Size;
DWORD CodePage;
DWORD Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;

好了,讲了这么多,现在我们可以开始计算了,(我们以读取第三层第一个ICON为例<通常资源都为三层>)
前面我们已经得到根资源的地址:dirResourceA

IMAGE_RESOURCE_DIRECTORY *dirResourceA=(IMAGE_RESOURCE_DIRECTORY *)((char *)pFileA + secHeadA->PointerToRawData); //根
IMAGE_RESOURCE_DIRECTORY_ENTRY *entryResourceA=(IMAGE_RESOURCE_DIRECTORY_ENTRY *)((DWORD)dirResourceA + sizeof (IMAGE_RESOURCE_DIRECTORY));
IMAGE_RESOURCE_DIRECTORY *dirTemp; //第二层
IMAGE_RESOURCE_DIRECTORY_ENTRY *entryTemp;
IMAGE_RESOURCE_DIRECTORY *dirTempICON; //第三层
IMAGE_RESOURCE_DIRECTORY_ENTRY *entryTempICON;
IMAGE_RESOURCE_DATA_ENTRY *entryData; //资源入口结构
for(i=0;i<(dirResourceA->NumberOfIdEntries+dirResourceA->NumberOfNamedEntries);i++,entryResourceA++){ //所有资源
if(entryResourceA->Name==3){ //ICON
dirTemp=(IMAGE_RESOURCE_DIRECTORY *)((char *)dirResourceA+entryResourceA->OffsetToDirectory);
entryTemp=(IMAGE_RESOURCE_DIRECTORY_ENTRY *)((char *)dirTemp+sizeof(IMAGE_RESOURCE_DIRECTORY));
for(int k=0;k<(dirTemp->NumberOfIdEntries+dirTemp->NumberOfNamedEntries);k++,entryTemp++){ //子目录
if(entryTemp->DataIsDirectory >0){ //还有子目录
dirTempICON=(IMAGE_RESOURCE_DIRECTORY *)((char *)dirResourceA + entryTemp->OffsetToDirectory );
entryTempICON=(IMAGE_RESOURCE_DIRECTORY_ENTRY *)((char *)dirTempICON + sizeof(IMAGE_RESOURCE_DIRECTORY));
entryData=(IMAGE_RESOURCE_DATA_ENTRY *)((char *)dirResourceA + entryTempICON->OffsetToData ); //资源入口结构
break; //得到后跳出
}
}
}
}

最后,读入内存中:
DWORD dwIconSize=entryDataA->Size;
char *pSrcIcon=entryDataA->OffsetToData - secHeadA->VirtualAddress + (char *)dirResourceA;
char *pSourceIcon= new char[dwIconSize+1];
memcpy(pSourceIcon,pSrcIcon,dwIconSize);
最后得到的数据就在pSourceIcon中了。
同理,得到另一个文件中的ICON入口地址,用pSourceIcon覆盖之即可。
函数地址:http://go3.163.com/antghazi/main3.htm

 
送分?我先报到。
 
是这样吗?
由于大多数程序都是使用32*32象素,16色的图表,下面就以提取此种格式的图标为
例进行介绍。
一:提取图标
1。用ultra edit打开要提取图标的程序文件或*.dll文件,按“alt+f3”打开要查询
对话框,输入“2800000020”,按回车键后开始查找,找到后选中“2800000020”以后的
47行以上(多选几十行也行,就是不能少于47行),按“ctrl+c”键复制选中内容。
2。用ultra edit打开一个32*32象素,16色的图标*.ico(大小为766字节的图标就
可以了),记住长度是到“000002f0h”行的第十四格,共47行。选中第二行“2800000020”
以后的所有内容,按“ctrl+v”键粘贴。再选中从“000002f0h”行的第十四格以后的
内容,按“ctrl+x”剪切,这是为了让文件保持原来的长度,按“f12”把文件另存为
2.ico,这就是我们要的图标
3。按f3继续查找,重复前面两步可以把所有图标找出来,连工具栏的图标也能找出来。
这个方法可以弥补用图标提取软件无法提取的部分程序图标的不足。
二:更换图标
由于这种方法还具有可逆性,因此,我们可以将程序中的图标替换掉。
打开一个自己的图标,同样是32*32象素,16色的格式,把光标移到最后,按“ctrl+v”
把第二步剪切下来的内容粘贴大文件后面。选中第二行“2800000020”以后的所有内容,
复制后回到第一步打开的程序文件,可以看到刚选中的区域还处于选中状态,
按“ctrl+v”粘贴后,保存。这样你已经包自己的图标放入程序中取代了程序中
原有的图标。
在16进制模式下进行
 
何必这么辛苦?在Win2K下运行VC,用resource方式打开可执行文件,然后编辑图标,存盘退出就可以了。
 
好象都没那么复杂吧。
文件->属性 或 Delphi->Project->paramters->有一个有icons的选项很好找了
 
同意 chenzhou
 
那样只是改了快捷方式的图标,并没有修改实际的可执行文件
 
Delphi->Project->application 有一项是选图标的
 
我明白楼主的意思,因为我也需要这一功能,例如我制作电子贺卡,想让电子贺卡可以改换图标,
这个如何实现呢?

可能要用到,静态修改,也就是我要让我的程修改c:/test.exe程序的图标,使它在静态时显示不同的图标

不用其它软件
 
试试这个吧。

unit Exeico

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls,shellapi;

type
TForm1 = class(TForm)
Label1: TLabel;
Edit1: TEdit;
Label2: TLabel;
Edit2: TEdit;
Button1: TButton;
Button2: TButton;
OpenDialog1: TOpenDialog;
OpenDialog2: TOpenDialog;
StatusBar1: TStatusBar;
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button2Click(Sender: TObject);
begin
Close;
end;

procedure TForm1.Button1Click(Sender: TObject);
const
readlen=10; //每次读取字节数,可改变
icolen=766;//32*32图标长度,根据研究前126为图标头,后640为图标数据
var
i,j,itemp,nPos:int64;// nPos为目的图标在目的文件的位置
ci,cj:array[0..readlen-1] of char;
SourceFile,DestFile:String;
bOK:boolean;
SourceIcon,DestIcon:TIcon;
SIconStream,s,sDest:TMemoryStream;
begin
bOK:=false;
if OpenDialog1.Execute then
SourceFile:=OpenDialog1.FileName
else
exit;
if AnsiUpperCase(ExtractFileExt(SourceFile))<>'.EXE' then
begin
ShowMessage(AnsiUpperCase(ExtractFileExt(SourceFile)));
exit;
end;
Edit1.Text:=SourceFile;
if OpenDialog2.Execute then
DestFile:=OpenDialog2.FileName
else
exit;
if AnsiUpperCase(ExtractFileExt(DestFile))<>'.EXE' then
exit;
Edit2.Text:=DestFile;
SourceIcon:=TIcon.Create;
case ExtractIcon(handle,PChar(SourceFile),UINT(-1)) of
0:begin ShowMessage('源程序没有图标');exit;end;
1:;
else ShowMessage('源程序有多个图标,本程序选择第一个图标');
end;
SourceIcon.Handle:=ExtractIcon(handle,PChar(SourceFile),0);//选择第一个图

DestIcon:=TIcon.Create;//选择第N个图标为 ExtractIcon(handle,PChar(Source
File),N-1)
case ExtractIcon(handle,PChar(DestFile),UINT(-1)) of
0:begin ShowMessage('目的程序没有图标');exit;end;
1:;
else ShowMessage('目的程序有多个图标,本程序选择第一个图标替换');
end;
DestIcon.Handle:=ExtractIcon(handle,PChar(DestFile),0);//选择第一个图标
SIconStream:=TMemoryStream.Create;
DestIcon.SaveToStream(sIconStream);
if sIconStream.size<>icolen then
ShowMessage('SIcon.size<>icolen');
SDest:=TMemoryStream.Create;
sDest.LoadFromFile(DestFile);
i:=0;j:=0; //以下程序查找目的图标在目的程序中的位置
while i< sDest.size do
begin
itemp:=i;
j:=126;
{ repeat
SDest.Position:=i;
sDest.read(ci,Readlen);
SiconStream.Position:=j;
SIconStream.Read(cj,Readlen);
i:=i+Readlen;
j:=j+Readlen;
until (String(ci)=String(cj)) and (i<sDest.size) and (j<icolen);
} ci:='';cj:='';
while (String(ci)=String(cj)) and (i<SDest.size) and (j<
icolen) do
begin
i:=i+readlen;
j:=j+readlen;
SDest.Position:=i;
SDest.read(ci,readlen);
SiconStream.Position:=j;
SiconStream.Read(cj,readlen);
end;
if j<icolen then
i:=itemp+1 //没找到
else
begin
nPos:=itemp; //找到
bOK:=true;
break;
end;
end;
if bOK=false then
exit;//目标文件二进制码中未找到图标
SIconStream.Clear;//将源程序图标存入
SourceIcon.SaveToStream(SIconStream);

SIconStream.position:=126;
s:=TMemoryStream.Create;

sDest.Position:=0;
s.CopyFrom(sDest,nPos);//将目的程序图标前数据拷入
s.CopyFrom(SIconStream,640); //将源程序图标拷入
if sDest.size>sDest.Position+640 then //将目的程序剩余数据拷入
begin
sDest.Position:=sDest.Position+640;
s.CopyFrom(sDest,sDest.Size-sDest.Position);
end;
s.SaveToFile(Extractfilepath(application.exename)+'Result.exe');
SourceIcon.Free;DestIcon.Free; //改造好的程序存放在本目录Result.exe文件中
SIconStream.Free;s.Free;sDest.Free;
ShowMessage(Extractfilepath(application.exename)+'Result.exe');
end;
 
我测试过了,这个代码可以用,

不知道48*48的怎么加

如果改为一个过程函数就方便了,

只要 iocexe(图标文件,修改文件,大小);

以前我看过一个控件,但是只支持D3的
 
是不是只要改编号为1的图标就可以了?
我怎么看到VB编的程序图标编号是30001?
 
后退
顶部