您的位置:首页 > 技术中心 > 其他 >

golang程序图标修改

时间:2023-05-11 01:16

在我们日常使用电脑时,我们常常需要打开一些常用的程序。这些程序会在我们的界面上展示出一个特定的图标,以便我们快速地识别和找到它们。但是在某些情况下,我们可能会希望更改这些程序图标,例如让它们更符合自己的个人喜好或主题。

在本篇文章中,我们将着重介绍如何使用golang和一些系统库来更改程序的图标。我们将使用Windows作为我们的演示环境。

首先,让我们概述一下我们需要进行的基本步骤:

  1. 打开程序的资源文件(.exe或.dll文件)并找到它的图标资源。
  2. 将新的图标资源添加到程序的资源文件中。
  3. 更改程序的.manifest文件,以便它可以访问新的图标资源。

接下来,我们将逐一讨论如何完成这些步骤。

第一步:打开资源文件并找到图标资源

在golang中,我们可以使用系统库“syscall”中的函数来打开和读取文件。为此,我们需要定义一些必要的变量:

package mainimport (    "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 mainimport (    "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 mainimport (    "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 headertype 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 headertype resourceDirectoryHeader struct {    Characteristics uint32    TimeDateStamp   uint32    VersionMajor    uint16    VersionMinor    uint16    NumNamedEntries uint16    NumIDEntries    uint16}// Resource directory entrytype resourceDirectoryEntry struct {    nameIsString bool    ID           uint32    offset       uint32    nameOffset   uint32    name         string}// Resource icon file headertype resourceIconFileHeader struct {    Reserved uint16    Type     uint16    Count    uint16}// Resource icon directory entrytype resourceIconDirectoryEntry struct {    Width       uint8    Height      uint8    Colors      uint8    Reserved    uint8    Plane       uint16    BitCount    uint16    SizeInBytes uint32    Offset      uint32}// Resource group icon directory headertype resourceGroupIconDirectoryHeader struct {    Width    uint16    Height   uint16    ColorCount uint16    Reserved uint16    Planes   uint16    BitCount uint16    Count    uint32}// Resource group icon directory entrytype resourceGroupIconDirectoryEntry struct {    Width      uint8    Height     uint8    ColorCount uint8    Reserved   uint8    Planes     uint16    BitCount   uint16    BytesInRes uint32    ID         uint16}// Bitmap headertype 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程序图标修改的详细内容,更多请关注Gxl网其它相关文章!

热门排行

今日推荐

热门手游