1package coff
2
3import (
4	"debug/pe"
5	"encoding/binary"
6	"errors"
7	"io"
8	"reflect"
9	"regexp"
10	"sort"
11	"strconv"
12	"strings"
13
14	"github.com/akavel/rsrc/binutil"
15)
16
17type Dir struct { // struct IMAGE_RESOURCE_DIRECTORY
18	Characteristics      uint32
19	TimeDateStamp        uint32
20	MajorVersion         uint16
21	MinorVersion         uint16
22	NumberOfNamedEntries uint16
23	NumberOfIdEntries    uint16
24	DirEntries
25	Dirs
26}
27
28type DirEntries []DirEntry
29type Dirs []Dir
30
31type DirEntry struct { // struct IMAGE_RESOURCE_DIRECTORY_ENTRY
32	NameOrId     uint32
33	OffsetToData uint32
34}
35
36type DataEntry struct { // struct IMAGE_RESOURCE_DATA_ENTRY
37	OffsetToData uint32
38	Size1        uint32
39	CodePage     uint32 //FIXME: what value here? for now just using 0
40	Reserved     uint32
41}
42
43type RelocationEntry struct {
44	RVA         uint32 // "offset within the Section's raw data where the address starts."
45	SymbolIndex uint32 // "(zero based) index in the Symbol table to which the reference refers."
46	Type        uint16
47}
48
49// Values reverse-engineered from windres output; names from teh Internets.
50// Teh googlies Internets don't seem to have much to say about the AMD64 one,
51// unfortunately :/ but it works...
52const (
53	_IMAGE_REL_AMD64_ADDR32NB = 0x03
54	_IMAGE_REL_I386_DIR32NB   = 0x07
55)
56
57type Auxiliary [18]byte
58
59type Symbol struct {
60	Name           [8]byte
61	Value          uint32
62	SectionNumber  uint16
63	Type           uint16
64	StorageClass   uint8
65	AuxiliaryCount uint8
66	Auxiliaries    []Auxiliary
67}
68
69type StringsHeader struct {
70	Length uint32
71}
72
73const (
74	MASK_SUBDIRECTORY = 1 << 31
75
76	RT_ICON       = 3
77	RT_GROUP_ICON = 3 + 11
78	RT_MANIFEST   = 24
79)
80
81// http://www.delorie.com/djgpp/doc/coff/symtab.html
82const (
83	DT_PTR  = 1
84	T_UCHAR = 12
85)
86
87var (
88	STRING_RSRC  = [8]byte{'.', 'r', 's', 'r', 'c', 0, 0, 0}
89	STRING_RDATA = [8]byte{'.', 'r', 'd', 'a', 't', 'a', 0, 0}
90
91	LANG_ENTRY = DirEntry{NameOrId: 0x0409} //FIXME: language; what value should be here?
92)
93
94type Sizer interface {
95	Size() int64 //NOTE: must not exceed limits of uint32, or behavior is undefined
96}
97
98type Coff struct {
99	pe.FileHeader
100	pe.SectionHeader32
101
102	*Dir
103	DataEntries []DataEntry
104	Data        []Sizer
105
106	Relocations []RelocationEntry
107	Symbols     []Symbol
108	StringsHeader
109	Strings []Sizer
110}
111
112func NewRDATA() *Coff {
113	return &Coff{
114		pe.FileHeader{
115			Machine:              pe.IMAGE_FILE_MACHINE_I386,
116			NumberOfSections:     1, // .data
117			TimeDateStamp:        0,
118			NumberOfSymbols:      2, // starting only with '.rdata', will increase; must include auxiliaries, apparently
119			SizeOfOptionalHeader: 0,
120			Characteristics:      0x0105, //http://www.delorie.com/djgpp/doc/coff/filhdr.html
121		},
122		pe.SectionHeader32{
123			Name:            STRING_RDATA,
124			Characteristics: 0x40000040, // "INITIALIZED_DATA MEM_READ" ?
125		},
126
127		// "directory hierarchy" of .rsrc section; empty for .data function
128		nil,
129		[]DataEntry{},
130
131		[]Sizer{},
132
133		[]RelocationEntry{},
134
135		[]Symbol{Symbol{
136			Name:           STRING_RDATA,
137			Value:          0,
138			SectionNumber:  1,
139			Type:           0, // FIXME: wtf?
140			StorageClass:   3, // FIXME: is it ok? and uint8? and what does the value mean?
141			AuxiliaryCount: 1,
142			Auxiliaries:    []Auxiliary{{}}, //http://www6.cptec.inpe.br/sx4/sx4man2/g1af01e/chap5.html
143		}},
144
145		StringsHeader{
146			Length: uint32(binary.Size(StringsHeader{})), // empty strings table for now -- but we must still show size of the table's header...
147		},
148		[]Sizer{},
149	}
150}
151
152// NOTE: must be called immediately after NewRSRC, before any other
153// functions.
154func (coff *Coff) Arch(arch string) error {
155	switch arch {
156	case "386":
157		coff.Machine = pe.IMAGE_FILE_MACHINE_I386
158	case "amd64":
159		// Sources:
160		// https://github.com/golang/go/blob/0e23ca41d99c82d301badf1b762888e2c69e6c57/src/debug/pe/pe.go#L116
161		// https://github.com/yasm/yasm/blob/7160679eee91323db98b0974596c7221eeff772c/modules/objfmts/coff/coff-objfmt.c#L38
162		// FIXME: currently experimental -- not sure if something more doesn't need to be changed
163		coff.Machine = pe.IMAGE_FILE_MACHINE_AMD64
164	default:
165		return errors.New("coff: unknown architecture: " + arch)
166	}
167	return nil
168}
169
170//NOTE: only usable for Coff created using NewRDATA
171//NOTE: symbol names must be probably >8 characters long
172//NOTE: symbol names should not contain embedded zeroes
173func (coff *Coff) AddData(symbol string, data Sizer) {
174	coff.addSymbol(symbol)
175	coff.Data = append(coff.Data, data)
176	coff.SectionHeader32.SizeOfRawData += uint32(data.Size())
177}
178
179// addSymbol appends a symbol to Coff.Symbols and to Coff.Strings.
180//NOTE: symbol s must be probably >8 characters long
181//NOTE: symbol s should not contain embedded zeroes
182func (coff *Coff) addSymbol(s string) {
183	coff.FileHeader.NumberOfSymbols++
184
185	buf := strings.NewReader(s + "\000") // ASCIIZ
186	r := io.NewSectionReader(buf, 0, int64(len(s)+1))
187	coff.Strings = append(coff.Strings, r)
188
189	coff.StringsHeader.Length += uint32(r.Size())
190
191	coff.Symbols = append(coff.Symbols, Symbol{
192		//Name: // will be filled in Freeze
193		//Value: // as above
194		SectionNumber:  1,
195		Type:           0, // why 0??? // DT_PTR<<4 | T_UCHAR, // unsigned char* // (?) or use void* ? T_VOID=1
196		StorageClass:   2, // 2=C_EXT, or 5=C_EXTDEF ?
197		AuxiliaryCount: 0,
198	})
199}
200
201func NewRSRC() *Coff {
202	return &Coff{
203		pe.FileHeader{
204			Machine:              pe.IMAGE_FILE_MACHINE_I386,
205			NumberOfSections:     1, // .rsrc
206			TimeDateStamp:        0, // was also 0 in sample data from MinGW's windres.exe
207			NumberOfSymbols:      1,
208			SizeOfOptionalHeader: 0,
209			Characteristics:      0x0104, //FIXME: copied from windres.exe output, find out what should be here and why
210		},
211		pe.SectionHeader32{
212			Name:            STRING_RSRC,
213			Characteristics: 0x40000040, // "INITIALIZED_DATA MEM_READ" ?
214		},
215
216		// "directory hierarchy" of .rsrc section: top level goes resource type, then id/name, then language
217		&Dir{},
218
219		[]DataEntry{},
220		[]Sizer{},
221
222		[]RelocationEntry{},
223
224		[]Symbol{Symbol{
225			Name:           STRING_RSRC,
226			Value:          0,
227			SectionNumber:  1,
228			Type:           0, // FIXME: wtf?
229			StorageClass:   3, // FIXME: is it ok? and uint8? and what does the value mean?
230			AuxiliaryCount: 0, // FIXME: wtf?
231		}},
232
233		StringsHeader{
234			Length: uint32(binary.Size(StringsHeader{})), // empty strings table -- but we must still show size of the table's header...
235		},
236		[]Sizer{},
237	}
238}
239
240//NOTE: function assumes that 'id' is increasing on each entry
241//NOTE: only usable for Coff created using NewRSRC
242func (coff *Coff) AddResource(kind uint32, id uint16, data Sizer) {
243	re := RelocationEntry{
244		// "(zero based) index in the Symbol table to which the
245		// reference refers.  Once you have loaded the COFF file into
246		// memory and know where each symbol is, you find the new
247		// updated address for the given symbol and update the
248		// reference accordingly."
249		SymbolIndex: 0,
250	}
251	switch coff.Machine {
252	case pe.IMAGE_FILE_MACHINE_I386:
253		re.Type = _IMAGE_REL_I386_DIR32NB
254	case pe.IMAGE_FILE_MACHINE_AMD64:
255		re.Type = _IMAGE_REL_AMD64_ADDR32NB
256	}
257	coff.Relocations = append(coff.Relocations, re)
258	coff.SectionHeader32.NumberOfRelocations++
259
260	// find top level entry, inserting new if necessary at correct sorted position
261	entries0 := coff.Dir.DirEntries
262	dirs0 := coff.Dir.Dirs
263	i0 := sort.Search(len(entries0), func(i int) bool {
264		return entries0[i].NameOrId >= kind
265	})
266	if i0 >= len(entries0) || entries0[i0].NameOrId != kind {
267		// inserting new entry & dir
268		entries0 = append(entries0[:i0], append([]DirEntry{{NameOrId: kind}}, entries0[i0:]...)...)
269		dirs0 = append(dirs0[:i0], append([]Dir{{}}, dirs0[i0:]...)...)
270		coff.Dir.NumberOfIdEntries++
271	}
272	coff.Dir.DirEntries = entries0
273	coff.Dir.Dirs = dirs0
274
275	// for second level, assume ID is always increasing, so we don't have to sort
276	dirs0[i0].DirEntries = append(dirs0[i0].DirEntries, DirEntry{NameOrId: uint32(id)})
277	dirs0[i0].Dirs = append(dirs0[i0].Dirs, Dir{
278		NumberOfIdEntries: 1,
279		DirEntries:        DirEntries{LANG_ENTRY},
280	})
281	dirs0[i0].NumberOfIdEntries++
282
283	// calculate preceding DirEntry leaves, to find new index in Data & DataEntries
284	n := 0
285	for _, dir0 := range dirs0[:i0+1] {
286		n += len(dir0.DirEntries) //NOTE: assuming 1 language here; TODO: dwell deeper if more langs added
287	}
288	n--
289
290	// insert new data in correct place
291	coff.DataEntries = append(coff.DataEntries[:n], append([]DataEntry{{Size1: uint32(data.Size())}}, coff.DataEntries[n:]...)...)
292	coff.Data = append(coff.Data[:n], append([]Sizer{data}, coff.Data[n:]...)...)
293}
294
295// Freeze fills in some important offsets in resulting file.
296func (coff *Coff) Freeze() {
297	switch coff.SectionHeader32.Name {
298	case STRING_RSRC:
299		coff.freezeRSRC()
300	case STRING_RDATA:
301		coff.freezeRDATA()
302	}
303}
304
305func (coff *Coff) freezeCommon1(path string, offset, diroff uint32) (newdiroff uint32) {
306	switch path {
307	case "/Dir":
308		coff.SectionHeader32.PointerToRawData = offset
309		diroff = offset
310	case "/Relocations":
311		coff.SectionHeader32.PointerToRelocations = offset
312		coff.SectionHeader32.SizeOfRawData = offset - diroff
313	case "/Symbols":
314		coff.FileHeader.PointerToSymbolTable = offset
315	}
316	return diroff
317}
318
319func freezeCommon2(v reflect.Value, offset *uint32) error {
320	if binutil.Plain(v.Kind()) {
321		*offset += uint32(binary.Size(v.Interface())) // TODO: change to v.Type().Size() ?
322		return nil
323	}
324	vv, ok := v.Interface().(Sizer)
325	if ok {
326		*offset += uint32(vv.Size())
327		return binutil.WALK_SKIP
328	}
329	return nil
330}
331
332func (coff *Coff) freezeRDATA() {
333	var offset, diroff, stringsoff uint32
334	binutil.Walk(coff, func(v reflect.Value, path string) error {
335		diroff = coff.freezeCommon1(path, offset, diroff)
336
337		RE := regexp.MustCompile
338		const N = `\[(\d+)\]`
339		m := matcher{}
340		//TODO: adjust symbol pointers
341		//TODO: fill Symbols.Name, .Value
342		switch {
343		case m.Find(path, RE("^/Data"+N+"$")):
344			n := m[0]
345			coff.Symbols[1+n].Value = offset - diroff // FIXME: is it ok?
346			sz := uint64(coff.Data[n].Size())
347			binary.LittleEndian.PutUint64(coff.Symbols[0].Auxiliaries[0][0:8], binary.LittleEndian.Uint64(coff.Symbols[0].Auxiliaries[0][0:8])+sz)
348		case path == "/StringsHeader":
349			stringsoff = offset
350		case m.Find(path, RE("^/Strings"+N+"$")):
351			binary.LittleEndian.PutUint32(coff.Symbols[m[0]+1].Name[4:8], offset-stringsoff)
352		}
353
354		return freezeCommon2(v, &offset)
355	})
356	coff.SectionHeader32.PointerToRelocations = 0
357}
358
359func (coff *Coff) freezeRSRC() {
360	leafwalker := make(chan *DirEntry)
361	go func() {
362		for _, dir1 := range coff.Dir.Dirs { // resource type
363			for _, dir2 := range dir1.Dirs { // resource ID
364				for i := range dir2.DirEntries { // resource lang
365					leafwalker <- &dir2.DirEntries[i]
366				}
367			}
368		}
369	}()
370
371	var offset, diroff uint32
372	binutil.Walk(coff, func(v reflect.Value, path string) error {
373		diroff = coff.freezeCommon1(path, offset, diroff)
374
375		RE := regexp.MustCompile
376		const N = `\[(\d+)\]`
377		m := matcher{}
378		switch {
379		case m.Find(path, RE("^/Dir/Dirs"+N+"$")):
380			coff.Dir.DirEntries[m[0]].OffsetToData = MASK_SUBDIRECTORY | (offset - diroff)
381		case m.Find(path, RE("^/Dir/Dirs"+N+"/Dirs"+N+"$")):
382			coff.Dir.Dirs[m[0]].DirEntries[m[1]].OffsetToData = MASK_SUBDIRECTORY | (offset - diroff)
383		case m.Find(path, RE("^/DataEntries"+N+"$")):
384			direntry := <-leafwalker
385			direntry.OffsetToData = offset - diroff
386		case m.Find(path, RE("^/DataEntries"+N+"/OffsetToData$")):
387			coff.Relocations[m[0]].RVA = offset - diroff
388		case m.Find(path, RE("^/Data"+N+"$")):
389			coff.DataEntries[m[0]].OffsetToData = offset - diroff
390		}
391
392		return freezeCommon2(v, &offset)
393	})
394}
395
396func mustAtoi(s string) int {
397	i, err := strconv.Atoi(s)
398	if err != nil {
399		panic(err)
400	}
401	return i
402}
403
404type matcher []int
405
406func (m *matcher) Find(s string, re *regexp.Regexp) bool {
407	subs := re.FindStringSubmatch(s)
408	if subs == nil {
409		return false
410	}
411
412	*m = (*m)[:0]
413	for i := 1; i < len(subs); i++ {
414		*m = append(*m, mustAtoi(subs[i]))
415	}
416	return true
417}
418