在我们日常使用电脑时,我们常常需要打开一些常用的程序。这些程序会在我们的界面上展示出一个特定的图标,以便我们快速地识别和找到它们。但是在某些情况下,我们可能会希望更改这些程序图标,例如让它们更符合自己的个人喜好或主题。
在本篇文章中,我们将着重介绍如何使用golang和一些系统库来更改程序的图标。我们将使用Windows作为我们的演示环境。
首先,让我们概述一下我们需要进行的基本步骤:
接下来,我们将逐一讨论如何完成这些步骤。
第一步:打开资源文件并找到图标资源
在golang中,我们可以使用系统库“syscall”中的函数来打开和读取文件。为此,我们需要定义一些必要的变量:
package main import ( "os" "syscall" "unsafe" ) var ( kernel32DLL = syscall.MustLoadDLL("kernel32.dll") BeginUpdateResourceProc = kernel32DLL.MustFindProc("BeginUpdateResourceW") UpdateResourceProc = kernel32DLL.MustFindProc("UpdateResourceW") EndUpdateResourceProc = kernel32DLL.MustFindProc("EndUpdateResourceW") )
我们这里使用了Windows API中的几个函数,分别是“BeginUpdateResourceW”、“UpdateResourceW”和“EndUpdateResourceW”。这些函数可以帮助我们操作程序资源文件中的资源。
接下来,我们需要打开要更改的程序的资源文件(可以是.exe或.dll文件),并找到其图标资源。这里我们使用了一个名为“findIconIndex”的函数来遍历程序资源文件,找到其图标资源所在的索引号。
func findIconIndex(exePath string) (int, error) { exeFile, err := os.OpenFile(exePath, os.O_RDWR, 0666) defer exeFile.Close() if err != nil { return 0, err } exeStat, err := exeFile.Stat() if err != nil { return 0, err } exeSize := exeStat.Size() // DOS header dosHeader := new(image.DosHeader) err = binary.Read(exeFile, binary.LittleEndian, dosHeader) if err != nil { return 0, err } exeFile.Seek(int64(dosHeader.Lfanew), 0) // File header and optional header fileHeader := new(image.FileHeader) err = binary.Read(exeFile, binary.LittleEndian, fileHeader) if err != nil { return 0, err } extHeader := make([]byte, fileHeader.SizeOfOptionalHeader-2) exeFile.Read(extHeader) // Section headers sections := make([]image.SectionHeader, fileHeader.NumberOfSections) err = binary.Read(exeFile, binary.LittleEndian, sections) if err != nil { return 0, err } // Find icon resource for _, section := range sections { if section.Name == ".rsrc" { exeFile.Seek(int64(section.Offset), 0) resourceHeader := new(resourceHeader) err = binary.Read(exeFile, binary.LittleEndian, resourceHeader) if err != nil { return 0, err } stack := []resourceDirectoryEntry{resourceDirectoryEntry{uint32(resourceHeader.RootID), int64(resourceHeader.OffsetToDirectory)}} for len(stack) > 0 { currentEntry := stack[len(stack)-1] stack = stack[:len(stack)-1] exeFile.Seek(currentEntry.offset, 0) directoryHeader := new(resourceDirectoryHeader) err = binary.Read(exeFile, binary.LittleEndian, directoryHeader) if err != nil { return 0, err } entries := make([]resourceDirectoryEntry, directoryHeader.NumNamedEntries+directoryHeader.NumIDEntries) for i := range entries { err = binary.Read(exeFile, binary.LittleEndian, &entries[i]) if err != nil { return 0, err } if entries[i].nameIsString { nameBytes := make([]byte, entries[i].nameOffset&0x7FFFFFFF) exeFile.Read(nameBytes) entries[i].name = syscall.UTF16ToString(nameBytes) } } for _, entry := range entries { if entry.ID&^0xFFFF == rtIcon { return int(entry.ID & 0xFFFF), nil } else if entry.name == "ICON" { stack = append(stack, resourceDirectoryEntry{entry.ID, int64(entry.offset)}) } else if entry.name == "#0" && entry.ID&^0xFFFF == rtGroupIcon { groupIconDirHeader := new(resourceGroupIconDirectoryHeader) exeFile.Seek(int64(entry.offset), 0) err = binary.Read(exeFile, binary.LittleEndian, groupIconDirHeader) if err != nil { return 0, err } var largestIcon resourceGroupIconDirectoryEntry for i := 0; i < int(groupIconDirHeader.Count); i++ { groupIconDirEntry := new(resourceGroupIconDirectoryEntry) err = binary.Read(exeFile, binary.LittleEndian, groupIconDirEntry) if err != nil { return 0, err } if groupIconDirEntry.Width > largestIcon.Width || groupIconDirEntry.Height > largestIcon.Height { largestIcon = *groupIconDirEntry } } return int(largestIcon.ID), nil } else if entry.name == "ICONGROUP" { stack = append(stack, resourceDirectoryEntry{entry.ID, int64(entry.offset)}) } else if entry.name == "MAINICON" { stack = append(stack, resourceDirectoryEntry{entry.ID, int64(entry.offset)}) } else { stack = append(stack, resourceDirectoryEntry{entry.ID, int64(entry.offset)}) } } } return 0, fmt.Errorf("Icon not found") } } return 0, fmt.Errorf("Resource not found") }
此函数遍历程序资源文件中的每个节(.rsrc)并找到图标资源的索引。通常情况下,索引取决于图标资源的大小和格式。我们可以自行决定要更改的图标资源在资源文件中的索引。
第二步:将新的图标资源添加到资源文件中
要将新的图标资源添加到程序资源文件中,我们需要先将其保存为ICO文件格式。我们可以使用golang中的image库来创建ICO文件。
package main import ( "image" "image/draw" "image/png" "os" ) func writeIcoFile(icon image.Image, filename string) error { file, err := os.Create(filename) if err != nil { return err } defer file.Close() // Create icon file header iconSize := icon.Bounds().Size() fileHeader := new(resourceIconFileHeader) fileHeader.Reserved = 0 fileHeader.Type = 1 fileHeader.Count = 1 // Create icon directory entry dirEntry := new(resourceIconDirectoryEntry) dirEntry.Width = uint8(iconSize.X) dirEntry.Height = uint8(iconSize.Y) dirEntry.Colors = 0 dirEntry.Reserved = 0 dirEntry.Plane = 1 dirEntry.BitCount = 32 dirEntry.SizeInBytes = uint32(40 + 4*iconSize.X*iconSize.Y) dirEntry.Offset = 22 // Create bitmap info header and color mask for bitmap graphics colorMask := [12]byte{0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00} infoHeader := new(bitmapInfoHeader) infoHeader.Size = 40 infoHeader.Width = int32(iconSize.X) infoHeader.Height = int32(2 * iconSize.Y) infoHeader.Planes = 1 infoHeader.BitCount = 32 infoHeader.Compression = 0 infoHeader.SizeImage = uint32(4 * iconSize.X * iconSize.Y) infoHeader.XPelsPerMeter = 0 infoHeader.YPelsPerMeter = 0 infoHeader.ClrUsed = 0 infoHeader.ClrImportant = 0 // Write icon file header, directory entry, bitmap info header and color mask binary.Write(file, binary.LittleEndian, fileHeader) binary.Write(file, binary.LittleEndian, dirEntry) binary.Write(file, binary.LittleEndian, infoHeader) binary.Write(file, binary.LittleEndian, colorMask) // Write bitmap graphics rgba := image.NewRGBA(image.Rect(0, 0, iconSize.X, 2*iconSize.Y)) draw.Draw(rgba, rgba.Bounds(), image.Black, image.ZP, draw.Src) draw.Draw(rgba, image.Rect(0, 0, iconSize.X, iconSize.Y), icon, image.ZP, draw.Over) draw.Draw(rgba, image.Rect(0, iconSize.Y, iconSize.X, 2*iconSize.Y), image.Transparent, image.ZP, draw.Src) err = png.Encode(file, rgba) if err != nil { return err } return nil }
这个函数创建了一个ICO文件头,并将图标资源附加到其中。ICO文件头包含有关ICO文件中的图标资源的必要信息。
接下来,我们将它们写入资源文件中。我们需要使用Windows API中的“BeginUpdateResource”、“UpdateResource”和“EndUpdateResource”函数来执行此操作。
func updateIcon(exePath, icoPath string, iconIndex int) error { exeFile, err := os.OpenFile(exePath, os.O_RDWR, 0666) defer exeFile.Close() if err != nil { return err } icoFile, err := os.Open(icoPath) defer icoFile.Close() if err != nil { return err } // Read ICO file and prepare icon directory entry icoData, err := ioutil.ReadAll(icoFile) if err != nil { return err } dirEntry := new(resourceIconDirectoryEntry) dirEntry.Width = 0 dirEntry.Height = 0 dirEntry.Colors = 0 dirEntry.Reserved = 0 dirEntry.Plane = 1 dirEntry.BitCount = 0 dirEntry.SizeInBytes = uint32(len(icoData)) dirEntry.Offset = 22 // Find update handle exeHandle, err := syscall.CreateFile(syscall.StringToUTF16Ptr(exePath), syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_ATTRIBUTE_NORMAL, 0) if err != nil { return err } defer syscall.CloseHandle(exeHandle) updateHandle, _, err := BeginUpdateResourceProc.Call(uintptr(exeHandle), 0) defer syscall.CloseHandle(syscall.Handle(updateHandle)) if updateHandle == 0 { return fmt.Errorf("BeginUpdateResourceW failed") } // Write resource to update handle success, _, err := UpdateResourceProc.Call(uintptr(updateHandle), uintptr(rtIcon), uintptr(iconIndex), 0, uintptr(unsafe.Pointer(&icoData[0])), uintptr(len(icoData))) if success == 0 { return fmt.Errorf("UpdateResourceW failed") } // Write updated icon directory entry success, _, err = UpdateResourceProc.Call(uintptr(updateHandle), uintptr(rtGroupIcon), uintptr(MAKEINTRESOURCE(iconIndex)), 0, uintptr(unsafe.Pointer(dirEntry)), uintptr(unsafe.Sizeof(*dirEntry))) if success == 0 { return fmt.Errorf("UpdateResourceW failed") } // Commit update handle success, _, err = EndUpdateResourceProc.Call(updateHandle, 0) if success == 0 { return fmt.Errorf("EndUpdateResourceW failed") } return nil }
第三步:更改程序的.manifest文件
我们需要更改程序的.manifest文件,以便它可以访问新的图标资源。为此,我们需要在.manifest文件中添加以下内容:
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <assemblyIdentity type="win32" name="MyApplication" version="1.0.0.0" processorArchitecture="x86" /> <icon type="group">MyIconResourceIndex</icon> </assembly>
这将为程序指定一个图标资源索引号,以便它可以使用新的图标资源。我们可以使用golang中的os库来更改.manifest文件。
func updateManifest(manifestPath string, iconIndex int) error { manifestData, err := ioutil.ReadFile(manifestPath) if err != nil { return err } updatedManifest := strings.Replace(string(manifestData), "</assembly>", " <icon type="group">"+strconv.Itoa(iconIndex)+"</icon> </assembly>", 1) err = ioutil.WriteFile(manifestPath, []byte(updatedManifest), 0666) if err != nil { return err } return nil }
现在,我们已经知道了如何使用golang和系统库来更改程序的图标。将这些步骤组合在一起,我们构建了一个完整的功能。下面是一个示例代码:
package main import ( "encoding/binary" "encoding/hex" "fmt" "image" "image/png" "io/ioutil" "os" "strconv" "strings" "syscall" "unsafe" ) const ( rtIcon = 14 rtGroupIcon = rtIcon + 11 LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020 ) // Resource header type resourceHeader struct { RootID uint16 RootType uint16 RootName [16]uint16 RootDataSize uint32 RootDataVer uint32 RootDate uint32 RootRMLow uint16 RootRMHigh uint16 RootLangID uint16 RootDataVerOS uint32 } // Resource directory header type resourceDirectoryHeader struct { Characteristics uint32 TimeDateStamp uint32 VersionMajor uint16 VersionMinor uint16 NumNamedEntries uint16 NumIDEntries uint16 } // Resource directory entry type resourceDirectoryEntry struct { nameIsString bool ID uint32 offset uint32 nameOffset uint32 name string } // Resource icon file header type resourceIconFileHeader struct { Reserved uint16 Type uint16 Count uint16 } // Resource icon directory entry type resourceIconDirectoryEntry struct { Width uint8 Height uint8 Colors uint8 Reserved uint8 Plane uint16 BitCount uint16 SizeInBytes uint32 Offset uint32 } // Resource group icon directory header type resourceGroupIconDirectoryHeader struct { Width uint16 Height uint16 ColorCount uint16 Reserved uint16 Planes uint16 BitCount uint16 Count uint32 } // Resource group icon directory entry type resourceGroupIconDirectoryEntry struct { Width uint8 Height uint8 ColorCount uint8 Reserved uint8 Planes uint16 BitCount uint16 BytesInRes uint32 ID uint16 } // Bitmap header type bitmapInfoHeader struct { Size uint32 Width int32 Height int32 Planes uint16 BitCount uint16 Compression uint32 SizeImage uint32 XPelsPerMeter int32 YPelsPerMeter int32 ClrUsed uint32 ClrImportant uint32 } var ( kernel32DLL = syscall.MustLoadDLL("kernel32.dll") user32DLL = syscall.MustLoadDLL("user32.dll") autoDetectEncodingProc = kernel32DLL.MustFindProc("AutoDetectEncoding") BeginUpdateResourceProc = kernel32DLL.MustFindProc("BeginUpdateResourceW") LoadImageProc = user32DLL.MustFindProc("LoadImageW") ResourceNotFound = fmt.Errorf("Resource not found") NoIconFound = fmt.Errorf("Icon not found") ) func main() { exePath := "path/to/program.exe" icoPath := "path/to/newicon.png" manifestPath := "path/to/program.exe.manifest" iconIndex, err := findIconIndex(exePath)
以上是golang程序图标修改的详细内容。更多信息请关注PHP中文网其他相关文章!