mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-03 16:35:36 +00:00
cmd/gogio: remove windres dependency
Now, it's possible to compile to Windows (`-target windows`) without having `windres`. The PNG icon, manifest and version info will be generated and include using `gogio`. Signed-off-by: Inkeliz <inkeliz@inkeliz.com>
This commit is contained in:
@@ -4,9 +4,11 @@ go 1.13
|
||||
|
||||
require (
|
||||
gioui.org v0.0.0-20201206220230-a87a520ae825
|
||||
github.com/akavel/rsrc v0.10.1
|
||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4
|
||||
github.com/chromedp/chromedp v0.5.2
|
||||
golang.org/x/image v0.0.0-20200618115811-c13761719519
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||
golang.org/x/text v0.3.0
|
||||
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e
|
||||
)
|
||||
|
||||
@@ -2,6 +2,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
|
||||
gioui.org v0.0.0-20201206220230-a87a520ae825 h1:8eQeFlQ0IL5sOX74YcwEBk3OtGNTRCqIU3Rz0z0U6vE=
|
||||
gioui.org v0.0.0-20201206220230-a87a520ae825/go.mod h1:Y+uS7hHMvku1Q+ooaoq6fYD5B2LGoT8JtFgvmYmRzTw=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/akavel/rsrc v0.10.1 h1:hCCPImjmFKVNGpeLZyTDRHEFC283DzyTXTo0cO0Rq9o=
|
||||
github.com/akavel/rsrc v0.10.1/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4 h1:QD3KxSJ59L2lxG6MXBjNHxiQO2RmxTQ3XcK+wO44WOg=
|
||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
|
||||
github.com/chromedp/chromedp v0.5.2 h1:W8xBXQuUnd2dZK0SN/lyVwsQM7KgW+kY5HGnntms194=
|
||||
@@ -37,6 +39,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e h1:1xWUkZQQ9Z9UuZgNaIR6OQOE7rUFglXUUBZlO+dGg6I=
|
||||
golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
||||
+262
-167
@@ -4,20 +4,23 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"image"
|
||||
"github.com/akavel/rsrc/binutil"
|
||||
"github.com/akavel/rsrc/coff"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"image/png"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
func buildWindows(tmpDir string, bi *buildInfo) error {
|
||||
builder := &windowsBuilder{TempDir: tmpDir, BuildInfo: bi}
|
||||
builder := &windowsBuilder{TempDir: tmpDir}
|
||||
builder.DestDir = *destPath
|
||||
if builder.DestDir == "" {
|
||||
builder.DestDir = bi.pkgPath
|
||||
@@ -40,29 +43,36 @@ func buildWindows(tmpDir string, bi *buildInfo) error {
|
||||
return fmt.Errorf("version (%d) is larger than the maximum (%d)", bi.version, math.MaxUint16)
|
||||
}
|
||||
|
||||
builder.Resources.Name = name
|
||||
builder.Manifest.Name = name
|
||||
builder.Manifest.WindowsVersion = sdk
|
||||
builder.Resources.Version = "1,0,0," + version
|
||||
builder.Manifest.Version = "1.0.0." + version
|
||||
for _, arch := range bi.archs {
|
||||
builder.Coff = coff.NewRSRC()
|
||||
builder.Coff.Arch(arch)
|
||||
|
||||
if err := builder.createIcon(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := builder.embedIcon(bi.iconPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := builder.createManifest(); err != nil {
|
||||
return fmt.Errorf("can't create manifest: %v", err)
|
||||
}
|
||||
if err := builder.createResource(); err != nil {
|
||||
return fmt.Errorf("can't create resource: %v", err)
|
||||
}
|
||||
if err := builder.embedManifest(windowsManifest{
|
||||
Version: "1.0.0." + version,
|
||||
WindowsVersion: sdk,
|
||||
Name: name,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("can't create manifest: %v", err)
|
||||
}
|
||||
|
||||
if err := builder.buildResource(); err != nil {
|
||||
return fmt.Errorf("can't build the resources: %v", err)
|
||||
}
|
||||
if err := builder.embedInfo(windowsResources{
|
||||
Version: [2]uint32{uint32(1) << 16, uint32(bi.version)},
|
||||
VersionHuman: "1.0.0." + version,
|
||||
Name: name,
|
||||
Language: 0x0400, // Process Default Language: https://docs.microsoft.com/en-us/previous-versions/ms957130(v=msdn.10)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("can't create info: %v", err)
|
||||
}
|
||||
|
||||
for _, arch := range builder.BuildInfo.archs {
|
||||
if err := builder.buildProgram(arch); err != nil {
|
||||
if err := builder.buildResource(bi, name, arch); err != nil {
|
||||
return fmt.Errorf("can't build the resources: %v", err)
|
||||
}
|
||||
|
||||
if err := builder.buildProgram(bi, name, arch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -72,157 +82,134 @@ func buildWindows(tmpDir string, bi *buildInfo) error {
|
||||
|
||||
type (
|
||||
windowsResources struct {
|
||||
IconPath string
|
||||
ManifestPath string
|
||||
Version string
|
||||
Version [2]uint32
|
||||
VersionHuman string
|
||||
Language uint16
|
||||
Name string
|
||||
CompanyName string
|
||||
}
|
||||
windowsManifest struct {
|
||||
Version string
|
||||
WindowsVersion int
|
||||
Name string
|
||||
Arch string
|
||||
}
|
||||
windowsFiles struct {
|
||||
Resources windowsResources
|
||||
ResourcesPath string
|
||||
Manifest windowsManifest
|
||||
windowsBuilder struct {
|
||||
TempDir string
|
||||
DestDir string
|
||||
Coff *coff.Coff
|
||||
}
|
||||
)
|
||||
|
||||
type windowsBuilder struct {
|
||||
TempDir string
|
||||
DestDir string
|
||||
BuildInfo *buildInfo
|
||||
windowsFiles
|
||||
const (
|
||||
// https://docs.microsoft.com/en-us/windows/win32/menurc/resource-types
|
||||
windowsResourceIcon = 3
|
||||
windowsResourceIconGroup = windowsResourceIcon + 11
|
||||
windowsResourceManifest = 24
|
||||
windowsResourceVersion = 16
|
||||
)
|
||||
|
||||
type bufferCoff struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *windowsBuilder) createIcon() (err error) {
|
||||
if _, err := os.Stat(b.BuildInfo.iconPath); err != nil {
|
||||
return nil
|
||||
}
|
||||
func (b *bufferCoff) Size() int64 {
|
||||
return int64(b.Len())
|
||||
}
|
||||
|
||||
iconFile, err := os.Open(b.BuildInfo.iconPath)
|
||||
func (b *windowsBuilder) embedIcon(path string) (err error) {
|
||||
iconFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't read the icon located at %s: %v", b.BuildInfo.iconPath, err)
|
||||
return fmt.Errorf("can't read the icon located at %s: %v", path, err)
|
||||
}
|
||||
defer iconFile.Close()
|
||||
|
||||
iconImage, err := png.Decode(iconFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't decode the PNG file (%s): %v", b.BuildInfo.iconPath, err)
|
||||
return fmt.Errorf("can't decode the PNG file (%s): %v", path, err)
|
||||
}
|
||||
|
||||
b.Resources.IconPath = filepath.Join(b.TempDir, "appicon.ico")
|
||||
exeIcon, err := os.Create(b.Resources.IconPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("impossibe to create icon file at %s: %v", b.Resources.IconPath, err)
|
||||
}
|
||||
defer exeIcon.Close()
|
||||
|
||||
return convertPNGtoICO(exeIcon, iconImage)
|
||||
}
|
||||
|
||||
func convertPNGtoICO(w io.Writer, img image.Image) error {
|
||||
// The file must be in .ICO format.
|
||||
const (
|
||||
OffsetICONDIR int = 2 * 3
|
||||
OffsetICONDIRENTRY int = (4 * 1) + (2 * 2) + (4 * 2)
|
||||
)
|
||||
|
||||
sizes := []int{16, 32, 48, 64, 128, 256}
|
||||
var iconHeader bufferCoff
|
||||
|
||||
// ICONDIR structure
|
||||
if err := binary.Write(w, binary.LittleEndian, [3]uint16{0, 1, uint16(len(sizes))}); err != nil {
|
||||
// GRPICONDIR structure.
|
||||
if err := binary.Write(&iconHeader, binary.LittleEndian, [3]uint16{0, 1, uint16(len(sizes))}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
headerOffset = OffsetICONDIR + (OffsetICONDIRENTRY * len(sizes))
|
||||
imageBuffer bytes.Buffer
|
||||
)
|
||||
for _, size := range sizes {
|
||||
imageOffset := imageBuffer.Len()
|
||||
scaledImage := resizeIcon(iconVariant{size: size, fill: false}, img)
|
||||
var iconBuffer bufferCoff
|
||||
|
||||
if err := png.Encode(&imageBuffer, scaledImage); err != nil {
|
||||
if err := png.Encode(&iconBuffer, resizeIcon(iconVariant{size: size, fill: false}, iconImage)); err != nil {
|
||||
return fmt.Errorf("can't encode image: %v", err)
|
||||
}
|
||||
|
||||
// ICONDIRENTRY 0-3 structure.
|
||||
// The width/height is defined from 0 to 255 (uint8). But "0" means 256px.
|
||||
if err := binary.Write(w, binary.LittleEndian, [4]uint8{uint8(size % 256), uint8(size % 256), 0, 0}); err != nil {
|
||||
return err
|
||||
}
|
||||
// ICONDIRENTRY 4-6 structure
|
||||
if err := binary.Write(w, binary.LittleEndian, [2]uint16{1, 32}); err != nil {
|
||||
return err
|
||||
}
|
||||
// ICONDIRENTRY 8-12 structure
|
||||
if err := binary.Write(w, binary.LittleEndian, [2]uint32{uint32(imageBuffer.Len() - imageOffset), uint32(headerOffset + imageOffset)}); err != nil {
|
||||
b.Coff.AddResource(windowsResourceIcon, uint16(size), &iconBuffer)
|
||||
|
||||
if err := binary.Write(&iconHeader, binary.LittleEndian, struct {
|
||||
Size [2]uint8
|
||||
Color [2]uint8
|
||||
Planes uint16
|
||||
BitCount uint16
|
||||
Length uint32
|
||||
Id uint16
|
||||
}{
|
||||
Size: [2]uint8{uint8(size % 256), uint8(size % 256)}, // "0" means 256px.
|
||||
Planes: 1,
|
||||
BitCount: 32,
|
||||
Length: uint32(iconBuffer.Len()),
|
||||
Id: uint16(size),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err := io.Copy(w, &imageBuffer)
|
||||
b.Coff.AddResource(windowsResourceIconGroup, 1, &iconHeader)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *windowsBuilder) buildResource(buildInfo *buildInfo, name string, arch string) error {
|
||||
out, err := os.Create(filepath.Join(buildInfo.pkgPath, name+"_windows_"+arch+".syso"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
b.Coff.Freeze()
|
||||
|
||||
// See https://github.com/akavel/rsrc/internal/write.go#L13.
|
||||
w := binutil.Writer{W: out}
|
||||
binutil.Walk(b.Coff, func(v reflect.Value, path string) error {
|
||||
if binutil.Plain(v.Kind()) {
|
||||
w.WriteLE(v.Interface())
|
||||
return nil
|
||||
}
|
||||
vv, ok := v.Interface().(binutil.SizedReader)
|
||||
if ok {
|
||||
w.WriteFromSized(vv)
|
||||
return binutil.WALK_SKIP
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if w.Err != nil {
|
||||
return fmt.Errorf("error writing output file: %s", w.Err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *windowsBuilder) createManifest() error {
|
||||
// The manifest have some information about the executable itself,
|
||||
// such as the supported Windows and Execution Level/Permissions.
|
||||
b.Resources.ManifestPath = filepath.Join(b.TempDir, "manifest_windows.xml")
|
||||
manifest, err := os.Create(b.Resources.ManifestPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer manifest.Close()
|
||||
|
||||
return b.Manifest.encode(manifest)
|
||||
}
|
||||
|
||||
func (b *windowsBuilder) createResource() error {
|
||||
// The resource includes the icon and manifest previously created
|
||||
// it also defines the version and some other information about the
|
||||
// program and the developer.
|
||||
b.ResourcesPath = filepath.Join(b.TempDir, "main_windows.rc")
|
||||
resources, err := os.Create(b.ResourcesPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resources.Close()
|
||||
|
||||
return b.Resources.encode(resources)
|
||||
}
|
||||
|
||||
func (b *windowsBuilder) buildResource() error {
|
||||
cmd := exec.Command(
|
||||
"windres",
|
||||
b.ResourcesPath,
|
||||
filepath.Join(b.BuildInfo.pkgPath, "main_windows.syso"),
|
||||
)
|
||||
_, err := runCmd(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *windowsBuilder) buildProgram(arch string) error {
|
||||
func (b *windowsBuilder) buildProgram(buildInfo *buildInfo, name string, arch string) error {
|
||||
dest := b.DestDir
|
||||
if len(b.BuildInfo.archs) > 1 {
|
||||
dest = filepath.Join(filepath.Dir(b.DestDir), b.Resources.Name+"_"+arch+".exe")
|
||||
if len(buildInfo.archs) > 1 {
|
||||
dest = filepath.Join(filepath.Dir(b.DestDir), name+"_"+arch+".exe")
|
||||
}
|
||||
|
||||
cmd := exec.Command(
|
||||
"go",
|
||||
"build",
|
||||
"-ldflags=-H=windowsgui "+b.BuildInfo.ldflags,
|
||||
"-tags="+b.BuildInfo.tags,
|
||||
"-ldflags=-H=windowsgui "+buildInfo.ldflags,
|
||||
"-tags="+buildInfo.tags,
|
||||
"-o", dest,
|
||||
b.BuildInfo.pkgPath,
|
||||
buildInfo.pkgPath,
|
||||
)
|
||||
cmd.Env = append(
|
||||
os.Environ(),
|
||||
@@ -233,8 +220,8 @@ func (b *windowsBuilder) buildProgram(arch string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *windowsManifest) encode(w io.Writer) error {
|
||||
t := `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
func (b *windowsBuilder) embedManifest(v windowsManifest) error {
|
||||
t, err := template.New("manifest").Parse(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<assemblyIdentity type="win32" name="{{.Name}}" version="{{.Version}}" />
|
||||
<description>{{.Name}}</description>
|
||||
@@ -264,53 +251,161 @@ func (f *windowsManifest) encode(w io.Writer) error {
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>`
|
||||
template, err := template.New("manifest").Parse(t)
|
||||
</assembly>`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return template.Execute(w, f)
|
||||
}
|
||||
|
||||
func (f *windowsResources) encode(w io.Writer) error {
|
||||
const t = `{{if .IconPath}}#define IDI_ICON1 1
|
||||
IDI_ICON1 ICON "{{escapePath .IconPath}}"{{end}}
|
||||
|
||||
#define IDI_MANIFEST 1
|
||||
IDI_MANIFEST 24 "{{escapePath .ManifestPath}}"
|
||||
|
||||
#define IDI_VERSION 1
|
||||
IDI_VERSION VERSIONINFO
|
||||
FILEVERSION {{.Version}}
|
||||
PRODUCTVERSION {{.Version}}
|
||||
FILEFLAGSMASK 0X3FL
|
||||
FILEFLAGS 0x0L
|
||||
FILEOS 0X40004L
|
||||
FILETYPE 0X1L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "04000400"
|
||||
BEGIN
|
||||
VALUE "ProductVersion", "{{.Version}}"
|
||||
VALUE "FileVersion", "{{.Version}}"
|
||||
VALUE "FileDescription", "{{.Name}}"
|
||||
VALUE "ProductName", "{{.Name}}"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x0400, 0x0400
|
||||
END
|
||||
END`
|
||||
template, err := template.New("rc").Funcs(template.FuncMap{"escapePath": func(s string) string {
|
||||
return strings.Replace(s, `\`, `\\`, -1)
|
||||
}}).Parse(t)
|
||||
if err != nil {
|
||||
var manifest bufferCoff
|
||||
if err := t.Execute(&manifest, v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return template.Execute(w, f)
|
||||
b.Coff.AddResource(windowsResourceManifest, 1, &manifest)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *windowsBuilder) embedInfo(v windowsResources) error {
|
||||
page := uint16(1)
|
||||
|
||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/vs-versioninfo
|
||||
t := newValue(valueBinary, "VS_VERSION_INFO", []io.WriterTo{
|
||||
// https://docs.microsoft.com/pt-br/windows/win32/api/VerRsrc/ns-verrsrc-vs_fixedfileinfo
|
||||
windowsInfoValueFixed{
|
||||
Signature: 0xFEEF04BD,
|
||||
StructVersion: 0x00010000,
|
||||
FileVersion: v.Version,
|
||||
ProductVersion: v.Version,
|
||||
FileFlagMask: 0x3F,
|
||||
FileFlags: 0,
|
||||
FileOS: 0x40004,
|
||||
FileType: 0x1,
|
||||
FileSubType: 0,
|
||||
},
|
||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/stringfileinfo
|
||||
newValue(valueText, "StringFileInfo", []io.WriterTo{
|
||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/stringtable
|
||||
newValue(valueText, fmt.Sprintf("%04X%04X", v.Language, page), []io.WriterTo{
|
||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/string-str
|
||||
newValue(valueText, "ProductVersion", v.VersionHuman),
|
||||
newValue(valueText, "FileVersion", v.VersionHuman),
|
||||
newValue(valueText, "FileDescription", v.Name),
|
||||
newValue(valueText, "ProductName", v.Name),
|
||||
// TODO include more data: gogio must have some way to provide such information (like Company Name, Copyright...)
|
||||
}),
|
||||
}),
|
||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/varfileinfo
|
||||
newValue(valueBinary, "VarFileInfo", []io.WriterTo{
|
||||
// https://docs.microsoft.com/pt-br/windows/win32/menurc/var-str
|
||||
newValue(valueBinary, "Translation", uint32(page)<<16|uint32(v.Language)),
|
||||
}),
|
||||
})
|
||||
|
||||
// For some reason the ValueLength of the VS_VERSIONINFO must be the byte-length of `windowsInfoValueFixed`:
|
||||
t.ValueLength = 52
|
||||
|
||||
var verrsrc bufferCoff
|
||||
if _, err := t.WriteTo(&verrsrc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.Coff.AddResource(windowsResourceVersion, 1, &verrsrc)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type windowsInfoValueFixed struct {
|
||||
Signature uint32
|
||||
StructVersion uint32
|
||||
FileVersion [2]uint32
|
||||
ProductVersion [2]uint32
|
||||
FileFlagMask uint32
|
||||
FileFlags uint32
|
||||
FileOS uint32
|
||||
FileType uint32
|
||||
FileSubType uint32
|
||||
FileDate [2]uint32
|
||||
}
|
||||
|
||||
func (v windowsInfoValueFixed) WriteTo(w io.Writer) (_ int64, err error) {
|
||||
return 0, binary.Write(w, binary.LittleEndian, v)
|
||||
}
|
||||
|
||||
type windowsInfoValue struct {
|
||||
Length uint16
|
||||
ValueLength uint16
|
||||
Type uint16
|
||||
Key []byte
|
||||
Value []byte
|
||||
}
|
||||
|
||||
func (v windowsInfoValue) WriteTo(w io.Writer) (_ int64, err error) {
|
||||
// binary.Write doesn't support []byte inside struct.
|
||||
if err = binary.Write(w, binary.LittleEndian, [3]uint16{v.Length, v.ValueLength, v.Type}); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err = w.Write(v.Key); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err = w.Write(v.Value); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
const (
|
||||
valueBinary uint16 = 0
|
||||
valueText uint16 = 1
|
||||
)
|
||||
|
||||
func newValue(valueType uint16, key string, input interface{}) windowsInfoValue {
|
||||
v := windowsInfoValue{
|
||||
Type: valueType,
|
||||
Length: 6,
|
||||
}
|
||||
|
||||
padding := func(in []byte) []byte {
|
||||
if l := uint16(len(in)) + v.Length; l%4 != 0 {
|
||||
return append(in, make([]byte, 4-l%4)...)
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
v.Key = padding(utf16Encode(key))
|
||||
v.Length += uint16(len(v.Key))
|
||||
|
||||
switch in := input.(type) {
|
||||
case string:
|
||||
v.Value = padding(utf16Encode(in))
|
||||
v.ValueLength = uint16(len(v.Value) / 2)
|
||||
case []io.WriterTo:
|
||||
var buff bytes.Buffer
|
||||
for k := range in {
|
||||
if _, err := in[k].WriteTo(&buff); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
v.Value = buff.Bytes()
|
||||
default:
|
||||
var buff bytes.Buffer
|
||||
if err := binary.Write(&buff, binary.LittleEndian, in); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
v.ValueLength = uint16(buff.Len())
|
||||
v.Value = buff.Bytes()
|
||||
}
|
||||
|
||||
v.Length += uint16(len(v.Value))
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// utf16Encode encodes the string to UTF16 with null-termination.
|
||||
func utf16Encode(s string) []byte {
|
||||
b, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder().Bytes([]byte(s))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return append(b, 0x00, 0x00) // null-termination.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user