-
Notifications
You must be signed in to change notification settings - Fork 8
/
syso.go
163 lines (153 loc) · 4.37 KB
/
syso.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package syso
import (
"encoding/json"
"io"
"os"
"github.com/hallazzang/syso/pkg/coff"
"github.com/hallazzang/syso/pkg/common"
"github.com/hallazzang/syso/pkg/ico"
"github.com/hallazzang/syso/pkg/rsrc"
"github.com/pkg/errors"
)
// FileResource represents a file resource that can be found at Path.
type FileResource struct {
ID int
Name string
Path string
}
// Validate returns an error if the resource is invalid.
func (r *FileResource) Validate() error {
if r.Path == "" {
return errors.New("no file path given")
} else if r.ID == 0 && r.Name == "" {
return errors.New("neither id nor name given")
} else if r.ID != 0 && r.Name != "" {
return errors.New("id and name cannot be set together")
} else if r.ID < 0 {
return errors.Errorf("invalid id: %d", r.ID)
} else if r.Path == "" {
return errors.New("path should be set")
}
return nil
}
// Config is a syso config data.
type Config struct {
Icons []*FileResource
Manifest *FileResource
VersionInfos []*VersionInfoResource
}
// ParseConfig reads JSON-formatted syso config from r and returns Config object.
func ParseConfig(r io.Reader) (*Config, error) {
var c Config
if err := json.NewDecoder(r).Decode(&c); err != nil {
return nil, errors.Wrap(err, "failed to decode JSON")
}
for i, icon := range c.Icons {
if err := icon.Validate(); err != nil {
return nil, errors.Wrapf(err, "failed to validate icon #%d", i)
}
for j, icon2 := range c.Icons[:i] {
if icon.ID != 0 && icon2.ID != 0 && icon2.ID == icon.ID {
return nil, errors.Errorf("icon #%d's id and icon #%d's id are same", i, j)
} else if icon.Name != "" && icon2.Name != "" && icon2.Name == icon.Name {
return nil, errors.Errorf("icon #%d's name and icon #%d's name are same", i, j)
}
}
}
if c.Manifest != nil {
if err := c.Manifest.Validate(); err != nil {
return nil, errors.Wrap(err, "failed to validate manifest")
}
}
// TODO: validate version info resource
return &c, nil
}
// EmbedIcon embeds an icon into c.
func EmbedIcon(c *coff.File, icon *FileResource) error {
if err := icon.Validate(); err != nil {
return errors.Wrap(err, "invalid icon")
}
r, err := getOrCreateRSRCSection(c)
if err != nil {
return errors.Wrap(err, "failed to get or create .rsrc section")
}
f, err := os.Open(icon.Path)
if err != nil {
return errors.Wrap(err, "failed to open icon file")
}
defer f.Close()
icons, err := ico.DecodeAll(f)
if err != nil {
return errors.Wrap(err, "failed to decode icon file")
}
for i, img := range icons.Images {
img.ID = findPossibleID(r, 1000)
if err := r.AddResourceByID(rsrc.IconResource, img.ID, img); err != nil {
return errors.Wrapf(err, "failed to add icon image #%d", i)
}
}
if icon.ID != 0 {
err = r.AddResourceByID(rsrc.IconGroupResource, icon.ID, icons)
} else {
err = r.AddResourceByName(rsrc.IconGroupResource, icon.Name, icons)
}
if err != nil {
return errors.Wrap(err, "failed to add icon group resource")
}
return nil
}
// EmbedManifest embeds a manifest into c.
func EmbedManifest(c *coff.File, manifest *FileResource) error {
if err := manifest.Validate(); err != nil {
return errors.Wrap(err, "invalid manifest")
}
r, err := getOrCreateRSRCSection(c)
if err != nil {
return errors.Wrap(err, "failed to get or create .rsrc section")
}
f, err := os.Open(manifest.Path)
if err != nil {
return errors.Wrap(err, "failed to open manifest file")
}
defer f.Close()
b, err := common.NewBlob(f)
if err != nil {
return err
}
if manifest.ID != 0 {
err = r.AddResourceByID(rsrc.ManifestResource, manifest.ID, b)
} else {
err = r.AddResourceByName(rsrc.ManifestResource, manifest.Name, b)
}
if err != nil {
return errors.Wrap(err, "failed to add manifest resource")
}
return nil
}
func getOrCreateRSRCSection(c *coff.File) (*rsrc.Section, error) {
s, err := c.Section(".rsrc")
if err != nil {
if err == coff.ErrSectionNotFound {
s = rsrc.New()
if err := c.AddSection(s); err != nil {
return nil, errors.New("failed to add new .rsrc section")
}
} else {
return nil, errors.Wrap(err, "failed to get .rsrc section")
}
}
r, ok := s.(*rsrc.Section)
if !ok {
return nil, errors.New("the .rsrc section is not a valid rsrc section")
}
return r, nil
}
func findPossibleID(r *rsrc.Section, from int) int {
// TODO: is 65535 a good limit for resource id?
for ; from < 65536; from++ {
if !r.ResourceIDExists(from) {
break
}
}
return from
}