提交 0a88d65a 编写于 作者: yanghye's avatar yanghye

移除winres, 在go.mod引入

上级 2c18faf2
package argument
import (
"testing"
)
func TestList(t *testing.T) {
list := &List{
Data: make([]string, 0),
}
json := list.JSON()
json.Add("value1")
json.Add("value2")
json.Add("value3")
if json.Size() != 3 {
t.Fail()
}
if json.GetStringByIndex(1) != "value2" {
t.Fail()
}
data := json.Bytes()
if data == nil {
t.Fail()
}
}
......@@ -14,8 +14,8 @@ require (
github.com/jessevdk/go-flags v1.5.0
github.com/json-iterator/go v1.1.12
github.com/lithammer/fuzzysearch v1.1.8
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/pterm/pterm v0.12.66
github.com/tc-hib/winres v0.2.0
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c
golang.org/x/image v0.12.0
golang.org/x/sys v0.12.0
......@@ -28,6 +28,7 @@ require (
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/term v0.11.0 // indirect
......
......@@ -74,6 +74,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tc-hib/winres v0.2.0 h1:gly/ivDWGvlhl7ENtEmA7wPQ6dWab1LlLq/DgcZECKE=
github.com/tc-hib/winres v0.2.0/go.mod h1:uG6S5M2Q0/kThoqsCSYvGJODUQP9O9R0SNxUPmFIegw=
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c h1:coVla7zpsycc+kA9NXpcvv2E4I7+ii6L5hZO2S6C3kw=
github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
......@@ -83,6 +85,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
golang.org/x/image v0.12.0 h1:w13vZbU4o5rKOFFR8y7M+c4A5jXDC0uXTdHYRP8X2DQ=
golang.org/x/image v0.12.0/go.mod h1:Lu90jvHG7GfemOIcldsh9A2hS01ocl6oNO7ype5mEnk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
......@@ -117,6 +120,7 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
......
Copyright (c) 2021 Thomas Combeléran
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
# winres
Put this file in your project directory, name it "something.syso" or, preferably,
"something_windows_amd64.syso", and you're done :
the `go build` command will detect it and automatically use it.
You should have a look at the [command line tool](https://github.com/tc-hib/go-winres) to try it. Using the library
gives you more control, though.
Here is a quick example:
```go
package main
import (
"io/ioutil"
"os"
"github.com/tc-hib/winres"
)
func main() {
// Start by creating an empty resource set
rs := winres.ResourceSet{}
// Add resources
// This is a cursor named ID(1)
cursorData, _ := ioutil.ReadFile("cursor.cur")
rs.Set(winres.RT_CURSOR, winres.ID(1), 0, cursorData)
// This is a custom data type, translated in english (0x409) and french (0x40C)
// You can find more language IDs by searching for LCID
rs.Set(winres.Name("CUSTOM"), winres.Name("COOLDATA"), 0x409, []byte("Hello World"))
rs.Set(winres.Name("CUSTOM"), winres.Name("COOLDATA"), 0x40C, []byte("Bonjour Monde"))
// Compile to a COFF object file
// It is recommended to use the target suffix "_window_amd64"
// so that `go build` knows when not to include it.
out, _ := os.Create("rsrc_windows_amd64.syso")
rs.WriteObject(out, winres.ArchAMD64)
}
```
# From: github.com/tc-hib/winres
\ No newline at end of file
package winres
import (
"debug/pe"
"encoding/binary"
"errors"
"io"
)
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#other-contents-of-the-file
const (
_IMAGE_SCN_MEM_READ = 0x00000040
_IMAGE_SCN_CNT_INITIALIZED_DATA = 0x40000000
)
const sizeOfReloc = 10
func writeObject(w io.Writer, r *ResourceSet, arch Arch) error {
file := pe.FileHeader{
Machine: pe.IMAGE_FILE_MACHINE_UNKNOWN,
NumberOfSections: 1,
NumberOfSymbols: 1,
}
section := pe.SectionHeader32{
Name: [8]byte{'.', 'r', 's', 'r', 'c'},
PointerToLineNumbers: 0,
Characteristics: _IMAGE_SCN_MEM_READ | _IMAGE_SCN_CNT_INITIALIZED_DATA,
}
section.PointerToRawData = uint32(binary.Size(file) + binary.Size(section))
section.SizeOfRawData = uint32(r.fullSize())
section.PointerToRelocations = section.PointerToRawData + section.SizeOfRawData
section.NumberOfRelocations = uint16(r.numDataEntries())
switch arch {
case ArchI386:
file.Machine = pe.IMAGE_FILE_MACHINE_I386
case ArchAMD64:
file.Machine = pe.IMAGE_FILE_MACHINE_AMD64
case ArchARM:
file.Machine = pe.IMAGE_FILE_MACHINE_ARMNT
case ArchARM64:
file.Machine = pe.IMAGE_FILE_MACHINE_ARM64
default:
return errors.New(errUnknownArch)
}
file.PointerToSymbolTable = section.PointerToRelocations + uint32(section.NumberOfRelocations)*sizeOfReloc
if err := binary.Write(w, binary.LittleEndian, file); err != nil {
return err
}
if err := binary.Write(w, binary.LittleEndian, section); err != nil {
return err
}
addr, err := r.write(w)
if err != nil {
return err
}
if err := writeRelocTable(w, 0, arch, addr); err != nil {
return err
}
if err := writeSymbol(w, 1); err != nil {
return err
}
// Empty string table
if err := binary.Write(w, binary.LittleEndian, uint32(4)); err != nil {
return err
}
return nil
}
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#type-indicators
const (
_IMAGE_REL_I386_DIR32NB uint16 = 0x7
_IMAGE_REL_AMD64_ADDR32NB uint16 = 0x3
_IMAGE_REL_ARM_ADDR32NB uint16 = 0x2
_IMAGE_REL_ARM64_ADDR32NB uint16 = 0x2
)
func writeRelocTable(w io.Writer, symbolIndex int, arch Arch, addr []int) error {
var t uint16
switch arch {
case ArchI386:
t = _IMAGE_REL_I386_DIR32NB
case ArchAMD64:
t = _IMAGE_REL_AMD64_ADDR32NB
case ArchARM:
t = _IMAGE_REL_ARM_ADDR32NB
case ArchARM64:
t = _IMAGE_REL_ARM64_ADDR32NB
default:
return errors.New(errUnknownArch)
}
for _, a := range addr {
err := binary.Write(w, binary.LittleEndian, &pe.Reloc{
VirtualAddress: uint32(a),
SymbolTableIndex: uint32(symbolIndex),
Type: t,
})
if err != nil {
return err
}
}
return nil
}
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#coff-symbol-table
const _IMAGE_SYM_TYPE_NULL = 0
const _IMAGE_SYM_CLASS_STATIC = 3
func writeSymbol(w io.Writer, sectionNumber int) error {
// The symbol is a section name because Value == 0 and StorageClass == IMAGE_SYM_CLASS_STATIC
return binary.Write(w, binary.LittleEndian, &pe.COFFSymbol{
Name: [8]byte{'.', 'r', 's', 'r', 'c'},
Value: 0,
SectionNumber: int16(sectionNumber),
Type: _IMAGE_SYM_TYPE_NULL,
StorageClass: _IMAGE_SYM_CLASS_STATIC,
})
}
package winres
import (
"io"
"testing"
)
func Test_writeObject_WriteErrFileHeader(t *testing.T) {
bw := newBadWriter(19)
rs := &ResourceSet{}
err := writeObject(bw, rs, ArchAMD64)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
}
func Test_writeObject_WriteErrSectionHeader(t *testing.T) {
bw := newBadWriter(58)
rs := &ResourceSet{}
err := writeObject(bw, rs, ArchAMD64)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
}
func Test_writeObject_WriteErrReloc(t *testing.T) {
bw := newBadWriter(165)
rs := &ResourceSet{}
rs.Set(RT_RCDATA, ID(1), 0, make([]byte, 1))
err := writeObject(bw, rs, ArchAMD64)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
}
func Test_writeObject_WriteErrSymbol(t *testing.T) {
bw := newBadWriter(182)
rs := &ResourceSet{}
rs.Set(RT_RCDATA, ID(1), 0, make([]byte, 1))
err := writeObject(bw, rs, ArchAMD64)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
}
func Test_writeObject_WriteErrStringTable(t *testing.T) {
bw := newBadWriter(185)
rs := &ResourceSet{}
rs.Set(RT_RCDATA, ID(1), 0, make([]byte, 1))
err := writeObject(bw, rs, ArchAMD64)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
}
func Test_writeObject_WriteErrSection(t *testing.T) {
bw := newBadWriter(61)
rs := &ResourceSet{}
rs.Set(RT_RCDATA, ID(1), 0, make([]byte, 1))
err := writeObject(bw, rs, ArchAMD64)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
}
func Test_writeRelocTable_UnknownArch(t *testing.T) {
err := writeRelocTable(io.Discard, 1, "*", []int{1, 2, 3, 4})
if err == nil || err.Error() != errUnknownArch {
t.Fail()
}
}
package winres
import (
"bytes"
"encoding/binary"
"errors"
"image"
"io"
"sort"
"golang.org/x/image/bmp"
)
// Cursor describes a mouse cursor.
//
// This structure must only be created by constructors:
// NewCursorFromImages, LoadCUR
type Cursor struct {
images []cursorImage
}
// CursorImage defines an image to import into a cursor.
//
// It is an Image with hot spot coordinates.
type CursorImage struct {
Image image.Image
HotSpot HotSpot
}
// HotSpot is the coordinates of a cursor's hot spot.
type HotSpot struct {
X uint16
Y uint16
}
// NewCursorFromImages makes a cursor from a list of images and hot spots.
func NewCursorFromImages(images []CursorImage) (*Cursor, error) {
cursor := &Cursor{}
for _, img := range images {
if err := cursor.addImage(img.Image, img.HotSpot); err != nil {
return nil, err
}
}
return cursor, nil
}
// LoadCUR loads a CUR file and returns a cursor, ready to embed in a resource set.
func LoadCUR(cur io.ReadSeeker) (*Cursor, error) {
hdr := cursorDirHeader{}
if err := binaryRead(cur, &hdr); err != nil {
return nil, err
}
if hdr.Type != 2 || hdr.Reserved != 0 {
return nil, errors.New(errNotCUR)
}
entries := make([]cursorFileDirEntry, hdr.Count)
if err := binaryRead(cur, entries); err != nil {
return nil, err
}
cursor := &Cursor{}
for _, e := range entries {
// Arbitrary limit: no more than 10MB per image, so we can blindly allocate bytes and try to read them.
if e.BytesInRes > 0xA00000 {
return nil, errors.New(errImageLengthTooBig)
}
if _, err := cur.Seek(int64(e.ImageOffset), io.SeekStart); err != nil {
return nil, err
}
img := make([]byte, e.BytesInRes)
if err := readFull(cur, img); err != nil {
return nil, err
}
planes, bitCount, err := readDIBBitCount(img)
if err != nil {
return nil, err
}
cursor.images = append(cursor.images, cursorImage{
info: cursorInfo{
Width: uint16(e.Width-1) + 1,
Height: uint16(e.Height-1) + 1,
Planes: planes,
BitCount: bitCount,
BytesInRes: e.BytesInRes,
},
hotSpot: HotSpot{
X: e.XHotSpot,
Y: e.YHotSpot,
},
image: img,
})
}
return cursor, nil
}
// SaveCUR saves a cursor as a CUR file.
func (cursor *Cursor) SaveCUR(ico io.Writer) error {
err := binary.Write(ico, binary.LittleEndian, &cursorDirHeader{
Type: 2,
Count: uint16(len(cursor.images)),
})
if err != nil {
return err
}
var (
pos = sizeOfCursorDirHeader
offset = sizeOfCursorDirHeader + len(cursor.images)*sizeOfCursorFileDirEntry
)
cursor.order()
for i := range cursor.images {
err = binary.Write(ico, binary.LittleEndian, &cursorFileDirEntry{
Width: uint8(cursor.images[i].info.Width),
Height: uint8(cursor.images[i].info.Height),
XHotSpot: cursor.images[i].hotSpot.X,
YHotSpot: cursor.images[i].hotSpot.Y,
BytesInRes: uint32(len(cursor.images[i].image)),
ImageOffset: uint32(offset),
})
if err != nil {
return err
}
offset += len(cursor.images[i].image)
pos += sizeOfCursorFileDirEntry
}
for i := range cursor.images {
_, err = ico.Write(cursor.images[i].image)
if err != nil {
return err
}
}
return nil
}
// SetCursor adds the cursor to the resource set.
func (rs *ResourceSet) SetCursor(resID Identifier, cursor *Cursor) error {
return rs.SetCursorTranslation(resID, LCIDNeutral, cursor)
}
// SetCursorTranslation adds the cursor to a specific language in the resource set.
func (rs *ResourceSet) SetCursorTranslation(resID Identifier, langID uint16, cursor *Cursor) error {
b := &bytes.Buffer{}
binary.Write(b, binary.LittleEndian, cursorDirHeader{
Type: 2,
Count: uint16(len(cursor.images)),
})
cursor.order()
for _, img := range cursor.images {
id := rs.lastCursorID + 1
binary.Write(b, binary.LittleEndian, cursorResDirEntry{
cursorInfo: img.info,
Id: id,
})
if err := rs.Set(RT_CURSOR, ID(id), LCIDNeutral, img.resData()); err != nil {
return err
}
}
return rs.Set(RT_GROUP_CURSOR, resID, langID, b.Bytes())
}
// GetCursor extracts a cursor from a resource set.
func (rs *ResourceSet) GetCursor(resID Identifier) (*Cursor, error) {
return rs.GetCursorTranslation(resID, rs.firstLang(RT_GROUP_CURSOR, resID))
}
// GetCursorTranslation extracts a cursor from a specific language of the resource set.
func (rs *ResourceSet) GetCursorTranslation(resID Identifier, langID uint16) (*Cursor, error) {
data := rs.Get(RT_GROUP_CURSOR, resID, langID)
if data == nil {
return nil, errors.New(errGroupNotFound)
}
in := bytes.NewReader(data)
dir := cursorDirHeader{}
err := binaryRead(in, &dir)
if err != nil || dir.Type != 2 || dir.Reserved != 0 {
return nil, errors.New(errInvalidGroup)
}
g := &Cursor{}
for i := 0; i < int(dir.Count); i++ {
entry := cursorResDirEntry{}
err := binaryRead(in, &entry)
if err != nil {
return nil, errors.New(errInvalidGroup)
}
img := rs.Get(RT_CURSOR, ID(entry.Id), rs.firstLang(RT_CURSOR, ID(entry.Id)))
if img == nil {
return nil, errors.New(errCursorMissing)
}
g.images = append(g.images, cursorImage{
info: entry.cursorInfo,
image: img[4:],
hotSpot: HotSpot{
X: uint16(img[1])<<8 | uint16(img[0]),
Y: uint16(img[3])<<8 | uint16(img[2]),
},
})
}
return g, nil
}
type cursorDirHeader struct {
Reserved uint16
Type uint16
Count uint16
}
const sizeOfCursorDirHeader = 6
type cursorFileDirEntry struct {
Width uint8
Height uint8
Reserved uint16
XHotSpot uint16
YHotSpot uint16
BytesInRes uint32
ImageOffset uint32
}
const sizeOfCursorFileDirEntry = 16
type cursorResDirEntry struct {
cursorInfo
Id uint16
}
type cursorInfo struct {
Width uint16
Height uint16
Planes uint16
BitCount uint16
BytesInRes uint32
}
type cursorImage struct {
info cursorInfo
hotSpot HotSpot
image []byte
}
func (ci *cursorImage) resData() []byte {
// As a resource, image data includes the hot spot
buf := bytes.NewBuffer(make([]byte, 0, len(ci.image)+4))
binary.Write(buf, binary.LittleEndian, ci.hotSpot)
buf.Write(ci.image)
return buf.Bytes()
}
// This makes a testing error reporting possible
var bmpEncode = bmp.Encode
func (cursor *Cursor) addImage(img image.Image, hotSpot HotSpot) error {
bounds := img.Bounds()
if bounds.Empty() {
return errors.New(errInvalidImageDimensions)
}
if bounds.Size().X > 256 || bounds.Size().Y > 256 {
return errors.New(errImageTooBig)
}
// PNG seems to be supported, and simpler. (no need to double the height or to skip a part of the header)
// But I've never seen PNG cursors and I would not take the risk.
// PNG icons, on the other hand, are very common.
buf := &bytes.Buffer{}
curImg := imageInSquareNRGBA(img, false)
if err := bmpEncode(buf, curImg); err != nil {
return err
}
// 14 is the size of a BMPFILEHEADER, which we want to skip.
// A BMP file is a BMPFILEHEADER followed by a DIB.
dib := buf.Bytes()[14:]
width, height := curImg.Bounds().Size().X, curImg.Bounds().Size().Y
// Height must be doubled in the DIB header, as if there was an AND mask for transparency.
// In a 32 bits DIB, the mask can be the alpha channel, therefore there is no AND mask.
dib[8] = byte(height << 1)
dib[9] = byte(height >> 7)
cursor.images = append(cursor.images, cursorImage{
info: cursorInfo{
Width: uint16(width),
Height: uint16(height),
Planes: 1,
BitCount: 32,
BytesInRes: uint32(len(dib) + 4), // +4 for the hot spot
},
hotSpot: hotSpot,
image: dib,
})
return nil
}
func (cursor *Cursor) order() {
sort.SliceStable(cursor.images, func(i, j int) bool {
img1, img2 := &cursor.images[i].info, &cursor.images[j].info
return img1.BitCount > img2.BitCount || img1.BitCount == img2.BitCount && img1.Width > img2.Width
})
}
func readDIBBitCount(data []byte) (uint16, uint16, error) {
// Icons and cursor may contain PNG instead of DIB
_, s, _ := image.DecodeConfig(bytes.NewReader(data))
if s == "png" {
return 1, 32, nil
}
hdrSize := uint32(data[3])<<24 | uint32(data[2])<<16 | uint32(data[1])<<8 | uint32(data[0])
if hdrSize != 40 && hdrSize != 108 && hdrSize != 124 {
return 0, 0, errors.New(errUnknownImageFormat)
}
var (
planes = uint16(data[13])<<8 | uint16(data[12])
bitCount = uint16(data[15])<<8 | uint16(data[14])
)
return planes, bitCount, nil
}
package winres
import (
"bytes"
"errors"
"image"
"io"
"os"
"path/filepath"
"reflect"
"testing"
)
func TestLoadCUR(t *testing.T) {
f, err := os.Open(filepath.Join(testDataDir, "cursor.cur"))
if err != nil {
t.Fatal("missing test data")
}
defer f.Close()
cursor, err := LoadCUR(f)
if err != nil {
t.Fatal(err)
}
cursor.order()
expected := []cursorImage{
{
info: cursorInfo{
Width: 32,
Height: 32,
Planes: 1,
BitCount: 32,
BytesInRes: 0x10A8,
},
hotSpot: HotSpot{14, 8},
},
{
info: cursorInfo{
Width: 256,
Height: 256,
Planes: 1,
BitCount: 24,
BytesInRes: 0x32028,
},
hotSpot: HotSpot{112, 64},
},
{
info: cursorInfo{
Width: 32,
Height: 32,
Planes: 1,
BitCount: 8,
BytesInRes: 0x710,
},
hotSpot: HotSpot{14, 8},
},
{
info: cursorInfo{
Width: 32,
Height: 32,
Planes: 1,
BitCount: 4,
BytesInRes: 0x2E8,
},
hotSpot: HotSpot{21, 10},
},
{
info: cursorInfo{
Width: 32,
Height: 32,
Planes: 1,
BitCount: 1,
BytesInRes: 0x130,
},
hotSpot: HotSpot{21, 10},
},
}
for i := range expected {
if !reflect.DeepEqual(cursor.images[i].info, expected[i].info) {
t.Errorf("%s - image %d: expected %v got %v", t.Name(), i, expected[i].info, cursor.images[i].info)
}
if cursor.images[i].hotSpot != expected[i].hotSpot {
t.Errorf("%s - hotspot %d: expected %v got %v", t.Name(), i, expected[i].hotSpot, cursor.images[i].hotSpot)
}
}
}
func TestLoadCUR_ErrEOF1(t *testing.T) {
cursor, err := LoadCUR(bytes.NewReader([]byte{0, 0, 2, 0, 1}))
if err != io.ErrUnexpectedEOF || cursor != nil {
t.Fail()
}
}
func TestLoadCUR_ErrEOF2(t *testing.T) {
cursor, err := LoadCUR(bytes.NewReader([]byte{
0, 0, 2, 0, 0xFF, 0xFF,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}))
if err != io.ErrUnexpectedEOF || cursor != nil {
t.Fail()
}
}
func TestLoadCUR_ErrImageOffset(t *testing.T) {
temp, err := os.ReadFile(filepath.Join(testDataDir, "cursor.cur"))
if err != nil {
t.Fatal(err)
}
temp[21] = 0x04
cursor, err := LoadCUR(bytes.NewReader(temp))
if err != io.ErrUnexpectedEOF || cursor != nil {
t.Fail()
}
}
func TestLoadCUR_ErrNotCUR(t *testing.T) {
temp, err := os.ReadFile(filepath.Join(testDataDir, "cursor.cur"))
if err != nil {
t.Fatal(err)
}
temp[0] = 1
cursor, err := LoadCUR(bytes.NewReader(temp))
if err == nil || cursor != nil || err.Error() != errNotCUR {
t.Fail()
}
temp[0] = 0
temp[2] = 1
cursor, err = LoadCUR(bytes.NewReader(temp))
if err == nil || cursor != nil || err.Error() != errNotCUR {
t.Fail()
}
}
func TestLoadCUR_ErrNotDIB(t *testing.T) {
temp, err := os.ReadFile(filepath.Join(testDataDir, "cursor.cur"))
if err != nil {
t.Fatal(err)
}
temp[0x56] = 41
cursor, err := LoadCUR(bytes.NewReader(temp))
if err == nil || cursor != nil || err.Error() != errUnknownImageFormat {
t.Fail()
}
}
func TestLoadCUR_ErrSeek(t *testing.T) {
data, err := os.ReadFile(filepath.Join(testDataDir, "cursor.cur"))
if err != nil {
t.Fatal("missing test data")
}
r := &badSeeker{br: bytes.NewReader(data)}
cursor, err := LoadCUR(r)
if !isExpectedSeekErr(err) || cursor != nil {
t.Fatal("expected seek error, got", err)
}
}
func TestLoadCUR_ImageLengthLimit(t *testing.T) {
_, err := LoadCUR(bytes.NewReader([]byte{
0, 0, 2, 0, 1, 0, 32, 32, 0, 0, 0, 0, 0, 0,
0x00, 0x00, 0xA0, 0x00, // image data length = 0xA00000
0, 0, 0, 22,
}))
if err == nil || err.Error() == errImageLengthTooBig {
t.Fail()
}
_, err = LoadCUR(bytes.NewReader([]byte{
0, 0, 2, 0, 1, 0, 32, 32, 0, 0, 0, 0, 0, 0,
0x01, 0x00, 0xA0, 0x00, // image data length = 0xA00001
0, 0, 0, 22,
}))
if err == nil || err.Error() != errImageLengthTooBig {
t.Fail()
}
}
func TestLoadCUR_PNG(t *testing.T) {
data, err := os.ReadFile(filepath.Join(testDataDir, "png.cur"))
if err != nil {
t.Fatal(err)
}
cursor, err := LoadCUR(bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
if cursor.images[0].info.BitCount != 32 {
t.Fail()
}
}
func TestCursor_SaveCUR(t *testing.T) {
data, err := os.ReadFile(filepath.Join(testDataDir, "cursor.cur"))
if err != nil {
t.Fatal(err)
}
cursor, err := LoadCUR(bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
buf := &bytes.Buffer{}
err = cursor.SaveCUR(buf)
if err != nil {
t.Fatal(err)
}
checkBinary(t, buf.Bytes())
}
func TestCursor_SaveCUR_ErrWrite(t *testing.T) {
data, err := os.ReadFile(filepath.Join(testDataDir, "cursor.cur"))
if err != nil {
t.Fatal(err)
}
cursor, err := LoadCUR(bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
w := newBadWriter(5)
err = cursor.SaveCUR(w)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
w = newBadWriter(20)
err = cursor.SaveCUR(w)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
w = newBadWriter(9190)
err = cursor.SaveCUR(w)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
}
func TestNewCursorFromImages(t *testing.T) {
cursor, err := NewCursorFromImages([]CursorImage{
{
Image: loadImage(t, "cur-16x8.png"),
HotSpot: HotSpot{1, 2},
},
{
Image: shiftImage(loadImage(t, "cur-16x32.png"), -10, -100),
HotSpot: HotSpot{3, 4},
},
{
Image: shiftImage(loadImage(t, "cur-32x64.png"), 2, -1),
HotSpot: HotSpot{5, 6},
},
{
Image: loadImage(t, "cur-64x128.png"),
HotSpot: HotSpot{7, 8},
},
})
if err != nil {
t.Fatal(err)
}
checkBinary(t, curToBinary(cursor))
}
func TestNewCursorFromImages_ErrDimensions(t *testing.T) {
_, err := NewCursorFromImages([]CursorImage{
{
Image: image.NewNRGBA(image.Rectangle{}),
HotSpot: HotSpot{0, 0},
},
})
if err == nil || err.Error() != errInvalidImageDimensions {
t.Fail()
}
}
func TestNewCursorFromImages_ErrEncode(t *testing.T) {
enc := bmpEncode
defer func() { bmpEncode = enc }()
bmpEncode = func(w io.Writer, m image.Image) error {
return errors.New("oops")
}
_, err := NewCursorFromImages([]CursorImage{
{
Image: loadImage(t, "cur-32x64.png"),
HotSpot: HotSpot{5, 6},
},
{
Image: loadImage(t, "cur-64x128.png"),
HotSpot: HotSpot{7, 8},
},
})
if err == nil || err.Error() != "oops" {
t.Fail()
}
}
func TestNewCursorFromImages_ErrTooBig(t *testing.T) {
_, err := NewCursorFromImages([]CursorImage{
{
Image: image.NewNRGBA(image.Rectangle{
Max: image.Point{
X: 257,
Y: 256,
},
}),
HotSpot: HotSpot{1, 2},
},
})
if err == nil || err.Error() != errImageTooBig {
t.Fail()
}
_, err = NewCursorFromImages([]CursorImage{
{
Image: image.NewNRGBA(image.Rectangle{
Max: image.Point{
X: 256,
Y: 257,
},
}),
HotSpot: HotSpot{1, 2},
},
})
if err == nil || err.Error() != errImageTooBig {
t.Fail()
}
_, err = NewCursorFromImages([]CursorImage{
{
Image: image.NewNRGBA(image.Rectangle{
Max: image.Point{
X: 256,
Y: 256,
},
}),
HotSpot: HotSpot{1, 2},
},
})
if err != nil {
t.Fail()
}
}
func TestResourceSet_SetCursor(t *testing.T) {
rs := ResourceSet{}
id := Name("CURSOR")
cursor := loadCURFile(t, "cursor.cur")
err := rs.SetCursor(id, cursor)
if err != nil {
t.Fatal(err)
}
if int(rs.lastCursorID) != len(cursor.images) {
t.Fail()
}
checkCursorResource(t, &rs, id, 0, cursor)
}
func TestResourceSet_SetCursor_IDOverflow(t *testing.T) {
rs := ResourceSet{}
rs.lastCursorID = 0xFFFD
cursor := loadCURFile(t, "cursor.cur")
err := rs.SetCursor(Name("CURSOR"), cursor)
if err == nil || err.Error() != errZeroID {
t.Fail()
}
}
func TestResourceSet_SetCursorTranslation(t *testing.T) {
rs := ResourceSet{}
cursor1 := loadCURFile(t, "en.cur")
cursor2 := loadCURFile(t, "fr.cur")
cursor3, err := NewCursorFromImages([]CursorImage{
{
Image: loadImage(t, "cur-16x8.png"),
HotSpot: HotSpot{1, 2},
},
})
if err != nil {
t.Fatal(err)
}
err = rs.SetCursorTranslation(Name("LOCALCUR"), 0x409, cursor1)
if err != nil {
t.Fatal(err)
}
err = rs.SetCursorTranslation(Name("ANOTHERCUR"), 0x409, cursor3)
if err != nil {
t.Fatal(err)
}
err = rs.SetCursorTranslation(Name("LOCALCUR"), 0x40C, cursor2)
if err != nil {
t.Fatal(err)
}
checkCursorResource(t, &rs, Name("LOCALCUR"), 0x409, cursor1)
checkCursorResource(t, &rs, Name("LOCALCUR"), 0x40C, cursor2)
checkCursorResource(t, &rs, Name("ANOTHERCUR"), 0x409, cursor3)
}
func TestResourceSet_GetCursorTranslation_Err(t *testing.T) {
rs := ResourceSet{}
bin0 := []byte{0, 0, 2, 0, 1}
bin1 := []byte{0, 0, 1, 0, 1, 0, 32, 0, 32, 0, 1, 0, 32, 0, 42, 0, 0, 0, 1, 0}
bin2 := []byte{0, 0, 2, 0, 1, 0, 32, 0, 32, 0, 1, 0, 32, 0, 42, 0, 0, 0, 1}
bin3 := []byte{0, 0, 2, 0, 1, 0, 32, 0, 32, 0, 1, 0, 32, 0, 42, 0, 0, 0, 1, 0}
err := rs.Set(RT_GROUP_CURSOR, Name("LOCALCUR"), 0, bin0)
if err != nil {
t.Fatal(err)
}
err = rs.Set(RT_GROUP_CURSOR, Name("LOCALCUR"), 0x401, bin1)
if err != nil {
t.Fatal(err)
}
err = rs.Set(RT_GROUP_CURSOR, Name("LOCALCUR"), 0x402, bin2)
if err != nil {
t.Fatal(err)
}
err = rs.Set(RT_GROUP_CURSOR, Name("LOCALCUR"), 0x403, bin3)
if err != nil {
t.Fatal(err)
}
var cursor *Cursor
cursor, err = rs.GetCursor(ID(0))
if err == nil || cursor != nil || err.Error() != errGroupNotFound {
t.Fail()
}
cursor, err = rs.GetCursorTranslation(Name("LOCALCU"), 0x401)
if err == nil || cursor != nil || err.Error() != errGroupNotFound {
t.Fail()
}
cursor, err = rs.GetCursorTranslation(Name("LOCALCUR"), 0x409)
if err == nil || cursor != nil || err.Error() != errGroupNotFound {
t.Fail()
}
cursor, err = rs.GetCursorTranslation(Name("LOCALCUR"), 0)
if err == nil || cursor != nil || err.Error() != errInvalidGroup {
t.Fail()
}
cursor, err = rs.GetCursorTranslation(Name("LOCALCUR"), 0x401)
if err == nil || cursor != nil || err.Error() != errInvalidGroup {
t.Fail()
}
cursor, err = rs.GetCursorTranslation(Name("LOCALCUR"), 0x402)
if err == nil || cursor != nil || err.Error() != errInvalidGroup {
t.Fail()
}
cursor, err = rs.GetCursorTranslation(Name("LOCALCUR"), 0x403)
if err == nil || cursor != nil || err.Error() != errCursorMissing {
t.Fail()
}
}
func checkCursorResource(t *testing.T, rs *ResourceSet, ident Identifier, langID uint16, source *Cursor) {
cursor, err := rs.GetCursorTranslation(ident, langID)
if err != nil {
t.Fatal(err)
}
buf1, buf2 := &bytes.Buffer{}, &bytes.Buffer{}
err = source.SaveCUR(buf1)
if err != nil {
t.Fatal(err)
}
err = cursor.SaveCUR(buf2)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf1.Bytes(), buf2.Bytes()) {
t.Fail()
}
}
func curToBinary(cursor *Cursor) []byte {
buf := &bytes.Buffer{}
cursor.SaveCUR(buf)
return buf.Bytes()
}
package winres
import "errors"
const (
errZeroID = "ordinal identifier must not be zero"
errEmptyName = "string identifier must not be empty"
errNameContainsNUL = "string identifier must not contain NUL char"
errUnknownArch = "unknown architecture"
errNotICO = "not a valid ICO file"
errImageLengthTooBig = "image size found in ICONDIRENTRY is too big (above 10 MB)"
errTooManyIconSizes = "too many sizes"
errGroupNotFound = "group does not exist"
errInvalidGroup = "invalid group"
errIconMissing = "icon missing from group"
errCursorMissing = "cursor missing from group"
errInvalidImageDimensions = "invalid image dimensions"
errImageTooBig = "image size too big, must fit in 256x256"
errNotCUR = "not a valid CUR file"
errUnknownImageFormat = "unknown image format"
errInvalidResDir = "invalid resource directory"
errDataEntryOutOfBounds = "data entry out of bounds"
errNotPEImage = "not a valid PE image"
errSignedPE = "cannot modify a signed PE image"
errUnknownPE = "unknown PE format"
errNoRSRC = "image doesn't have a resource directory" // This is when the data directory entry is zero
errRSRCNotFound = "resource section not found" // This is when the data directory entry is not zero
errSectionTooFar = "invalid section header points too far"
errNoRoomForRSRC = "not enough room to add .rsrc section header"
errRSRCTwice = "found resource section twice"
errRelocTwice = "found reloc section twice"
errInvalidVersion = "invalid version number"
errUnknownSupportedOS = "unknown minimum-os value"
errUnknownDPIAwareness = "unknown dpi-awareness value"
errUnknownExecLevel = "unknown execution-level value"
)
// ErrNoResources is the error returned by LoadFromEXE when it didn't find a .rsrc section.
var ErrNoResources = errors.New(errNoRSRC)
// ErrSignedPE is the error returned by WriteToEXE when it refused to touch signed code. (Authenticode)
var ErrSignedPE = errors.New(errSignedPE)
package winres
import (
"bytes"
"debug/pe"
"encoding/binary"
"errors"
"io"
"os"
)
type authenticodeHandling int
const (
// ErrorIfSigned means winres won't patch a signed executable.
ErrorIfSigned authenticodeHandling = 0
// RemoveSignature means winres will patch a signed executable,
// and remove the signature.
RemoveSignature authenticodeHandling = 1
// IgnoreSignature means winres will patch a signed executable,
// and leave the now invalid signature as is.
IgnoreSignature authenticodeHandling = 2
)
type exeOptions struct {
forceCheckSum bool
authenticodeHandling authenticodeHandling
}
type exeOption func(opt *exeOptions)
// ForceCheckSum forces updating the PE checksum, even when the original file didn't have one
func ForceCheckSum() exeOption {
return func(opt *exeOptions) {
opt.forceCheckSum = true
}
}
// WithAuthenticode allows patching signed executables, either by removing the signature or by ignoring it (and making it wrong)
func WithAuthenticode(handling authenticodeHandling) exeOption {
return func(opt *exeOptions) {
opt.authenticodeHandling = handling
}
}
type peHeaders struct {
file pe.FileHeader
opt peOptionalHeader
dirs []pe.DataDirectory
sections []pe.SectionHeader32
stubLength int64
length int64
hasChecksum bool
}
const sizeOfSectionHeader = 40
type peOptionalHeader interface {
getSizeOfInitializedData() uint32
getSectionAlignment() uint32
getFileAlignment() uint32
getNumberOfRvaAndSizes() uint32
getCheckSum() uint32
setSizeOfInitializedData(uint32)
setSizeOfImage(uint32)
setCheckSum(uint32)
}
type peOptionalHeader32 struct {
Magic uint16
MajorLinkerVersion uint8
MinorLinkerVersion uint8
SizeOfCode uint32
SizeOfInitializedData uint32
SizeOfUninitializedData uint32
AddressOfEntryPoint uint32
BaseOfCode uint32
BaseOfData uint32
ImageBase uint32
SectionAlignment uint32
FileAlignment uint32
MajorOperatingSystemVersion uint16
MinorOperatingSystemVersion uint16
MajorImageVersion uint16
MinorImageVersion uint16
MajorSubsystemVersion uint16
MinorSubsystemVersion uint16
Win32VersionValue uint32
SizeOfImage uint32
SizeOfHeaders uint32
CheckSum uint32
Subsystem uint16
DllCharacteristics uint16
SizeOfStackReserve uint32
SizeOfStackCommit uint32
SizeOfHeapReserve uint32
SizeOfHeapCommit uint32
LoaderFlags uint32
NumberOfRvaAndSizes uint32
}
func (h *peOptionalHeader32) getSizeOfInitializedData() uint32 {
return h.SizeOfInitializedData
}
func (h *peOptionalHeader32) getSectionAlignment() uint32 {
return h.SectionAlignment
}
func (h *peOptionalHeader32) getFileAlignment() uint32 {
return h.FileAlignment
}
func (h *peOptionalHeader32) getNumberOfRvaAndSizes() uint32 {
return h.NumberOfRvaAndSizes
}
func (h *peOptionalHeader32) getCheckSum() uint32 {
return h.CheckSum
}
func (h *peOptionalHeader32) setSizeOfInitializedData(s uint32) {
h.SizeOfInitializedData = s
}
func (h *peOptionalHeader32) setSizeOfImage(s uint32) {
h.SizeOfImage = s
}
func (h *peOptionalHeader32) setCheckSum(c uint32) {
h.CheckSum = c
}
type peOptionalHeader64 struct {
Magic uint16
MajorLinkerVersion uint8
MinorLinkerVersion uint8
SizeOfCode uint32
SizeOfInitializedData uint32
SizeOfUninitializedData uint32
AddressOfEntryPoint uint32
BaseOfCode uint32
ImageBase uint64
SectionAlignment uint32
FileAlignment uint32
MajorOperatingSystemVersion uint16
MinorOperatingSystemVersion uint16
MajorImageVersion uint16
MinorImageVersion uint16
MajorSubsystemVersion uint16
MinorSubsystemVersion uint16
Win32VersionValue uint32
SizeOfImage uint32
SizeOfHeaders uint32
CheckSum uint32
Subsystem uint16
DllCharacteristics uint16
SizeOfStackReserve uint64
SizeOfStackCommit uint64
SizeOfHeapReserve uint64
SizeOfHeapCommit uint64
LoaderFlags uint32
NumberOfRvaAndSizes uint32
}
func (h *peOptionalHeader64) getSizeOfInitializedData() uint32 {
return h.SizeOfInitializedData
}
func (h *peOptionalHeader64) getSectionAlignment() uint32 {
return h.SectionAlignment
}
func (h *peOptionalHeader64) getFileAlignment() uint32 {
return h.FileAlignment
}
func (h *peOptionalHeader64) getNumberOfRvaAndSizes() uint32 {
return h.NumberOfRvaAndSizes
}
func (h *peOptionalHeader64) getCheckSum() uint32 {
return h.CheckSum
}
func (h *peOptionalHeader64) setSizeOfInitializedData(s uint32) {
h.SizeOfInitializedData = s
}
func (h *peOptionalHeader64) setSizeOfImage(s uint32) {
h.SizeOfImage = s
}
func (h *peOptionalHeader64) setCheckSum(c uint32) {
h.CheckSum = c
}
func extractRSRCSection(r io.ReadSeeker) ([]byte, uint32, error) {
r.Seek(0, io.SeekStart)
fileSize := getSeekerSize(r)
h, err := readPEHeaders(r)
if err != nil {
return nil, 0, err
}
if h.dirs[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress == 0 && h.dirs[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].Size == 0 {
return nil, 0, ErrNoResources
}
var sec *pe.SectionHeader32
for i := range h.sections {
if h.sections[i].VirtualAddress == h.dirs[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress {
sec = &h.sections[i]
break
}
}
if sec == nil {
return nil, 0, errors.New(errRSRCNotFound)
}
if int64(sec.PointerToRawData)+int64(sec.SizeOfRawData) > fileSize {
return nil, 0, errors.New(errSectionTooFar)
}
data := make([]byte, sec.SizeOfRawData)
r.Seek(int64(sec.PointerToRawData), io.SeekStart)
err = readFull(r, data)
if err != nil {
return nil, 0, err
}
return data, sec.VirtualAddress, nil
}
type peWriter struct {
h *peHeaders
rsrcData []byte
rsrcHdr *pe.SectionHeader32
relocHdr *pe.SectionHeader32
src struct {
r io.ReadSeeker
fileSize int64
sigSize int64 // size of a code signature we'd want to skip (only if it is at the end of the file)
dataOffset uint32
dataEnd uint32
virtEnd uint32
rsrcEnd int64
}
}
func replaceRSRCSection(dst io.Writer, src io.ReadSeeker, rsrcData []byte, reloc []int, options exeOptions) error {
src.Seek(0, io.SeekStart)
pew, err := preparePEWriter(src, rsrcData, options.authenticodeHandling)
if err != nil {
return err
}
pew.applyReloc(reloc)
if options.forceCheckSum || pew.h.hasChecksum {
c := peCheckSum{}
pew.writeEXE(&c)
pew.h.opt.setCheckSum(c.Sum())
}
return pew.writeEXE(dst)
}
func preparePEWriter(src io.ReadSeeker, rsrcData []byte, sigHandling authenticodeHandling) (*peWriter, error) {
var (
pew peWriter
err error
)
pew.src.r = src
pew.rsrcData = rsrcData
pew.src.fileSize = getSeekerSize(src)
pew.h, err = readPEHeaders(src)
if err != nil {
return nil, err
}
if len(pew.h.dirs) > pe.IMAGE_DIRECTORY_ENTRY_SECURITY && pew.h.dirs[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress > 0 {
switch sigHandling {
case RemoveSignature:
entry := pew.h.dirs[pe.IMAGE_DIRECTORY_ENTRY_SECURITY]
pew.h.dirs[pe.IMAGE_DIRECTORY_ENTRY_SECURITY] = pe.DataDirectory{}
// The certificate entry actually contains a raw data offset, not a virtual address.
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#the-attribute-certificate-table-image-only
if int64(entry.VirtualAddress)+int64(entry.Size) == pew.src.fileSize {
pew.src.sigSize = pew.src.fileSize - int64(entry.VirtualAddress)
}
case IgnoreSignature:
default:
return nil, ErrSignedPE
}
}
err = pew.fillSectionsInfo()
if err != nil {
return nil, err
}
if pew.src.fileSize < int64(pew.src.dataEnd) {
return nil, io.ErrUnexpectedEOF
}
if pew.rsrcHdr == nil && (int64(pew.src.dataOffset) < pew.h.length+sizeOfSectionHeader || pew.h.file.NumberOfSections == 0xFFFF) {
return nil, errors.New(errNoRoomForRSRC)
}
if pew.requiresNewSection() {
// Play it safe, abandon the existing .rsrc section and create a new one.
pew.rsrcHdr.Name = [8]byte{'o', 'l', 'd', '.', 'r', 's', 'r', 'c'}
pew.rsrcHdr = nil
}
pew.updateHeaders()
return &pew, nil
}
func (pew *peWriter) fillSectionsInfo() error {
pew.src.dataOffset = 0xFFFFFFFF
pew.src.rsrcEnd = pew.src.fileSize
for i := range pew.h.sections {
if pew.h.sections[i].VirtualAddress == pew.h.dirs[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress {
if pew.rsrcHdr != nil {
return errors.New(errRSRCTwice)
}
pew.rsrcHdr = &pew.h.sections[i]
pew.src.rsrcEnd = int64(pew.roundRaw(pew.rsrcHdr.PointerToRawData + pew.rsrcHdr.SizeOfRawData))
}
if pew.h.sections[i].VirtualAddress == pew.h.dirs[pe.IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress {
if pew.relocHdr != nil {
return errors.New(errRelocTwice)
}
pew.relocHdr = &pew.h.sections[i]
}
}
for i := range pew.h.sections {
if pew.h.sections[i].PointerToRawData < pew.src.dataOffset {
pew.src.dataOffset = pew.h.sections[i].PointerToRawData
}
if pew.h.sections[i].PointerToRawData+pew.h.sections[i].SizeOfRawData > pew.src.dataEnd {
pew.src.dataEnd = pew.h.sections[i].PointerToRawData + pew.h.sections[i].SizeOfRawData
}
if pew.h.sections[i].VirtualAddress+pew.h.sections[i].VirtualSize > pew.src.virtEnd {
pew.src.virtEnd = pew.h.sections[i].VirtualAddress + pew.h.sections[i].VirtualSize
}
}
pew.src.virtEnd = pew.roundVirt(pew.src.virtEnd)
return nil
}
func (pew *peWriter) requiresNewSection() bool {
if pew.rsrcHdr == nil {
return false
}
endOfRSRC := pew.roundVirt(pew.rsrcHdr.VirtualAddress + pew.rsrcHdr.VirtualSize)
if endOfRSRC >= pew.src.virtEnd {
return false
}
if pew.relocHdr.VirtualAddress == endOfRSRC &&
pew.roundVirt(pew.relocHdr.VirtualAddress+pew.relocHdr.VirtualSize) >= pew.src.virtEnd {
return false
}
// From here, we should not shift data after the existing .rsrc section
if pew.rsrcHdr.SizeOfRawData >= uint32(len(pew.rsrcData)) {
// The .rsrc section won't grow, so we only have to ensure it won't shrink too much either
buf := make([]byte, pew.rsrcHdr.SizeOfRawData)
copy(buf, pew.rsrcData)
pew.rsrcData = buf
return false
}
return true
}
func (pew *peWriter) updateHeaders() {
var (
rsrcLen = uint32(len(pew.rsrcData))
lastSection *pe.SectionHeader32
oldSize uint32
virtDelta uint32
)
if pew.rsrcHdr == nil {
// Add .rsrc section
pew.h.sections = append(pew.h.sections, pe.SectionHeader32{
Name: [8]uint8{'.', 'r', 's', 'r', 'c'},
VirtualSize: rsrcLen,
VirtualAddress: pew.roundVirt(pew.src.virtEnd),
SizeOfRawData: pew.roundRaw(uint32(len(pew.rsrcData))),
PointerToRawData: pew.roundRaw(pew.src.dataEnd),
Characteristics: _IMAGE_SCN_MEM_READ | _IMAGE_SCN_CNT_INITIALIZED_DATA,
})
pew.rsrcHdr = &pew.h.sections[len(pew.h.sections)-1]
pew.h.file.NumberOfSections++
pew.h.length += sizeOfSectionHeader
lastSection = pew.rsrcHdr
pew.h.opt.setSizeOfInitializedData(pew.h.opt.getSizeOfInitializedData() + rsrcLen)
} else {
oldSize = pew.rsrcHdr.SizeOfRawData
virtDelta = pew.roundVirt(rsrcLen) - pew.roundVirt(pew.rsrcHdr.VirtualSize)
rawDelta := pew.roundRaw(rsrcLen) - pew.roundRaw(pew.rsrcHdr.SizeOfRawData)
pew.rsrcHdr.VirtualSize = rsrcLen
pew.rsrcHdr.SizeOfRawData = pew.roundRaw(rsrcLen)
lastSection = pew.rsrcHdr
for i := range pew.h.sections {
if pew.h.sections[i].VirtualAddress > pew.rsrcHdr.VirtualAddress {
pew.h.sections[i].VirtualAddress += virtDelta
if pew.h.sections[i].VirtualAddress > lastSection.VirtualAddress {
lastSection = &pew.h.sections[i]
}
}
if pew.h.sections[i].PointerToRawData > pew.rsrcHdr.PointerToRawData {
pew.h.sections[i].PointerToRawData += rawDelta
}
}
}
if pew.h.dirs[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress >= pew.src.dataEnd {
pew.h.dirs[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress += lastSection.PointerToRawData + lastSection.SizeOfRawData - pew.src.dataEnd
}
pew.h.dirs[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress = pew.rsrcHdr.VirtualAddress
pew.h.dirs[pe.IMAGE_DIRECTORY_ENTRY_RESOURCE].Size = rsrcLen
for i := range pew.h.dirs {
if i != pe.IMAGE_DIRECTORY_ENTRY_SECURITY && pew.h.dirs[i].VirtualAddress > pew.rsrcHdr.VirtualAddress {
pew.h.dirs[i].VirtualAddress += virtDelta
}
}
pew.h.opt.setCheckSum(0)
pew.h.opt.setSizeOfImage(lastSection.VirtualAddress + pew.roundVirt(lastSection.VirtualSize))
pew.h.opt.setSizeOfInitializedData(pew.h.opt.getSizeOfInitializedData() - oldSize + pew.rsrcHdr.SizeOfRawData)
}
func (pew *peWriter) roundRaw(p uint32) uint32 {
a := pew.h.opt.getFileAlignment()
x := p + a - 1
return x - x%a
}
func (pew *peWriter) roundVirt(p uint32) uint32 {
a := pew.h.opt.getSectionAlignment()
x := p + a - 1
return x - x%a
}
func (pew *peWriter) applyReloc(reloc []int) {
for _, o := range reloc {
addr := uint32(pew.rsrcData[o+3])<<24 |
uint32(pew.rsrcData[o+2])<<16 |
uint32(pew.rsrcData[o+1])<<8 |
uint32(pew.rsrcData[o])
addr += pew.rsrcHdr.VirtualAddress
pew.rsrcData[o+3] = uint8(addr >> 24)
pew.rsrcData[o+2] = uint8(addr >> 16)
pew.rsrcData[o+1] = uint8(addr >> 8)
pew.rsrcData[o] = uint8(addr)
}
}
func (pew *peWriter) writeEXE(w io.Writer) error {
var err error
_, err = pew.src.r.Seek(0, io.SeekStart)
if err != nil {
return err
}
// MS-DOS Stub + PE signature
_, err = io.CopyN(w, pew.src.r, pew.h.stubLength+4)
if err != nil {
return err
}
// Headers
err = binary.Write(w, binary.LittleEndian, &pew.h.file)
if err != nil {
return err
}
err = binary.Write(w, binary.LittleEndian, pew.h.opt)
if err != nil {
return err
}
err = binary.Write(w, binary.LittleEndian, pew.h.dirs)
if err != nil {
return err
}
err = binary.Write(w, binary.LittleEndian, pew.h.sections)
if err != nil {
return err
}
err = writeBlank(w, int64(pew.src.dataOffset)-pew.h.length)
if err != nil {
return err
}
_, err = pew.src.r.Seek(int64(pew.src.dataOffset), io.SeekStart)
if err != nil {
return err
}
// Sections before .rsrc
end := int64(pew.src.dataEnd)
if int64(pew.rsrcHdr.PointerToRawData) < end {
end = int64(pew.rsrcHdr.PointerToRawData)
}
_, err = io.CopyN(w, pew.src.r, end-int64(pew.src.dataOffset))
if err != nil {
return err
}
err = writeBlank(w, int64(pew.rsrcHdr.PointerToRawData)-end)
if err != nil {
return err
}
// .rsrc
_, err = w.Write(pew.rsrcData)
if err != nil {
return err
}
err = writeBlank(w, int64(pew.rsrcHdr.SizeOfRawData)-int64(len(pew.rsrcData)))
if err != nil {
return err
}
// Remainder
_, err = pew.src.r.Seek(pew.src.rsrcEnd, io.SeekStart)
if err != nil {
return err
}
_, err = io.CopyN(w, pew.src.r, pew.src.fileSize-pew.src.sigSize-pew.src.rsrcEnd)
if err != nil {
return err
}
return nil
}
func writeBlank(w io.Writer, length int64) error {
if length <= 0 {
return nil
}
if ws, ok := w.(io.WriteSeeker); ok {
ws.Seek(length-1, io.SeekCurrent)
var b [1]byte
_, err := w.Write(b[:])
if err != nil {
return err
}
return nil
}
const bufLen = 0x100
var b [bufLen]byte
for length > 0 {
l := length
if l > bufLen {
l = bufLen
}
_, err := w.Write(b[:l])
if err != nil {
return err
}
length -= l
}
return nil
}
func readPEOffset(r io.Reader) (int64, error) {
stubHead := make([]byte, 0x40)
err := readFull(r, stubHead)
if err != nil {
return 0, err
}
if string(stubHead[:2]) != "MZ" {
return 0, errors.New(errNotPEImage)
}
return int64(stubHead[0x3F])<<24 | int64(stubHead[0x3E])<<16 | int64(stubHead[0x3D])<<8 | int64(stubHead[0x3C]), nil
}
func readPEHeaders(r io.ReadSeeker) (*peHeaders, error) {
var (
h peHeaders
err error
)
h.stubLength, err = readPEOffset(r)
if err != nil {
return nil, err
}
r.Seek(h.stubLength, io.SeekStart)
var sig [4]byte
err = readFull(r, sig[:])
if err != nil {
return nil, err
}
if sig != [4]byte{'P', 'E'} {
return nil, errors.New(errNotPEImage)
}
err = binaryRead(r, &h.file)
if err != nil {
return nil, err
}
optHdr := make([]byte, h.file.SizeOfOptionalHeader)
err = readFull(r, optHdr)
if err != nil {
return nil, err
}
if optHdr[0] != 11 {
return nil, errors.New(errUnknownPE)
}
switch optHdr[1] {
case 1:
h.opt = &peOptionalHeader32{}
case 2:
h.opt = &peOptionalHeader64{}
default:
return nil, errors.New(errUnknownPE)
}
optRead := bytes.NewReader(optHdr)
err = binaryRead(optRead, h.opt)
if err != nil {
return nil, err
}
numDirs := int(h.opt.getNumberOfRvaAndSizes())
if numDirs < 6 {
return nil, errors.New(errUnknownPE)
}
if int(h.file.SizeOfOptionalHeader) != binary.Size(h.opt)+numDirs*8 {
return nil, errors.New(errNotPEImage)
}
h.dirs = make([]pe.DataDirectory, numDirs)
binaryRead(optRead, &h.dirs)
h.sections = make([]pe.SectionHeader32, h.file.NumberOfSections, h.file.NumberOfSections+1)
err = binaryRead(r, &h.sections)
if err != nil {
return nil, err
}
h.hasChecksum = h.opt.getCheckSum() != 0
h.length, err = r.Seek(0, io.SeekCurrent)
if err != nil {
return nil, err
}
return &h, nil
}
func getSeekerSize(r io.ReadSeeker) int64 {
switch r := r.(type) {
case *os.File:
stat, err := r.Stat()
if err == nil {
return stat.Size()
}
case interface{ Size() int64 }:
return r.Size()
}
pos, _ := r.Seek(0, io.SeekCurrent)
size, _ := r.Seek(0, io.SeekEnd)
r.Seek(pos, io.SeekStart)
return size
}
package winres
import (
"bytes"
"errors"
"image"
"image/png"
"io"
"os"
"path/filepath"
"testing"
)
const testDataDir = "testdata"
func checkResourceSet(t *testing.T, rs *ResourceSet, arch Arch) {
buf := &bytes.Buffer{}
if err := rs.WriteObject(buf, arch); err != nil {
t.Fatal(err)
}
checkBinary(t, buf.Bytes())
}
func golden(t *testing.T) string {
return filepath.Join(testDataDir, t.Name()+".golden")
}
func checkBinary(t *testing.T, data []byte) {
refFile := golden(t)
ref, _ := os.ReadFile(refFile)
if !bytes.Equal(ref, data) {
t.Error(t.Name() + " output is different")
bugFile := refFile[:len(refFile)-7] + ".bug"
err := os.WriteFile(bugFile, data, 0666)
if err != nil {
t.Error(err)
return
}
t.Log("dumped output to", bugFile)
}
}
func loadBinary(t *testing.T, filename string) []byte {
data, err := os.ReadFile(filepath.Join(testDataDir, filename))
if err != nil {
t.Fatal(err)
}
return data
}
func loadPNGFileAsIcon(t *testing.T, name string, sizes []int) *Icon {
f, err := os.Open(filepath.Join(testDataDir, name))
if err != nil {
t.Fatal(err)
}
img, err := png.Decode(f)
if err != nil {
t.Fatal(err)
}
icon, err := NewIconFromResizedImage(img, sizes)
if err != nil {
t.Fatal(err)
}
return icon
}
func loadPNGFileAsCursor(t *testing.T, name string, spotX, spotY uint16) *Cursor {
f, err := os.Open(filepath.Join(testDataDir, name))
if err != nil {
t.Fatal(err)
}
img, err := png.Decode(f)
if err != nil {
t.Fatal(err)
}
cursor, err := NewCursorFromImages([]CursorImage{{img, HotSpot{spotX, spotY}}})
if err != nil {
t.Fatal(err)
}
return cursor
}
func loadICOFile(t *testing.T, name string) *Icon {
f, err := os.Open(filepath.Join(testDataDir, name))
if err != nil {
t.Fatal(err)
}
icon, err := LoadICO(f)
if err != nil {
t.Fatal(err)
}
return icon
}
func loadCURFile(t *testing.T, name string) *Cursor {
f, err := os.Open(filepath.Join(testDataDir, name))
if err != nil {
t.Fatal(err)
}
cursor, err := LoadCUR(f)
if err != nil {
t.Fatal(err)
}
return cursor
}
func loadImage(t *testing.T, name string) image.Image {
f, err := os.Open(filepath.Join(testDataDir, name))
if err != nil {
t.Fatal(err)
}
img, _, err := image.Decode(f)
if err != nil {
t.Fatal(err)
}
return img
}
func shiftImage(img image.Image, x, y int) image.Image {
shifted := image.NewNRGBA(image.Rectangle{
Min: image.Point{
X: img.Bounds().Min.X + x,
Y: img.Bounds().Min.Y + y,
},
Max: image.Point{
X: img.Bounds().Max.X + x,
Y: img.Bounds().Max.Y + y,
},
})
for srcY := img.Bounds().Min.Y; srcY < img.Bounds().Max.Y; srcY++ {
for srcX := img.Bounds().Min.X; srcX < img.Bounds().Max.X; srcX++ {
shifted.Set(srcX+x, srcY+y, img.At(srcX, srcY))
}
}
return shifted
}
type badReader struct {
br *bytes.Reader
errPos int64
returned bool
}
type badSeeker struct {
br *bytes.Reader
errIter int
returned bool
}
type badWriter struct {
badLen int
returned bool
}
func newBadWriter(badLen int) *badWriter {
return &badWriter{badLen: badLen}
}
const (
errRead = "expected read error"
errReadOn = "reading on after error"
errSeek = "expected seek error"
errSeekOn = "seeking on after error"
errWrite = "expected write error"
errWriteOn = "writing on after error"
)
func (r *badReader) Read(b []byte) (int, error) {
if r.returned {
return 0, errors.New(errReadOn)
}
p, _ := r.br.Seek(0, io.SeekCurrent)
if p <= r.errPos && r.errPos < p+int64(len(b)) {
n, _ := r.br.Read(b[:r.errPos-p])
r.returned = true
return n, errors.New(errRead)
}
return r.br.Read(b)
}
func (r *badReader) Seek(offset int64, whence int) (int64, error) {
if r.returned {
return 0, errors.New(errSeekOn)
}
return r.br.Seek(offset, whence)
}
func isExpectedReadErr(err error) bool {
return err != nil && err.Error() == errRead
}
func (s *badSeeker) Read(b []byte) (int, error) {
if s.returned {
return 0, errors.New(errReadOn)
}
return s.br.Read(b)
}
func (s *badSeeker) Seek(offset int64, whence int) (int64, error) {
if s.returned {
return 0, errors.New(errSeekOn)
}
if s.errIter <= 0 {
s.returned = true
return 0, errors.New(errSeek)
}
s.errIter--
return s.br.Seek(offset, whence)
}
func isExpectedSeekErr(err error) bool {
return err != nil && err.Error() == errSeek
}
func (w *badWriter) Write(b []byte) (n int, err error) {
if w.returned {
return 0, errors.New(errWriteOn)
}
w.badLen -= len(b)
if w.badLen <= 0 {
w.returned = true
return 0, errors.New(errWrite)
}
return len(b), nil
}
func isExpectedWriteErr(err error) bool {
return err != nil && err.Error() == errWrite
}
package winres
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"image"
"image/color"
"image/png"
"io"
"sort"
"github.com/nfnt/resize"
)
// Icon describes a Windows icon.
//
// This structure must only be created by constructors:
// NewIconFromImages, NewIconFromResizedImage, LoadICO
type Icon struct {
images []iconImage
}
var DefaultIconSizes = []int{256, 64, 48, 32, 16}
// NewIconFromImages makes an icon from a list of images.
//
// This converts every image to 32bpp PNG.
func NewIconFromImages(images []image.Image) (*Icon, error) {
icon := Icon{}
for _, img := range images {
if err := icon.addImage(img); err != nil {
return nil, err
}
}
return &icon, nil
}
// NewIconFromResizedImage makes an icon from a single Image by resizing it.
//
// If sizes is nil, the icon will be resized to: 256px, 64px, 48px, 32px, 16px.
func NewIconFromResizedImage(img image.Image, sizes []int) (*Icon, error) {
if sizes == nil {
sizes = DefaultIconSizes
}
if len(sizes) > 30 {
return nil, errors.New(errTooManyIconSizes)
}
icon := Icon{}
for _, s := range sizes {
if err := icon.addImage(resizeImage(img, s)); err != nil {
return nil, err
}
}
return &icon, nil
}
// LoadICO loads an ICO file and returns an icon, ready to embed in a resource set.
func LoadICO(ico io.ReadSeeker) (*Icon, error) {
hdr := iconDirHeader{}
if err := binaryRead(ico, &hdr); err != nil {
return nil, err
}
if hdr.Type != 1 || hdr.Reserved != 0 {
return nil, errors.New(errNotICO)
}
entries := make([]iconFileDirEntry, hdr.Count)
if err := binaryRead(ico, entries); err != nil {
return nil, err
}
icon := &Icon{}
for _, e := range entries {
// Arbitrary limit: no more than 10MB per image, so we can blindly allocate bytes and try to read them.
if e.BytesInRes > 0xA00000 {
return nil, fmt.Errorf(errImageLengthTooBig)
}
if _, err := ico.Seek(int64(e.ImageOffset), io.SeekStart); err != nil {
return nil, err
}
img := make([]byte, e.BytesInRes)
if err := readFull(ico, img); err != nil {
return nil, err
}
icon.images = append(icon.images, iconImage{
info: e.iconInfo,
image: img,
})
}
return icon, nil
}
// SaveICO saves an icon as an ICO file.
func (icon *Icon) SaveICO(ico io.Writer) error {
err := binary.Write(ico, binary.LittleEndian, &iconDirHeader{
Type: 1,
Count: uint16(len(icon.images)),
})
if err != nil {
return err
}
var (
pos = sizeOfIconDirHeader
hdrLen = sizeOfIconDirHeader + len(icon.images)*sizeOfIconFileDirEntry
offset = hdrLen
)
icon.order()
for i := range icon.images {
err = binary.Write(ico, binary.LittleEndian, &iconFileDirEntry{
iconInfo: icon.images[i].info,
ImageOffset: uint32(offset),
})
if err != nil {
return err
}
offset += len(icon.images[i].image)
pos += sizeOfIconFileDirEntry
}
for i := range icon.images {
_, err = ico.Write(icon.images[i].image)
if err != nil {
return err
}
}
return nil
}
// SetIcon adds the icon to the resource set.
//
// The first icon will be the application's icon, as shown in Windows Explorer.
// That means:
// 1. First name in case-sensitive ascending order, or else...
// 2. First ID in ascending order
//
func (rs *ResourceSet) SetIcon(resID Identifier, icon *Icon) error {
return rs.SetIconTranslation(resID, LCIDNeutral, icon)
}
// SetIconTranslation adds the icon to a specific language in the resource set.
//
// The first icon will be the application's icon, as shown in Windows Explorer.
// That means:
// 1. First name in case-sensitive ascending order, or else...
// 2. First ID in ascending order
//
func (rs *ResourceSet) SetIconTranslation(resID Identifier, langID uint16, icon *Icon) error {
b := &bytes.Buffer{}
binary.Write(b, binary.LittleEndian, iconDirHeader{
Type: 1,
Count: uint16(len(icon.images)),
})
icon.order()
for _, img := range icon.images {
id := rs.lastIconID + 1
binary.Write(b, binary.LittleEndian, iconResDirEntry{
iconInfo: img.info,
Id: id,
})
if err := rs.Set(RT_ICON, ID(id), LCIDNeutral, img.image); err != nil {
return err
}
}
return rs.Set(RT_GROUP_ICON, resID, langID, b.Bytes())
}
// GetIcon extracts an icon from a resource set.
func (rs *ResourceSet) GetIcon(resID Identifier) (*Icon, error) {
return rs.GetIconTranslation(resID, rs.firstLang(RT_GROUP_ICON, resID))
}
// GetIconTranslation extracts an icon from a specific language of the resource set.
func (rs *ResourceSet) GetIconTranslation(resID Identifier, langID uint16) (*Icon, error) {
data := rs.Get(RT_GROUP_ICON, resID, langID)
if data == nil {
return nil, errors.New(errGroupNotFound)
}
in := bytes.NewReader(data)
hdr := iconDirHeader{}
err := binaryRead(in, &hdr)
if err != nil || hdr.Type != 1 || hdr.Reserved != 0 {
return nil, errors.New(errInvalidGroup)
}
icon := &Icon{}
for i := 0; i < int(hdr.Count); i++ {
entry := iconResDirEntry{}
err := binaryRead(in, &entry)
if err != nil {
return nil, errors.New(errInvalidGroup)
}
img := rs.Get(RT_ICON, ID(entry.Id), rs.firstLang(RT_ICON, ID(entry.Id)))
if img == nil {
return nil, errors.New(errIconMissing)
}
icon.images = append(icon.images, iconImage{
info: entry.iconInfo,
image: img,
})
}
return icon, nil
}
// An icon is made of an icon directory and actual icons.
//
// The icon directory is made of a header and entries.
//
// Directory entries are slightly different between ICO files and RT_GROUP_ICON resources.
//
// https://devblogs.microsoft.com/oldnewthing/20120720-00/?p=7083
// https://docs.microsoft.com/en-us/previous-versions/ms997538
// iconDirHeader is the binary format of an icon directory header.
type iconDirHeader struct {
Reserved uint16
Type uint16
Count uint16
}
const sizeOfIconDirHeader = 6
// iconFileDirEntry is the binary format of an icon directory entry, in an ICO file.
type iconFileDirEntry struct {
iconInfo
ImageOffset uint32
}
const sizeOfIconFileDirEntry = 16
// iconResDirEntry is the binary format of an icon directory entry, in an RT_GROUP_ICON resource.
type iconResDirEntry struct {
iconInfo
Id uint16
}
// iconInfo is the common part of iconResDirEntry and iconFileDirEntry.
type iconInfo struct {
Width uint8
Height uint8
ColorCount uint8
Reserved uint8
Planes uint16
BitCount uint16
BytesInRes uint32
}
type iconImage struct {
info iconInfo
image []byte
}
// This makes a testing error reporting possible
var pngEncode = png.Encode
func (icon *Icon) addImage(img image.Image) error {
bounds := img.Bounds()
if bounds.Empty() {
return errors.New(errInvalidImageDimensions)
}
if bounds.Size().X > 256 || bounds.Size().Y > 256 {
return errors.New(errImageTooBig)
}
img = imageInSquareNRGBA(img, true)
bounds = img.Bounds()
buf := &bytes.Buffer{}
if err := pngEncode(buf, img); err != nil {
return err
}
icon.images = append(icon.images, iconImage{
info: iconInfo{
Width: uint8(bounds.Size().X), // 0 means 256
Height: uint8(bounds.Size().Y), // 0 means 256
ColorCount: 0, // should be defined as 1 << BitCount only if BitCount < 8
Reserved: 0,
Planes: 1,
BitCount: 32,
BytesInRes: uint32(buf.Len()),
},
image: buf.Bytes(),
})
return nil
}
func (icon *Icon) order() {
// Sort images by descending size and quality
sort.SliceStable(icon.images, func(i, j int) bool {
img1, img2 := &icon.images[i].info, &icon.images[j].info
return img1.BitCount > img2.BitCount ||
img1.BitCount == img2.BitCount && int(img1.Width-1)+1 > int(img2.Width-1)+1
})
}
func resizeImage(img image.Image, size int) image.Image {
var (
sz = img.Bounds().Size()
w, h = size, size
)
if sz.X < sz.Y {
w = 0
} else if sz.X > sz.Y {
h = 0
}
return resize.Resize(uint(w), uint(h), img, resize.Lanczos2)
}
func imageInSquareNRGBA(img image.Image, center bool) image.Image {
w, h := img.Bounds().Size().X, img.Bounds().Size().Y
if w == h && img.ColorModel() == color.NRGBAModel {
return img
}
length := w
if length < h {
length = h
}
offset := image.Point{
X: -img.Bounds().Min.X,
Y: -img.Bounds().Min.Y,
}
if center {
offset.X -= (w - length) / 2
offset.Y -= (h - length) / 2
}
square := image.NewNRGBA(image.Rectangle{Max: image.Point{X: length, Y: length}})
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
square.Set(x+offset.X, y+offset.Y, img.At(x, y))
}
}
return square
}
package winres
import (
"bytes"
"errors"
"image"
"io"
"os"
"path/filepath"
"reflect"
"testing"
)
func TestLoadICO(t *testing.T) {
f, err := os.Open(filepath.Join(testDataDir, "icon.ico"))
if err != nil {
t.Fatal("missing test data")
}
defer f.Close()
cursor, err := LoadICO(f)
if err != nil {
t.Fatal(err)
}
cursor.order()
expected := []iconImage{
{
info: iconInfo{
Width: 0,
Height: 0,
ColorCount: 0,
Planes: 1,
BitCount: 32,
BytesInRes: 0x7D30,
},
},
{
info: iconInfo{
Width: 48,
Height: 48,
ColorCount: 0,
Planes: 1,
BitCount: 32,
BytesInRes: 0x25A8,
},
},
{
info: iconInfo{
Width: 32,
Height: 32,
ColorCount: 0,
Planes: 1,
BitCount: 32,
BytesInRes: 0x10A8,
},
},
{
info: iconInfo{
Width: 16,
Height: 16,
ColorCount: 0,
Planes: 1,
BitCount: 32,
BytesInRes: 0x468,
},
},
{
info: iconInfo{
Width: 0,
Height: 0,
ColorCount: 0,
Planes: 1,
BitCount: 8,
BytesInRes: 0x5824,
},
},
{
info: iconInfo{
Width: 48,
Height: 48,
ColorCount: 0,
Planes: 1,
BitCount: 8,
BytesInRes: 0xEA8,
},
},
{
info: iconInfo{
Width: 32,
Height: 32,
ColorCount: 0,
Planes: 1,
BitCount: 8,
BytesInRes: 0x8A8,
},
},
{
info: iconInfo{
Width: 16,
Height: 16,
ColorCount: 0,
Planes: 1,
BitCount: 8,
BytesInRes: 0x568,
},
},
{
info: iconInfo{
Width: 0,
Height: 0,
ColorCount: 16,
Planes: 1,
BitCount: 4,
BytesInRes: 0x55FC,
},
},
{
info: iconInfo{
Width: 48,
Height: 48,
ColorCount: 16,
Planes: 1,
BitCount: 4,
BytesInRes: 0x668,
},
},
{
info: iconInfo{
Width: 32,
Height: 32,
ColorCount: 16,
Planes: 1,
BitCount: 4,
BytesInRes: 0x2E8,
},
},
{
info: iconInfo{
Width: 16,
Height: 16,
ColorCount: 16,
Planes: 1,
BitCount: 4,
BytesInRes: 0x128,
},
},
}
for i := range expected {
if !reflect.DeepEqual(cursor.images[i].info, expected[i].info) {
t.Errorf("%s - image %d: expected %v got %v", t.Name(), i, expected[i].info, cursor.images[i].info)
}
}
}
func TestLoadICO_ErrEOF1(t *testing.T) {
cursor, err := LoadICO(bytes.NewReader([]byte{0, 0, 1, 0, 1}))
if err != io.ErrUnexpectedEOF || cursor != nil {
t.Fail()
}
}
func TestLoadICO_ErrEOF2(t *testing.T) {
icon, err := LoadICO(bytes.NewReader([]byte{
0, 0, 1, 0, 0xFF, 0xFF,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}))
if err != io.ErrUnexpectedEOF || icon != nil {
t.Fail()
}
}
func TestLoadICO_ErrImageOffset(t *testing.T) {
temp, err := os.ReadFile(filepath.Join(testDataDir, "icon.ico"))
if err != nil {
t.Fatal(err)
}
temp[21] = 0x01
icon, err := LoadICO(bytes.NewReader(temp))
if err != io.ErrUnexpectedEOF || icon != nil {
t.Fail()
}
}
func TestLoadCUR_ErrNotICO(t *testing.T) {
temp, err := os.ReadFile(filepath.Join(testDataDir, "icon.ico"))
if err != nil {
t.Fatal(err)
}
temp[0] = 1
icon, err := LoadICO(bytes.NewReader(temp))
if err == nil || icon != nil || err.Error() != errNotICO {
t.Fail()
}
temp[0] = 0
temp[2] = 2
icon, err = LoadICO(bytes.NewReader(temp))
if err == nil || icon != nil || err.Error() != errNotICO {
t.Fail()
}
}
func TestLoadICO_ErrSeek(t *testing.T) {
data, err := os.ReadFile(filepath.Join(testDataDir, "icon.ico"))
if err != nil {
t.Fatal("missing test data")
}
r := &badSeeker{br: bytes.NewReader(data)}
icon, err := LoadICO(r)
if !isExpectedSeekErr(err) || icon != nil {
t.Fatal("expected seek error, got", err)
}
}
func TestLoadICO_ImageLengthLimit(t *testing.T) {
_, err := LoadICO(bytes.NewReader([]byte{
0, 0, 1, 0, 1, 0, 32, 32, 0, 0, 1, 0, 32, 0,
0x00, 0x00, 0xA0, 0x00, // image data length = 0xA00000
0, 0, 0, 22,
}))
if err == nil || err.Error() == errImageLengthTooBig {
t.Fail()
}
_, err = LoadICO(bytes.NewReader([]byte{
0, 0, 1, 0, 1, 0, 32, 32, 0, 0, 0, 0, 0, 0,
0x01, 0x00, 0xA0, 0x00, // image data length = 0xA00001
0, 0, 0, 22,
}))
if err == nil || err.Error() != errImageLengthTooBig {
t.Fail()
}
}
func TestIcon_SaveICO(t *testing.T) {
data, err := os.ReadFile(filepath.Join(testDataDir, "icon.ico"))
if err != nil {
t.Fatal(err)
}
icon, err := LoadICO(bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
buf := &bytes.Buffer{}
err = icon.SaveICO(buf)
if err != nil {
t.Fatal(err)
}
checkBinary(t, buf.Bytes())
}
func TestIcon_SaveICO_ErrWrite(t *testing.T) {
data, err := os.ReadFile(filepath.Join(testDataDir, "icon.ico"))
if err != nil {
t.Fatal(err)
}
icon, err := LoadICO(bytes.NewReader(data))
if err != nil {
t.Fatal(err)
}
w := newBadWriter(5)
err = icon.SaveICO(w)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
w = newBadWriter(20)
err = icon.SaveICO(w)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
w = newBadWriter(41885)
err = icon.SaveICO(w)
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
}
func TestNewIconFromImages(t *testing.T) {
icon, err := NewIconFromImages([]image.Image{
shiftImage(loadImage(t, "cur-16x8.png"), 30, 0),
loadImage(t, "cur-16x32.png"),
loadImage(t, "cur-32x64.png"),
loadImage(t, "cur-64x128.png"),
shiftImage(loadImage(t, "cur-16x8.png"), 1, -100),
})
if err != nil {
t.Fatal(err)
}
checkBinary(t, icoToBinary(icon))
}
func TestNewIconFromImages_ErrDimensions(t *testing.T) {
_, err := NewIconFromImages([]image.Image{image.NewNRGBA(image.Rectangle{})})
if err == nil || err.Error() != errInvalidImageDimensions {
t.Fail()
}
}
func TestNewIconFromImages_ErrEncode(t *testing.T) {
enc := pngEncode
defer func() { pngEncode = enc }()
pngEncode = func(w io.Writer, m image.Image) error {
return errors.New("oops")
}
_, err := NewIconFromImages([]image.Image{
loadImage(t, "cur-32x64.png"),
loadImage(t, "cur-64x128.png"),
})
if err == nil || err.Error() != "oops" {
t.Fail()
}
}
func TestNewIconFromImages_ErrTooBig(t *testing.T) {
_, err := NewIconFromImages([]image.Image{
image.NewNRGBA(image.Rectangle{
Max: image.Point{
X: 257,
Y: 256,
},
}),
})
if err == nil || err.Error() != errImageTooBig {
t.Fail()
}
_, err = NewIconFromImages([]image.Image{
image.NewNRGBA(image.Rectangle{
Max: image.Point{
X: 256,
Y: 257,
},
}),
})
if err == nil || err.Error() != errImageTooBig {
t.Fail()
}
_, err = NewIconFromImages([]image.Image{
image.NewNRGBA(image.Rectangle{
Max: image.Point{
X: 256,
Y: 256,
},
}),
})
if err != nil {
t.Fail()
}
}
func TestNewIconFromResizedImage(t *testing.T) {
icon, err := NewIconFromResizedImage(shiftImage(loadImage(t, "img.png"), -100, -10), nil)
if err != nil {
t.Fatal(err)
}
checkBinary(t, icoToBinary(icon))
}
func TestNewIconFromResizedImage_Ratio1(t *testing.T) {
icon, err := NewIconFromResizedImage(shiftImage(loadImage(t, "cur-32x64.png"), 5, -3), []int{32, 64})
if err != nil {
t.Fatal(err)
}
checkBinary(t, icoToBinary(icon))
}
func TestNewIconFromResizedImage_Ratio2(t *testing.T) {
icon, err := NewIconFromResizedImage(shiftImage(loadImage(t, "cur-16x8.png"), -2, 8), []int{8, 16})
if err != nil {
t.Fatal(err)
}
checkBinary(t, icoToBinary(icon))
}
func TestNewIconFromResizedImage_InvalidSize(t *testing.T) {
icon, err := NewIconFromResizedImage(shiftImage(loadImage(t, "img.png"), 100, 100), []int{16, 257})
if err == nil || icon != nil || err.Error() != errImageTooBig {
t.Fail()
}
}
func TestNewIconFromResizedImage_TooManySizes(t *testing.T) {
s := make([]int, 30)
for i := range s {
s[i] = i + 1
}
_, err := NewIconFromResizedImage(shiftImage(loadImage(t, "img.png"), 100, 100), s)
if err != nil {
t.Fail()
}
s = append(s, len(s)+1)
icon, err := NewIconFromResizedImage(shiftImage(loadImage(t, "img.png"), 100, 100), s)
if err == nil || icon != nil || err.Error() != errTooManyIconSizes {
t.Fail()
}
}
func TestResourceSet_SetIcon(t *testing.T) {
rs := ResourceSet{}
id := Name("ICON")
icon := loadICOFile(t, "icon.ico")
err := rs.SetIcon(id, icon)
if err != nil {
t.Fatal(err)
}
if int(rs.lastIconID) != len(icon.images) {
t.Fail()
}
checkIconResource(t, &rs, id, 0, icon)
}
func TestResourceSet_SetIcon_IDOverflow(t *testing.T) {
rs := ResourceSet{}
rs.lastIconID = 0xFFF4
icon := loadICOFile(t, "icon.ico")
err := rs.SetIcon(Name("ICON"), icon)
if err == nil || err.Error() != errZeroID {
t.Fail()
}
}
func TestResourceSet_SetIconTranslation(t *testing.T) {
rs := ResourceSet{}
icon1 := loadICOFile(t, "en.ico")
icon2 := loadICOFile(t, "fr.ico")
icon3, err := NewIconFromImages([]image.Image{loadImage(t, "cur-16x8.png")})
if err != nil {
t.Fatal(err)
}
err = rs.SetIconTranslation(Name("LOCALICO"), 0x409, icon1)
if err != nil {
t.Fatal(err)
}
err = rs.SetIconTranslation(Name("ANOTHERICO"), 0x409, icon3)
if err != nil {
t.Fatal(err)
}
err = rs.SetIconTranslation(Name("LOCALICO"), 0x40C, icon2)
if err != nil {
t.Fatal(err)
}
checkIconResource(t, &rs, Name("LOCALICO"), 0x409, icon1)
checkIconResource(t, &rs, Name("LOCALICO"), 0x40C, icon2)
checkIconResource(t, &rs, Name("ANOTHERICO"), 0x409, icon3)
}
func TestResourceSet_GetIconTranslation_Err(t *testing.T) {
rs := ResourceSet{}
bin0 := []byte{0, 0, 1, 0, 1}
bin1 := []byte{0, 0, 2, 0, 1, 0, 32, 32, 0, 0, 1, 0, 32, 0, 42, 0, 0, 0, 1, 0}
bin2 := []byte{0, 0, 1, 0, 1, 0, 32, 32, 0, 0, 1, 0, 32, 0, 42, 0, 0, 0, 1}
bin3 := []byte{0, 0, 1, 0, 1, 0, 32, 32, 0, 0, 1, 0, 32, 0, 42, 0, 0, 0, 1, 0}
err := rs.Set(RT_GROUP_ICON, Name("LOCALICO"), 0, bin0)
if err != nil {
t.Fatal(err)
}
err = rs.Set(RT_GROUP_ICON, Name("LOCALICO"), 0x401, bin1)
if err != nil {
t.Fatal(err)
}
err = rs.Set(RT_GROUP_ICON, Name("LOCALICO"), 0x402, bin2)
if err != nil {
t.Fatal(err)
}
err = rs.Set(RT_GROUP_ICON, Name("LOCALICO"), 0x403, bin3)
if err != nil {
t.Fatal(err)
}
var icon *Icon
icon, err = rs.GetIcon(ID(0))
if err == nil || icon != nil || err.Error() != errGroupNotFound {
t.Fail()
}
icon, err = rs.GetIconTranslation(Name("LOCALIC"), 0x401)
if err == nil || icon != nil || err.Error() != errGroupNotFound {
t.Fail()
}
icon, err = rs.GetIconTranslation(Name("LOCALICO"), 0x409)
if err == nil || icon != nil || err.Error() != errGroupNotFound {
t.Fail()
}
icon, err = rs.GetIconTranslation(Name("LOCALICO"), 0)
if err == nil || icon != nil || err.Error() != errInvalidGroup {
t.Fail()
}
icon, err = rs.GetIconTranslation(Name("LOCALICO"), 0x401)
if err == nil || icon != nil || err.Error() != errInvalidGroup {
t.Fail()
}
icon, err = rs.GetIconTranslation(Name("LOCALICO"), 0x402)
if err == nil || icon != nil || err.Error() != errInvalidGroup {
t.Fail()
}
icon, err = rs.GetIconTranslation(Name("LOCALICO"), 0x403)
if err == nil || icon != nil || err.Error() != errIconMissing {
t.Fail()
}
}
func checkIconResource(t *testing.T, rs *ResourceSet, ident Identifier, langID uint16, source *Icon) {
icon, err := rs.GetIconTranslation(ident, langID)
if err != nil {
t.Fatal(err)
}
buf1, buf2 := &bytes.Buffer{}, &bytes.Buffer{}
err = source.SaveICO(buf1)
if err != nil {
t.Fatal(err)
}
err = icon.SaveICO(buf2)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(buf1.Bytes(), buf2.Bytes()) {
t.Fail()
}
}
func icoToBinary(icon *Icon) []byte {
buf := &bytes.Buffer{}
icon.SaveICO(buf)
return buf.Bytes()
}
package winres
import (
"errors"
"strings"
)
// ID is the type of a resource id, or resource type id.
type ID uint16
// Name is the type of a resource name, or a resource type name.
type Name string
// Identifier is either an ID or a Name.
//
// When you are asked for an Identifier, you can pass an int cast to an ID or a string cast to a Name.
type Identifier interface {
// This method serves both to seal the interface and help order identifiers the standard way
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#resource-directory-entries
lessThan(ident Identifier) bool
}
func (id ID) lessThan(ident Identifier) bool {
right, ok := ident.(ID)
return ok && id < right
}
func (n Name) lessThan(ident Identifier) bool {
right, ok := ident.(Name)
return !ok || n < right
}
func checkIdentifier(ident Identifier) error {
switch ident := ident.(type) {
case ID:
if ident == 0 {
return errors.New(errZeroID)
}
case Name:
if ident == "" {
return errors.New(errEmptyName)
}
if strings.ContainsRune(string(ident), 0) {
return errors.New(errNameContainsNUL)
}
}
return nil
}
package winres
import (
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"strconv"
"strings"
"text/template"
)
// AppManifest describes an application manifest.
//
// Its zero value corresponds to the most common case.
type AppManifest struct {
Identity AssemblyIdentity `json:"identity"`
Description string `json:"description"`
Compatibility SupportedOS `json:"minimum-os"`
ExecutionLevel ExecutionLevel `json:"execution-level"`
UIAccess bool `json:"ui-access"` // Require access to other applications' UI elements
AutoElevate bool `json:"auto-elevate"`
DPIAwareness DPIAwareness `json:"dpi-awareness"`
DisableTheming bool `json:"disable-theming"`
DisableWindowFiltering bool `json:"disable-window-filtering"`
HighResolutionScrollingAware bool `json:"high-resolution-scrolling-aware"`
UltraHighResolutionScrollingAware bool `json:"ultra-high-resolution-scrolling-aware"`
LongPathAware bool `json:"long-path-aware"`
PrinterDriverIsolation bool `json:"printer-driver-isolation"`
GDIScaling bool `json:"gdi-scaling"`
SegmentHeap bool `json:"segment-heap"`
UseCommonControlsV6 bool `json:"use-common-controls-v6"` // Application requires Common Controls V6 (V5 remains the default)
}
// AssemblyIdentity defines the side-by-side assembly identity of the executable.
//
// It should not be needed unless another assembly depends on this one.
//
// If the Name field is empty, the <assemblyIdentity> element will be omitted.
type AssemblyIdentity struct {
Name string
Version [4]uint16
}
// DPIAwareness is an enumeration which corresponds to the <dpiAware> and the <dpiAwareness> elements.
//
// When it is set to DPIPerMonitorV2, it will fallback to DPIAware if the OS does not support it.
//
// DPIPerMonitor would not scale windows on secondary monitors.
type DPIAwareness int
const (
DPIAware DPIAwareness = iota
DPIUnaware
DPIPerMonitor
DPIPerMonitorV2
)
// SupportedOS is an enumeration that provides a simplified way to fill the
// compatibility element in an application manifest, by only setting a minimum OS.
//
// Its zero value is Win7AndAbove, which matches Go's requirements.
//
// https://github.com/golang/go/wiki/MinimumRequirements#windows
type SupportedOS int
const (
WinVistaAndAbove SupportedOS = iota - 1
Win7AndAbove
Win8AndAbove
Win81AndAbove
Win10AndAbove
)
// ExecutionLevel is used in an AppManifest to set the required execution level.
type ExecutionLevel int
const (
AsInvoker ExecutionLevel = iota
HighestAvailable
RequireAdministrator
)
const (
osWin10 = "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"
osWin81 = "{1f676c76-80e1-4239-95bb-83d0f6d0da78}"
osWin8 = "{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"
osWin7 = "{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"
osWinVista = "{e2011457-1546-43c5-a5fe-008deee3d3f0}"
)
// language=GoTemplate
var manifestTemplate = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
{{- if .AssemblyName}}
<assemblyIdentity type="win32" name="{{.AssemblyName | html}}" version="{{.AssemblyVersion}}" processorArchitecture="*"/>
{{- end}}
{{- if .Description}}
<description>{{.Description | html}}</description>
{{- end}}
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
{{- range $osID := .SupportedOS}}
<supportedOS Id="{{$osID}}"/>
{{- end}}
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">{{.DPIAware}}</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">{{.DPIAwareness}}</dpiAwareness>
{{- if .AutoElevate}}
<autoElevate xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</autoElevate>
{{- end}}
{{- if .DisableTheming}}
<disableTheming xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</disableTheming>
{{- end}}
{{- if .DisableWindowFiltering}}
<disableWindowFiltering xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">true</disableWindowFiltering>
{{- end}}
{{- if .HighResolutionScrollingAware}}
<highResolutionScrollingAware xmlns="http://schemas.microsoft.com/SMI/2013/WindowsSettings">true</highResolutionScrollingAware>
{{- end}}
{{- if .PrinterDriverIsolation}}
<printerDriverIsolation xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">true</printerDriverIsolation>
{{- end}}
{{- if .UltraHighResolutionScrollingAware}}
<ultraHighResolutionScrollingAware xmlns="http://schemas.microsoft.com/SMI/2013/WindowsSettings">true</ultraHighResolutionScrollingAware>
{{- end}}
{{- if .LongPathAware}}
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
{{- end}}
{{- if .GDIScaling}}
<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>
{{- end}}
{{- if .SegmentHeap}}
<heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType>
{{- end}}
</windowsSettings>
</application>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="{{.ExecutionLevel}}" uiAccess="{{if .UIAccess}}true{{else}}false{{end}}"/>
</requestedPrivileges>
</security>
</trustInfo>
{{- if .UseCommonControlsV6}}
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
{{- end}}
</assembly>
`
func makeManifest(manifest AppManifest) []byte {
vars := struct {
AppManifest
AssemblyName string
AssemblyVersion string
SupportedOS []string
DPIAware string
DPIAwareness string
ExecutionLevel string
}{AppManifest: manifest}
if manifest.Identity.Name != "" {
vars.AssemblyName = manifest.Identity.Name
v := manifest.Identity.Version
vars.AssemblyVersion = fmt.Sprintf("%d.%d.%d.%d", v[0], v[1], v[2], v[3])
}
vars.SupportedOS = []string{
osWin10,
osWin81,
osWin8,
osWin7,
osWinVista,
}
switch manifest.Compatibility {
case Win7AndAbove:
vars.SupportedOS = vars.SupportedOS[:4]
case Win8AndAbove:
vars.SupportedOS = vars.SupportedOS[:3]
case Win81AndAbove:
vars.SupportedOS = vars.SupportedOS[:2]
case Win10AndAbove:
vars.SupportedOS = vars.SupportedOS[:1]
}
switch manifest.ExecutionLevel {
case RequireAdministrator:
vars.ExecutionLevel = "requireAdministrator"
case HighestAvailable:
vars.ExecutionLevel = "highestAvailable"
default:
vars.ExecutionLevel = "asInvoker"
}
switch manifest.DPIAwareness {
case DPIAware:
vars.DPIAware = "true"
vars.DPIAwareness = "system"
case DPIPerMonitor:
vars.DPIAware = "true/pm"
vars.DPIAwareness = "permonitor"
case DPIPerMonitorV2:
// PerMonitorV2 fixes the scale on secondary monitors
// If not available, the closest option seems to be System
vars.DPIAware = "true"
vars.DPIAwareness = "permonitorv2,system"
case DPIUnaware:
vars.DPIAware = "false"
vars.DPIAwareness = "unaware"
}
buf := &bytes.Buffer{}
tmpl := template.Must(template.New("manifest").Parse(manifestTemplate))
err := tmpl.Execute(buf, vars)
if err != nil {
panic(err)
}
return buf.Bytes()
}
type appManifestXML struct {
Identity struct {
Name string `xml:"name,attr"`
Version string `xml:"version,attr"`
} `xml:"assemblyIdentity"`
Description string `xml:"description"`
Compatibility struct {
Application struct {
SupportedOS []struct {
Id string `xml:"Id,attr"`
} `xml:"supportedOS"`
} `xml:"application"`
} `xml:"compatibility"`
Application struct {
WindowsSettings struct {
DPIAware string `xml:"dpiAware"`
DPIAwareness string `xml:"dpiAwareness"`
AutoElevate string `xml:"autoElevate"`
DisableTheming string `xml:"disableTheming"`
DisableWindowFiltering string `xml:"disableWindowFiltering"`
HighResolutionScrollingAware string `xml:"highResolutionScrollingAware"`
PrinterDriverIsolation string `xml:"printerDriverIsolation"`
UltraHighResolutionScrollingAware string `xml:"ultraHighResolutionScrollingAware"`
LongPathAware string `xml:"longPathAware"`
GDIScaling string `xml:"gdiScaling"`
HeapType string `xml:"heapType"`
} `xml:"windowsSettings"`
} `xml:"application"`
TrustInfo struct {
Security struct {
RequestedPrivileges struct {
RequestedExecutionLevel struct {
Level string `xml:"level,attr"`
UIAccess string `xml:"uiAccess,attr"`
} `xml:"requestedExecutionLevel"`
} `xml:"requestedPrivileges"`
} `xml:"security"`
} `xml:"trustInfo"`
Dependency struct {
DependentAssembly []struct {
Identity struct {
Name string `xml:"name,attr"`
Version string `xml:"version,attr"`
PublicKeyToken string `xml:"publicKeyToken,attr"`
} `xml:"assemblyIdentity"`
} `xml:"dependentAssembly"`
} `xml:"dependency"`
}
// AppManifestFromXML makes an AppManifest from an xml manifest,
// trying to retrieve as much valid information as possible.
//
// If the xml contains other data, they are ignored.
//
// This function can only return xml syntax errors, other errors are ignored.
func AppManifestFromXML(data []byte) (AppManifest, error) {
x := appManifestXML{}
err := xml.Unmarshal(data, &x)
if err != nil {
return AppManifest{}, err
}
var m AppManifest
m.Identity.Name = x.Identity.Name
v := strings.Split(x.Identity.Version, ".")
if len(v) > 4 {
v = v[:4]
}
for i := range v {
n, _ := strconv.ParseUint(v[i], 10, 16)
m.Identity.Version[i] = uint16(n)
}
m.Description = x.Description
m.Compatibility = Win10AndAbove + 1
for _, os := range x.Compatibility.Application.SupportedOS {
c := osIDToEnum(os.Id)
if c < m.Compatibility {
m.Compatibility = c
}
}
if m.Compatibility > Win10AndAbove {
m.Compatibility = Win7AndAbove
}
settings := x.Application.WindowsSettings
m.DPIAwareness = readDPIAwareness(settings.DPIAware, settings.DPIAwareness)
m.AutoElevate = manifestBool(settings.AutoElevate)
m.DisableTheming = manifestBool(settings.DisableTheming)
m.DisableWindowFiltering = manifestBool(settings.DisableWindowFiltering)
m.HighResolutionScrollingAware = manifestBool(settings.HighResolutionScrollingAware)
m.PrinterDriverIsolation = manifestBool(settings.PrinterDriverIsolation)
m.UltraHighResolutionScrollingAware = manifestBool(settings.UltraHighResolutionScrollingAware)
m.LongPathAware = manifestBool(settings.LongPathAware)
m.GDIScaling = manifestBool(settings.GDIScaling)
m.SegmentHeap = manifestString(settings.HeapType) == "segmentheap"
for _, dep := range x.Dependency.DependentAssembly {
if manifestString(dep.Identity.Name) == "microsoft.windows.common-controls" &&
strings.HasPrefix(manifestString(dep.Identity.Version), "6.") &&
manifestString(dep.Identity.PublicKeyToken) == "6595b64144ccf1df" {
m.UseCommonControlsV6 = true
}
}
m.UIAccess = manifestBool(x.TrustInfo.Security.RequestedPrivileges.RequestedExecutionLevel.UIAccess)
switch manifestString(x.TrustInfo.Security.RequestedPrivileges.RequestedExecutionLevel.Level) {
case "requireadministrator":
m.ExecutionLevel = RequireAdministrator
case "highestavailable":
m.ExecutionLevel = HighestAvailable
}
return m, nil
}
func readDPIAwareness(dpiAware string, dpiAwareness string) DPIAwareness {
for _, s := range strings.Split(dpiAwareness, ",") {
switch manifestString(s) {
case "permonitorv2":
return DPIPerMonitorV2
case "permonitor":
return DPIPerMonitor
case "system":
return DPIAware
case "unaware":
return DPIUnaware
}
}
switch manifestString(dpiAware) {
case "true":
return DPIAware
case "true/pm":
return DPIPerMonitor
}
return DPIUnaware
}
func manifestString(s string) string {
return strings.ToLower(strings.TrimSpace(s))
}
func manifestBool(s string) bool {
return manifestString(s) == "true"
}
func osIDToEnum(osID string) SupportedOS {
switch osID {
case osWinVista:
return WinVistaAndAbove
case osWin7:
return Win7AndAbove
case osWin8:
return Win8AndAbove
case osWin81:
return Win81AndAbove
}
return Win10AndAbove
}
// JSON marshalling:
func (os SupportedOS) MarshalText() ([]byte, error) {
switch os {
case WinVistaAndAbove:
return []byte("vista"), nil
case Win7AndAbove:
return []byte("win7"), nil
case Win8AndAbove:
return []byte("win8"), nil
case Win81AndAbove:
return []byte("win8.1"), nil
case Win10AndAbove:
return []byte("win10"), nil
}
return nil, errors.New(errUnknownSupportedOS)
}
func (os *SupportedOS) UnmarshalText(b []byte) error {
switch strings.ToLower(strings.TrimSpace(string(b))) {
case "vista":
*os = WinVistaAndAbove
return nil
case "win7":
*os = Win7AndAbove
return nil
case "win8":
*os = Win8AndAbove
return nil
case "win8.1":
*os = Win81AndAbove
return nil
case "win10":
*os = Win10AndAbove
return nil
}
return errors.New(errUnknownSupportedOS)
}
func (a DPIAwareness) MarshalText() ([]byte, error) {
switch a {
case DPIAware:
return []byte("system"), nil
case DPIUnaware:
return []byte("unaware"), nil
case DPIPerMonitor:
return []byte("per monitor"), nil
case DPIPerMonitorV2:
return []byte("per monitor v2"), nil
}
return nil, errors.New(errUnknownDPIAwareness)
}
func (a *DPIAwareness) UnmarshalText(b []byte) error {
switch strings.ToLower(strings.TrimSpace(string(b))) {
case "system", "true", "":
*a = DPIAware
return nil
case "unaware", "false":
*a = DPIUnaware
return nil
case "per monitor", "permonitor", "true/pm":
*a = DPIPerMonitor
return nil
case "per monitor v2", "permonitorv2":
*a = DPIPerMonitorV2
return nil
}
return errors.New(errUnknownDPIAwareness)
}
func (level ExecutionLevel) MarshalText() ([]byte, error) {
switch level {
case AsInvoker:
return []byte(""), nil
case HighestAvailable:
return []byte("highest"), nil
case RequireAdministrator:
return []byte("administrator"), nil
}
return nil, errors.New(errUnknownExecLevel)
}
func (level *ExecutionLevel) UnmarshalText(b []byte) error {
switch strings.ToLower(strings.TrimSpace(string(b))) {
case "", "as invoker", "asinvoker":
*level = AsInvoker
return nil
case "highest", "highest available", "highestavailable":
*level = HighestAvailable
return nil
case "administrator", "require administrator", "requireadministrator":
*level = RequireAdministrator
return nil
}
return errors.New(errUnknownExecLevel)
}
type assemblyIdentityJSON struct {
Name string `json:"name"`
Version string `json:"version"`
}
func (ai AssemblyIdentity) MarshalJSON() ([]byte, error) {
if ai.Name == "" {
return []byte(`{}`), nil
}
j := assemblyIdentityJSON{}
j.Name = ai.Name
if ai.Name != "" {
j.Version = fmt.Sprintf("%d.%d.%d.%d", ai.Version[0], ai.Version[1], ai.Version[2], ai.Version[3])
}
return json.Marshal(j)
}
func (ai *AssemblyIdentity) UnmarshalJSON(b []byte) error {
j := assemblyIdentityJSON{}
if err := json.Unmarshal(b, &j); err != nil {
return err
}
ai.Name = j.Name
j.Version = strings.TrimSpace(j.Version)
if j.Version == "" {
return nil
}
v := strings.Split(j.Version, ".")
if len(v) > 4 {
return errors.New(errInvalidVersion)
}
for i := range v {
n, err := strconv.ParseUint(v[i], 10, 16)
if err != nil {
return errors.New(errInvalidVersion)
}
ai.Version[i] = uint16(n)
}
return nil
}
此差异已折叠。
package winres
type peCheckSum struct {
size uint32
sum uint32
rem uint32
}
// Sum returns the PE checksum of the PE file written with Write
func (c *peCheckSum) Sum() uint32 {
if c.size&1 != 0 {
return add16c(c.sum, c.rem) + c.size
}
return c.sum + c.size
}
// Write implements the Writer interface to compute the
// PE checksum of a PE file.
//
// That file must have its CheckSum field set to zero.
func (c *peCheckSum) Write(p []byte) (int, error) {
l := uint32(len(p))
o := c.size & 1
m := (l-o)&^1 + o
if o != 0 && l != 0 {
c.sum = add16c(c.sum, uint32(p[0])<<8|c.rem)
}
for i := o; i < m; i += 2 {
c.sum = add16c(c.sum, uint32(p[i+1])<<8|uint32(p[i]))
}
if m < l {
c.rem = uint32(p[m])
}
c.size += l
return len(p), nil
}
func add16c(a uint32, b uint32) uint32 {
a += b
return (a + (a >> 16)) & 0xFFFF
}
package winres
import "testing"
func Test_peCheckSum_Write(t *testing.T) {
w := peCheckSum{}
w.Write(nil)
w.Write([]byte{0x42})
w.Write([]byte{0xAF, 0xE7, 0x50})
if w.sum != 42 {
t.FailNow()
}
w.Write([]byte{0x10, 0xFF, 0xFF, 0xCD, 0x00, 0x50, 0xF0})
w.Write([]byte{0xFF, 0xFF, 0x01, 0x82, 0xBF, 0x51})
if w.sum != 0xDEAD || w.rem != 0x51 || w.Sum() != 0xDF0F {
t.FailNow()
}
w.Write([]byte{0})
if w.Sum() != 0xDF10 {
t.FailNow()
}
w.Write([]byte{0x10, 0x20, 0xF7})
if w.Sum() != 0x1B {
t.FailNow()
}
b := make([]byte, 0x12345)
for i := 0; i < 0x100; i++ {
w.Write(b)
}
if w.Sum() != 0x123451B {
t.FailNow()
}
}
package winres
import (
"encoding/binary"
"io"
)
// readFull is like io.ReadFull, except it always returns io.ErrUnexpectedEOF instead of io.EOF.
func readFull(r io.Reader, data []byte) error {
_, err := io.ReadFull(r, data)
if err == io.EOF {
return io.ErrUnexpectedEOF
}
return err
}
// binaryRead is like binary.Read, except it always returns io.ErrUnexpectedEOF instead of io.EOF.
// Furthermore, it always uses binary.LittleEndian.
func binaryRead(r io.Reader, v interface{}) error {
err := binary.Read(r, binary.LittleEndian, v)
if err == io.EOF {
return io.ErrUnexpectedEOF
}
return err
}
此差异已折叠。
package winres
import (
"bytes"
"io"
"testing"
)
func Test_ResourceSet_write_WriteErr(t *testing.T) {
rs := ResourceSet{}
rs.Set(Name("NAME"), Name("NAME"), 0, make([]byte, 6))
const writeErrMsg = "expected write error, got"
if _, err := rs.write(newBadWriter(15)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
if _, err := rs.write(newBadWriter(23)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
if _, err := rs.write(newBadWriter(39)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
if _, err := rs.write(newBadWriter(47)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
if _, err := rs.write(newBadWriter(63)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
if _, err := rs.write(newBadWriter(71)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
if _, err := rs.write(newBadWriter(87)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
if _, err := rs.write(newBadWriter(103)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
if _, err := rs.write(newBadWriter(111)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
rs.Set(ID(1), ID(1), 0, make([]byte, 6))
if _, err := rs.write(newBadWriter(31)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
if _, err := rs.write(newBadWriter(79)); !isExpectedWriteErr(err) {
t.Fatal(writeErrMsg, err)
}
}
func Test_dataEntry_writeData(t *testing.T) {
expected := [][]byte{
{},
{1, 0, 0, 0, 0, 0, 0, 0},
{1, 2, 0, 0, 0, 0, 0, 0},
{1, 2, 3, 0, 0, 0, 0, 0},
{1, 2, 3, 4, 0, 0, 0, 0},
{1, 2, 3, 4, 5, 0, 0, 0},
{1, 2, 3, 4, 5, 6, 0, 0},
{1, 2, 3, 4, 5, 6, 7, 0},
{1, 2, 3, 4, 5, 6, 7, 8},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 0, 0, 0, 0},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 0},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 0, 0},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 0, 0},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0},
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
}
for i := 0; i <= 16; i++ {
de := dataEntry{data: make([]byte, i)}
for j := 0; j < i; j++ {
de.data[j] = byte(j + 1)
}
buf := &bytes.Buffer{}
err := de.writeData(buf)
if err != nil || !bytes.Equal(buf.Bytes(), expected[i]) {
t.Fail()
}
err = de.writeData(newBadWriter(i - 1))
if !isExpectedWriteErr(err) {
t.Fatal("expected write error, got", err)
}
}
}
func TestResourceSet_read1(t *testing.T) {
rs := ResourceSet{}
err := rs.read(loadBinary(t, "rsrc1.bin"), 0xE6B000, ID(0))
if err != nil || rs.lastIconID != 12 || rs.lastCursorID != 5 {
t.Fatal(err)
}
buf := &bytes.Buffer{}
rs.write(buf)
checkBinary(t, buf.Bytes())
}
func TestResourceSet_read_RT_ICON(t *testing.T) {
rs := ResourceSet{}
err := rs.read(loadBinary(t, "rsrc1.bin"), 0xE6B000, RT_ICON)
if err != nil || rs.lastIconID != 12 || rs.lastCursorID != 0 {
t.Fatal(err)
}
buf := &bytes.Buffer{}
rs.write(buf)
checkBinary(t, buf.Bytes())
}
func TestResourceSet_read_RT_GROUP_ICON(t *testing.T) {
rs := ResourceSet{}
err := rs.read(loadBinary(t, "rsrc1.bin"), 0xE6B000, RT_GROUP_ICON)
if err != nil || rs.lastIconID != 12 || rs.lastCursorID != 0 {
t.Fatal(err)
}
buf := &bytes.Buffer{}
rs.write(buf)
checkBinary(t, buf.Bytes())
}
func TestResourceSet_read_RT_CURSOR(t *testing.T) {
rs := ResourceSet{}
err := rs.read(loadBinary(t, "rsrc1.bin"), 0xE6B000, RT_CURSOR)
if err != nil || rs.lastIconID != 0 || rs.lastCursorID != 5 {
t.Fatal(err)
}
buf := &bytes.Buffer{}
rs.write(buf)
checkBinary(t, buf.Bytes())
}
func TestResourceSet_read_RT_GROUP_CURSOR(t *testing.T) {
rs := ResourceSet{}
err := rs.read(loadBinary(t, "rsrc1.bin"), 0xE6B000, RT_GROUP_CURSOR)
if err != nil || rs.lastIconID != 0 || rs.lastCursorID != 5 {
t.Fatal(err)
}
buf := &bytes.Buffer{}
rs.write(buf)
checkBinary(t, buf.Bytes())
}
func TestResourceSet_read_PNG(t *testing.T) {
rs := ResourceSet{}
err := rs.read(loadBinary(t, "rsrc1.bin"), 0xE6B000, Name("PNG"))
if err != nil || rs.lastIconID != 0 || rs.lastCursorID != 0 {
t.Fatal(err)
}
buf := &bytes.Buffer{}
rs.write(buf)
checkBinary(t, buf.Bytes())
}
func TestResourceSet_read_EOF1(t *testing.T) {
rs := ResourceSet{}
err := rs.read([]byte{}, 0x42, ID(0))
if err != io.ErrUnexpectedEOF {
t.Fail()
}
}
func TestResourceSet_read_EOF2(t *testing.T) {
rs := ResourceSet{}
err := rs.read([]byte{0xC: 1, 0x16: 0}, 0x42, ID(0))
if err != io.ErrUnexpectedEOF {
t.Fail()
}
}
func TestResourceSet_read_ErrNode(t *testing.T) {
rs := ResourceSet{}
err := rs.read([]byte{0xC: 1, 0x13: 0x80, 0x17: 0}, 0x42, ID(0))
if err == nil || err.Error() != errInvalidResDir {
t.Fail()
}
}
func TestResourceSet_read_ErrLeaf(t *testing.T) {
rs := ResourceSet{}
err := rs.read([]byte{0xC: 1, 0x17: 0x80}, 0x42, ID(0))
if err == nil || err.Error() != errInvalidResDir {
t.Fail()
}
}
func TestResourceSet_read_ErrLeafName(t *testing.T) {
rs := ResourceSet{}
err := rs.read([]byte{
0xE: 1,
0x14: 0x18,
0x17: 0x80,
0x26: 1,
0x2C: 0x30,
0x2F: 0x80,
0x3E: 1,
0x43: 0x80,
0x44: 0x48,
0x47: 0,
}, 0x42, ID(0))
if err == nil || err.Error() != errInvalidResDir {
t.Fail()
}
}
func TestResourceSet_read_ErrEOF3(t *testing.T) {
rs := ResourceSet{}
err := rs.read([]byte{
0xC: 1,
0x10: 0xFF,
0x13: 0x80,
0x14: 0x18,
0x17: 0x80,
0x26: 1,
0x2C: 0x30,
0x2F: 0x80,
0x3E: 1,
0x44: 0x48,
0x47: 0,
}, 0x42, ID(0))
if err != io.ErrUnexpectedEOF {
t.Fail()
}
}
func TestResourceSet_read_ErrEOF4(t *testing.T) {
rs := ResourceSet{}
err := rs.read([]byte{
0xC: 1,
0x10: 0x44,
0x13: 0x80,
0x14: 0x18,
0x17: 0x80,
0x26: 1,
0x2C: 0x30,
0x2F: 0x80,
0x3E: 1,
0x44: 0x48,
0x47: 0,
}, 0x42, ID(0))
if err != io.ErrUnexpectedEOF {
t.Fail()
}
}
func TestResourceSet_read_ErrEOF5(t *testing.T) {
rs := ResourceSet{}
err := rs.read([]byte{
0xE: 1,
0x10: 0x01,
0x14: 0x18,
0x17: 0x80,
0x26: 1,
0x2C: 0x30,
0x2F: 0x80,
0x3E: 1,
0x44: 0x48,
0x47: 0,
}, 0x42, ID(0))
if err != io.ErrUnexpectedEOF {
t.Fail()
}
}
func TestResourceSet_read_DataOutOfBounds1(t *testing.T) {
rs := ResourceSet{}
err := rs.read([]byte{
0xE: 1,
0x10: 1,
0x14: 0x18,
0x17: 0x80,
0x26: 1,
0x2C: 0x30,
0x2F: 0x80,
0x3E: 1,
0x44: 0x48,
0x48: 0x58,
0x49: 0x10,
0x4C: 0x10,
0x5D: 0,
}, 0x1000, ID(0))
if err == nil || err.Error() != errDataEntryOutOfBounds {
t.Fail()
}
}
func TestResourceSet_read_DataOutOfBounds2(t *testing.T) {
rs := ResourceSet{}
err := rs.read(loadBinary(t, "rsrc1.bin"), 0x42, ID(0))
if err == nil || err.Error() != errDataEntryOutOfBounds {
t.Fail()
}
}
此差异已折叠。
package version
import (
"io"
"testing"
)
type eofReader struct {
remainder int
}
func (r *eofReader) Read(data []byte) (int, error) {
for i := range data {
if r.remainder <= 0 {
return i, io.EOF
}
data[i] = byte(i)
r.remainder--
}
return len(data), nil
}
func Test_binaryRead(t *testing.T) {
r := eofReader{3 * 4 * 5}
s := make([]uint32, 5)
for j := 0; j < 4; j++ {
err := binaryRead(&r, &s)
if (err == nil) != (j < 3) {
t.FailNow()
}
if err != nil && err != io.ErrUnexpectedEOF {
t.Fatal(err)
}
for i := range s {
if s[i] != uint32((i*4)|(i*4+1)<<8|(i*4+2)<<16|(i*4+3)<<24) {
t.FailNow()
}
}
}
}
package version
const (
errInvalidSignature = "invalid fixed file info signature"
errInvalidLength = "invalid length"
errInvalidLangID = "invalid language id"
errUnhandledCodePage = "unhandled code page"
errInvalidStringLength = "invalid string length"
errEmptyKey = "empty key"
errKeyContainsNUL = "invalid key contains NUL character"
errValueContainsNUL = "invalid value contains NUL character"
)
此差异已折叠。
此差异已折叠。
package version
import "time"
// Conversion of Windows Time Stamp
// Difference Unix epoch in Windows timestamp format.
const unixEpoch = 0x019DB1DED53E8000
// Windows timestamp is in "hecto-nanoseconds" (100 ns)
const tsUnitToNano = 100
func timeToTimestamp(t time.Time) (uint32, uint32) {
if t.IsZero() {
return 0, 0
}
ts := t.UnixNano()/tsUnitToNano + unixEpoch
return uint32(uint64(ts) >> 32), uint32(ts)
}
func timestampToTime(ms uint32, ls uint32) time.Time {
if ms == 0 && ls == 0 {
return time.Time{}
}
return time.Unix(0, int64((uint64(ms)<<32|uint64(ls))-unixEpoch)*tsUnitToNano)
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册