feat(infra): set_exe_icon — embed icono .ico en .exe Windows post-build
Implementacion Go pura sin dependencias externas (sin rcedit, wine, ni rsrc). Parsea ICONDIR + ICONDIRENTRY del .ico, construye un IMAGE_RESOURCE_DIRECTORY tree con RT_ICON + RT_GROUP_ICON, y appendea una nueva seccion .rsrc al PE. Soporta PE32 y PE32+. No soporta exe que ya tienen recursos (retorna error). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,406 @@
|
|||||||
|
package infra
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
rtIcon = 3
|
||||||
|
rtGroupIcon = 14
|
||||||
|
|
||||||
|
scnCntInitializedData = 0x00000040
|
||||||
|
scnMemRead = 0x40000000
|
||||||
|
|
||||||
|
dataDirResource = 2
|
||||||
|
resourceTableSlot = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
type icoEntry struct {
|
||||||
|
width uint8
|
||||||
|
height uint8
|
||||||
|
colors uint8
|
||||||
|
reserved uint8
|
||||||
|
planes uint16
|
||||||
|
bitCount uint16
|
||||||
|
size uint32
|
||||||
|
offset uint32
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseICO(buf []byte) ([]icoEntry, error) {
|
||||||
|
if len(buf) < 6 {
|
||||||
|
return nil, fmt.Errorf("ico too short")
|
||||||
|
}
|
||||||
|
if binary.LittleEndian.Uint16(buf[0:2]) != 0 {
|
||||||
|
return nil, fmt.Errorf("ico reserved field must be 0")
|
||||||
|
}
|
||||||
|
if binary.LittleEndian.Uint16(buf[2:4]) != 1 {
|
||||||
|
return nil, fmt.Errorf("not an icon (type != 1)")
|
||||||
|
}
|
||||||
|
count := int(binary.LittleEndian.Uint16(buf[4:6]))
|
||||||
|
if count == 0 {
|
||||||
|
return nil, fmt.Errorf("ico has 0 images")
|
||||||
|
}
|
||||||
|
if len(buf) < 6+16*count {
|
||||||
|
return nil, fmt.Errorf("ico header truncated")
|
||||||
|
}
|
||||||
|
entries := make([]icoEntry, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
off := 6 + 16*i
|
||||||
|
e := icoEntry{
|
||||||
|
width: buf[off],
|
||||||
|
height: buf[off+1],
|
||||||
|
colors: buf[off+2],
|
||||||
|
reserved: buf[off+3],
|
||||||
|
planes: binary.LittleEndian.Uint16(buf[off+4 : off+6]),
|
||||||
|
bitCount: binary.LittleEndian.Uint16(buf[off+6 : off+8]),
|
||||||
|
size: binary.LittleEndian.Uint32(buf[off+8 : off+12]),
|
||||||
|
offset: binary.LittleEndian.Uint32(buf[off+12 : off+16]),
|
||||||
|
}
|
||||||
|
if int(e.offset)+int(e.size) > len(buf) {
|
||||||
|
return nil, fmt.Errorf("ico image %d out of bounds", i)
|
||||||
|
}
|
||||||
|
e.data = buf[e.offset : e.offset+e.size]
|
||||||
|
entries[i] = e
|
||||||
|
}
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type peLayout struct {
|
||||||
|
data []byte
|
||||||
|
peOff int
|
||||||
|
is64 bool
|
||||||
|
numSections int
|
||||||
|
sizeOptHdr int
|
||||||
|
sectionAlign uint32
|
||||||
|
fileAlign uint32
|
||||||
|
sectionsStart int
|
||||||
|
sizeOfImage uint32
|
||||||
|
dataDirCount uint32
|
||||||
|
dataDirOff int
|
||||||
|
checksumOff int
|
||||||
|
sizeOfImageOff int
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePE(buf []byte) (*peLayout, error) {
|
||||||
|
if len(buf) < 64 || buf[0] != 'M' || buf[1] != 'Z' {
|
||||||
|
return nil, fmt.Errorf("not a PE file (no MZ signature)")
|
||||||
|
}
|
||||||
|
peOff := int(binary.LittleEndian.Uint32(buf[0x3C:0x40]))
|
||||||
|
if peOff+24 > len(buf) {
|
||||||
|
return nil, fmt.Errorf("PE header out of bounds")
|
||||||
|
}
|
||||||
|
if string(buf[peOff:peOff+4]) != "PE\x00\x00" {
|
||||||
|
return nil, fmt.Errorf("not a PE file (no PE signature at e_lfanew)")
|
||||||
|
}
|
||||||
|
coff := peOff + 4
|
||||||
|
numSections := int(binary.LittleEndian.Uint16(buf[coff+2 : coff+4]))
|
||||||
|
sizeOptHdr := int(binary.LittleEndian.Uint16(buf[coff+16 : coff+18]))
|
||||||
|
optOff := coff + 20
|
||||||
|
if optOff+sizeOptHdr > len(buf) {
|
||||||
|
return nil, fmt.Errorf("optional header truncated")
|
||||||
|
}
|
||||||
|
magic := binary.LittleEndian.Uint16(buf[optOff : optOff+2])
|
||||||
|
var is64 bool
|
||||||
|
switch magic {
|
||||||
|
case 0x10B:
|
||||||
|
is64 = false
|
||||||
|
case 0x20B:
|
||||||
|
is64 = true
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown optional header magic 0x%X", magic)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sectionAlignOff, fileAlignOff, sizeOfImageOff, checksumOff, numRvaOff, dataDirOff int
|
||||||
|
if is64 {
|
||||||
|
sectionAlignOff = optOff + 32
|
||||||
|
fileAlignOff = optOff + 36
|
||||||
|
sizeOfImageOff = optOff + 56
|
||||||
|
checksumOff = optOff + 64
|
||||||
|
numRvaOff = optOff + 108
|
||||||
|
dataDirOff = optOff + 112
|
||||||
|
} else {
|
||||||
|
sectionAlignOff = optOff + 32
|
||||||
|
fileAlignOff = optOff + 36
|
||||||
|
sizeOfImageOff = optOff + 56
|
||||||
|
checksumOff = optOff + 64
|
||||||
|
numRvaOff = optOff + 92
|
||||||
|
dataDirOff = optOff + 96
|
||||||
|
}
|
||||||
|
dataDirCount := binary.LittleEndian.Uint32(buf[numRvaOff : numRvaOff+4])
|
||||||
|
|
||||||
|
return &peLayout{
|
||||||
|
data: buf,
|
||||||
|
peOff: peOff,
|
||||||
|
is64: is64,
|
||||||
|
numSections: numSections,
|
||||||
|
sizeOptHdr: sizeOptHdr,
|
||||||
|
sectionAlign: binary.LittleEndian.Uint32(buf[sectionAlignOff : sectionAlignOff+4]),
|
||||||
|
fileAlign: binary.LittleEndian.Uint32(buf[fileAlignOff : fileAlignOff+4]),
|
||||||
|
sectionsStart: optOff + sizeOptHdr,
|
||||||
|
sizeOfImage: binary.LittleEndian.Uint32(buf[sizeOfImageOff : sizeOfImageOff+4]),
|
||||||
|
dataDirCount: dataDirCount,
|
||||||
|
dataDirOff: dataDirOff,
|
||||||
|
checksumOff: checksumOff,
|
||||||
|
sizeOfImageOff: sizeOfImageOff,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sectionHdr struct {
|
||||||
|
name string
|
||||||
|
virtualSize uint32
|
||||||
|
virtualAddress uint32
|
||||||
|
sizeOfRawData uint32
|
||||||
|
pointerToRawData uint32
|
||||||
|
characteristics uint32
|
||||||
|
headerOff int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *peLayout) sections() []sectionHdr {
|
||||||
|
out := make([]sectionHdr, p.numSections)
|
||||||
|
for i := 0; i < p.numSections; i++ {
|
||||||
|
off := p.sectionsStart + 40*i
|
||||||
|
nameBytes := p.data[off : off+8]
|
||||||
|
end := bytes.IndexByte(nameBytes, 0)
|
||||||
|
if end < 0 {
|
||||||
|
end = 8
|
||||||
|
}
|
||||||
|
out[i] = sectionHdr{
|
||||||
|
name: string(nameBytes[:end]),
|
||||||
|
virtualSize: binary.LittleEndian.Uint32(p.data[off+8 : off+12]),
|
||||||
|
virtualAddress: binary.LittleEndian.Uint32(p.data[off+12 : off+16]),
|
||||||
|
sizeOfRawData: binary.LittleEndian.Uint32(p.data[off+16 : off+20]),
|
||||||
|
pointerToRawData: binary.LittleEndian.Uint32(p.data[off+20 : off+24]),
|
||||||
|
characteristics: binary.LittleEndian.Uint32(p.data[off+36 : off+40]),
|
||||||
|
headerOff: off,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *peLayout) hasRsrc() bool {
|
||||||
|
if p.dataDirCount > resourceTableSlot {
|
||||||
|
off := p.dataDirOff + resourceTableSlot*8
|
||||||
|
va := binary.LittleEndian.Uint32(p.data[off : off+4])
|
||||||
|
size := binary.LittleEndian.Uint32(p.data[off+4 : off+8])
|
||||||
|
if va != 0 && size != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range p.sections() {
|
||||||
|
if s.name == ".rsrc" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func alignUp(v, a uint32) uint32 {
|
||||||
|
if a == 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return (v + a - 1) &^ (a - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildResourceSection(entries []icoEntry, baseRVA uint32) ([]byte, error) {
|
||||||
|
n := uint32(len(entries))
|
||||||
|
|
||||||
|
rootDirSize := uint32(16 + 2*8)
|
||||||
|
rtIconDirSize := uint32(16 + n*8)
|
||||||
|
perIconNameDirSize := uint32(16 + 8)
|
||||||
|
rtGroupDirSize := uint32(16 + 8)
|
||||||
|
groupNameDirSize := uint32(16 + 8)
|
||||||
|
|
||||||
|
leafEntrySize := uint32(16)
|
||||||
|
totalLeafEntries := n + 1
|
||||||
|
leavesSize := leafEntrySize * totalLeafEntries
|
||||||
|
|
||||||
|
dirsSize := rootDirSize + rtIconDirSize + n*perIconNameDirSize + rtGroupDirSize + groupNameDirSize
|
||||||
|
|
||||||
|
dirsAndLeaves := dirsSize + leavesSize
|
||||||
|
|
||||||
|
groupDataSize := uint32(6 + 14*n)
|
||||||
|
dataStartOff := dirsAndLeaves
|
||||||
|
groupDataOff := dataStartOff
|
||||||
|
groupDataPadded := alignUp(groupDataSize, 4)
|
||||||
|
|
||||||
|
iconDataOffsets := make([]uint32, n)
|
||||||
|
cursor := groupDataOff + groupDataPadded
|
||||||
|
for i, e := range entries {
|
||||||
|
iconDataOffsets[i] = cursor
|
||||||
|
cursor += alignUp(uint32(len(e.data)), 4)
|
||||||
|
}
|
||||||
|
totalSize := cursor
|
||||||
|
|
||||||
|
out := make([]byte, totalSize)
|
||||||
|
|
||||||
|
leafOff := dirsSize
|
||||||
|
groupLeafOff := leafOff
|
||||||
|
iconLeavesStart := leafOff + leafEntrySize
|
||||||
|
|
||||||
|
rootOff := uint32(0)
|
||||||
|
rtGroupSubOff := rootDirSize
|
||||||
|
rtIconSubOff := rtGroupSubOff + rtGroupDirSize
|
||||||
|
groupNameSubOff := rtIconSubOff + rtIconDirSize
|
||||||
|
perIconNamesStart := groupNameSubOff + groupNameDirSize
|
||||||
|
|
||||||
|
writeDirHeader := func(off uint32, idEntries uint16) {
|
||||||
|
binary.LittleEndian.PutUint32(out[off:off+4], 0)
|
||||||
|
binary.LittleEndian.PutUint32(out[off+4:off+8], 0)
|
||||||
|
binary.LittleEndian.PutUint16(out[off+8:off+10], 0)
|
||||||
|
binary.LittleEndian.PutUint16(out[off+10:off+12], 0)
|
||||||
|
binary.LittleEndian.PutUint16(out[off+12:off+14], 0)
|
||||||
|
binary.LittleEndian.PutUint16(out[off+14:off+16], idEntries)
|
||||||
|
}
|
||||||
|
writeIDEntry := func(off uint32, id uint32, target uint32, isDir bool) {
|
||||||
|
binary.LittleEndian.PutUint32(out[off:off+4], id)
|
||||||
|
val := target
|
||||||
|
if isDir {
|
||||||
|
val |= 0x80000000
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint32(out[off+4:off+8], val)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeDirHeader(rootOff, 2)
|
||||||
|
writeIDEntry(rootOff+16, rtIcon, rtIconSubOff, true)
|
||||||
|
writeIDEntry(rootOff+24, rtGroupIcon, rtGroupSubOff, true)
|
||||||
|
|
||||||
|
writeDirHeader(rtGroupSubOff, 1)
|
||||||
|
writeIDEntry(rtGroupSubOff+16, 1, groupNameSubOff, true)
|
||||||
|
|
||||||
|
writeDirHeader(groupNameSubOff, 1)
|
||||||
|
writeIDEntry(groupNameSubOff+16, 0, groupLeafOff, false)
|
||||||
|
|
||||||
|
writeDirHeader(rtIconSubOff, uint16(n))
|
||||||
|
for i := uint32(0); i < n; i++ {
|
||||||
|
writeIDEntry(rtIconSubOff+16+i*8, i+1, perIconNamesStart+i*perIconNameDirSize, true)
|
||||||
|
}
|
||||||
|
for i := uint32(0); i < n; i++ {
|
||||||
|
nameDirOff := perIconNamesStart + i*perIconNameDirSize
|
||||||
|
writeDirHeader(nameDirOff, 1)
|
||||||
|
leafForIcon := iconLeavesStart + i*leafEntrySize
|
||||||
|
writeIDEntry(nameDirOff+16, 0, leafForIcon, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLeaf := func(off uint32, dataOff uint32, size uint32) {
|
||||||
|
binary.LittleEndian.PutUint32(out[off:off+4], baseRVA+dataOff)
|
||||||
|
binary.LittleEndian.PutUint32(out[off+4:off+8], size)
|
||||||
|
binary.LittleEndian.PutUint32(out[off+8:off+12], 0)
|
||||||
|
binary.LittleEndian.PutUint32(out[off+12:off+16], 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeLeaf(groupLeafOff, groupDataOff, groupDataSize)
|
||||||
|
for i := uint32(0); i < n; i++ {
|
||||||
|
writeLeaf(iconLeavesStart+i*leafEntrySize, iconDataOffsets[i], uint32(len(entries[i].data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint16(out[groupDataOff:groupDataOff+2], 0)
|
||||||
|
binary.LittleEndian.PutUint16(out[groupDataOff+2:groupDataOff+4], 1)
|
||||||
|
binary.LittleEndian.PutUint16(out[groupDataOff+4:groupDataOff+6], uint16(n))
|
||||||
|
for i, e := range entries {
|
||||||
|
eo := groupDataOff + 6 + uint32(i)*14
|
||||||
|
out[eo] = e.width
|
||||||
|
out[eo+1] = e.height
|
||||||
|
out[eo+2] = e.colors
|
||||||
|
out[eo+3] = e.reserved
|
||||||
|
binary.LittleEndian.PutUint16(out[eo+4:eo+6], e.planes)
|
||||||
|
binary.LittleEndian.PutUint16(out[eo+6:eo+8], e.bitCount)
|
||||||
|
binary.LittleEndian.PutUint32(out[eo+8:eo+12], e.size)
|
||||||
|
binary.LittleEndian.PutUint16(out[eo+12:eo+14], uint16(i+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, e := range entries {
|
||||||
|
copy(out[iconDataOffsets[i]:], e.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExeIcon embebe el icono del archivo .ico en el .exe sobreescribiendo
|
||||||
|
// el archivo. Funciona para PE32 y PE32+ que aun no tienen seccion .rsrc
|
||||||
|
// (caso comun de binarios Go compilados sin icono). Si el .exe ya tiene
|
||||||
|
// recursos retorna error.
|
||||||
|
func SetExeIcon(exePath, icoPath string) error {
|
||||||
|
icoBuf, err := os.ReadFile(icoPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read ico: %w", err)
|
||||||
|
}
|
||||||
|
entries, err := parseICO(icoBuf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse ico: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exeBuf, err := os.ReadFile(exePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("read exe: %w", err)
|
||||||
|
}
|
||||||
|
pe, err := parsePE(exeBuf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parse pe: %w", err)
|
||||||
|
}
|
||||||
|
if pe.hasRsrc() {
|
||||||
|
return fmt.Errorf("exe already has .rsrc resources; not supported")
|
||||||
|
}
|
||||||
|
if pe.dataDirCount <= resourceTableSlot {
|
||||||
|
return fmt.Errorf("optional header DataDirectory has only %d entries (need >= %d)", pe.dataDirCount, resourceTableSlot+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
sections := pe.sections()
|
||||||
|
if len(sections) == 0 {
|
||||||
|
return fmt.Errorf("exe has no sections")
|
||||||
|
}
|
||||||
|
last := sections[len(sections)-1]
|
||||||
|
|
||||||
|
newSecHdrOff := pe.sectionsStart + 40*pe.numSections
|
||||||
|
if newSecHdrOff+40 > int(last.pointerToRawData) {
|
||||||
|
return fmt.Errorf("not enough space in PE headers for new section header")
|
||||||
|
}
|
||||||
|
|
||||||
|
newRVA := alignUp(last.virtualAddress+last.virtualSize, pe.sectionAlign)
|
||||||
|
newRawOff := alignUp(last.pointerToRawData+last.sizeOfRawData, pe.fileAlign)
|
||||||
|
|
||||||
|
rsrc, err := buildResourceSection(entries, newRVA)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("build resource section: %w", err)
|
||||||
|
}
|
||||||
|
rawSize := alignUp(uint32(len(rsrc)), pe.fileAlign)
|
||||||
|
virtSize := uint32(len(rsrc))
|
||||||
|
|
||||||
|
out := make([]byte, 0, int(newRawOff)+int(rawSize))
|
||||||
|
out = append(out, exeBuf[:newRawOff]...)
|
||||||
|
if int(newRawOff) > len(exeBuf) {
|
||||||
|
out = append(out, make([]byte, int(newRawOff)-len(exeBuf))...)
|
||||||
|
}
|
||||||
|
out = append(out, rsrc...)
|
||||||
|
if rawSize > uint32(len(rsrc)) {
|
||||||
|
out = append(out, make([]byte, rawSize-uint32(len(rsrc)))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr := make([]byte, 40)
|
||||||
|
copy(hdr[0:8], ".rsrc\x00\x00\x00")
|
||||||
|
binary.LittleEndian.PutUint32(hdr[8:12], virtSize)
|
||||||
|
binary.LittleEndian.PutUint32(hdr[12:16], newRVA)
|
||||||
|
binary.LittleEndian.PutUint32(hdr[16:20], rawSize)
|
||||||
|
binary.LittleEndian.PutUint32(hdr[20:24], newRawOff)
|
||||||
|
binary.LittleEndian.PutUint32(hdr[36:40], scnCntInitializedData|scnMemRead)
|
||||||
|
copy(out[newSecHdrOff:newSecHdrOff+40], hdr)
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint16(out[pe.peOff+4+2:pe.peOff+4+4], uint16(pe.numSections+1))
|
||||||
|
|
||||||
|
rsrcEntryOff := pe.dataDirOff + resourceTableSlot*8
|
||||||
|
binary.LittleEndian.PutUint32(out[rsrcEntryOff:rsrcEntryOff+4], newRVA)
|
||||||
|
binary.LittleEndian.PutUint32(out[rsrcEntryOff+4:rsrcEntryOff+8], virtSize)
|
||||||
|
|
||||||
|
newSizeOfImage := alignUp(newRVA+virtSize, pe.sectionAlign)
|
||||||
|
binary.LittleEndian.PutUint32(out[pe.sizeOfImageOff:pe.sizeOfImageOff+4], newSizeOfImage)
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(out[pe.checksumOff:pe.checksumOff+4], 0)
|
||||||
|
|
||||||
|
if err := os.WriteFile(exePath, out, 0o755); err != nil {
|
||||||
|
return fmt.Errorf("write exe: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
name: set_exe_icon
|
||||||
|
kind: function
|
||||||
|
lang: go
|
||||||
|
domain: infra
|
||||||
|
version: "1.0.0"
|
||||||
|
purity: impure
|
||||||
|
signature: "func SetExeIcon(exePath, icoPath string) error"
|
||||||
|
description: "Embebe un icono (.ico multi-tamaño) en un ejecutable PE Windows post-build. Implementacion Go pura sin dependencias externas (sin rcedit/wine/rsrc). Parsea el ICONDIR + ICONDIRENTRY del .ico, construye un IMAGE_RESOURCE_DIRECTORY tree con RT_ICON + RT_GROUP_ICON, y appendea una nueva seccion .rsrc al PE. Soporta PE32 y PE32+. No soporta exe que ya tienen recursos (retorna error)."
|
||||||
|
tags: [windows, pe, exe, icon, rcedit, post-build]
|
||||||
|
uses_functions: []
|
||||||
|
uses_types: []
|
||||||
|
returns: []
|
||||||
|
returns_optional: false
|
||||||
|
error_type: "error_go_core"
|
||||||
|
imports: [bytes, encoding/binary, fmt, os]
|
||||||
|
params:
|
||||||
|
- name: exePath
|
||||||
|
desc: "ruta absoluta o relativa al .exe Windows a modificar (se sobreescribe in-place)"
|
||||||
|
- name: icoPath
|
||||||
|
desc: "ruta al archivo .ico con uno o mas tamaños de icono"
|
||||||
|
output: "nil si el icono se embebio correctamente; error si el .exe ya tiene recursos, no es PE valido, o el .ico es invalido"
|
||||||
|
tested: false
|
||||||
|
tests: []
|
||||||
|
test_file_path: ""
|
||||||
|
file_path: "functions/infra/set_exe_icon.go"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejemplo
|
||||||
|
|
||||||
|
```go
|
||||||
|
err := infra.SetExeIcon("myapp.exe", "logo.ico")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notas
|
||||||
|
|
||||||
|
- Solo Go binaries cross-compiled a Windows que **no** tengan seccion `.rsrc` previa. La mayoria de binarios Go limpios no la tienen.
|
||||||
|
- Si el .exe ya tiene recursos (creado con `goversioninfo`, `rsrc`, MSVC, etc.), retorna error y no modifica el archivo.
|
||||||
|
- El checksum del PE se pone a 0 tras la modificacion (Windows lo ignora para .exe normales; firmas Authenticode quedarian invalidadas).
|
||||||
|
- Soporta multi-resolucion: si el .ico tiene 16x16, 32x32, 256x256... todos se embeben y Windows elige el mejor.
|
||||||
|
- El icono cambia tras refrescar la cache de iconos de Explorer (a veces requiere `ie4uinit -show` o reiniciar Explorer).
|
||||||
Reference in New Issue
Block a user