483 lines
13 KiB
Go
483 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func optPanic(msg string, err error) {
|
|
if err != nil {
|
|
if msg != "" {
|
|
panic(msg + ": " + err.Error())
|
|
} else {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
type handling struct {
|
|
bytes int64
|
|
readFrom func(io.Reader, binary.ByteOrder) string
|
|
writeTo func(io.Writer, binary.ByteOrder, string)
|
|
}
|
|
|
|
func binaryRead(r io.Reader, order binary.ByteOrder, pointerTo interface{}) {
|
|
err := binary.Read(r, order, pointerTo)
|
|
optPanic(fmt.Sprintf("binary conversion failed (%T)", pointerTo), err)
|
|
}
|
|
|
|
func binaryWrite(w io.Writer, order binary.ByteOrder, pointerTo interface{}) {
|
|
err := binary.Write(w, order, pointerTo)
|
|
optPanic(fmt.Sprintf("binary write failed (%T)", pointerTo), err)
|
|
}
|
|
|
|
var handlings = map[string]handling{
|
|
"int8": handling{1,
|
|
func(r io.Reader, bo binary.ByteOrder) string {
|
|
var i int8
|
|
binaryRead(r, bo, &i)
|
|
return strconv.FormatInt(int64(i), 10)
|
|
},
|
|
func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
i, err := strconv.ParseInt(s, 0, 8)
|
|
optPanic("conversion of "+s+" to int8 failed", err)
|
|
binaryWrite(w, bo, int8(i))
|
|
}},
|
|
"uint8": handling{1,
|
|
func(r io.Reader, bo binary.ByteOrder) string {
|
|
var i uint8
|
|
binaryRead(r, bo, &i)
|
|
return strconv.FormatUint(uint64(i), 10)
|
|
},
|
|
func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
i, err := strconv.ParseUint(s, 0, 8)
|
|
optPanic("conversion of "+s+" to uint8 failed", err)
|
|
binaryWrite(w, bo, uint8(i))
|
|
}},
|
|
"int16": handling{2,
|
|
func(r io.Reader, bo binary.ByteOrder) string {
|
|
var i int16
|
|
binaryRead(r, bo, &i)
|
|
return strconv.FormatInt(int64(i), 10)
|
|
},
|
|
func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
i, err := strconv.ParseInt(s, 0, 16)
|
|
optPanic("conversion of "+s+" to int16 failed", err)
|
|
binaryWrite(w, bo, int16(i))
|
|
}},
|
|
"uint16": handling{2,
|
|
func(r io.Reader, bo binary.ByteOrder) string {
|
|
var i uint16
|
|
binaryRead(r, bo, &i)
|
|
return strconv.FormatUint(uint64(i), 10)
|
|
},
|
|
func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
i, err := strconv.ParseUint(s, 0, 16)
|
|
optPanic("conversion of "+s+" to uint16 failed", err)
|
|
binaryWrite(w, bo, uint16(i))
|
|
}},
|
|
"int32": handling{4,
|
|
func(r io.Reader, bo binary.ByteOrder) string {
|
|
var i int32
|
|
binaryRead(r, bo, &i)
|
|
return strconv.FormatInt(int64(i), 10)
|
|
},
|
|
func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
i, err := strconv.ParseInt(s, 0, 32)
|
|
optPanic("conversion of "+s+" to int32 failed", err)
|
|
binaryWrite(w, bo, int32(i))
|
|
}},
|
|
"uint32": handling{4,
|
|
func(r io.Reader, bo binary.ByteOrder) string {
|
|
var i uint32
|
|
binaryRead(r, bo, &i)
|
|
return strconv.FormatUint(uint64(i), 10)
|
|
},
|
|
func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
i, err := strconv.ParseUint(s, 0, 32)
|
|
optPanic("conversion of "+s+" to uint32 failed", err)
|
|
binaryWrite(w, bo, uint32(i))
|
|
}},
|
|
"int64": handling{8,
|
|
func(r io.Reader, bo binary.ByteOrder) string {
|
|
var i int64
|
|
binaryRead(r, bo, &i)
|
|
return strconv.FormatInt(i, 10)
|
|
},
|
|
func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
i, err := strconv.ParseInt(s, 0, 64)
|
|
optPanic("conversion of "+s+" to int64 failed", err)
|
|
binaryWrite(w, bo, i)
|
|
}},
|
|
"uint64": handling{8,
|
|
func(r io.Reader, bo binary.ByteOrder) string {
|
|
var i uint64
|
|
binaryRead(r, bo, &i)
|
|
return strconv.FormatUint(i, 10)
|
|
},
|
|
func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
i, err := strconv.ParseUint(s, 0, 64)
|
|
optPanic("conversion of "+s+" to uint64 failed", err)
|
|
binaryWrite(w, bo, i)
|
|
}},
|
|
"float32": handling{4,
|
|
func(r io.Reader, bo binary.ByteOrder) string {
|
|
var f float32
|
|
binaryRead(r, bo, &f)
|
|
return strconv.FormatFloat(float64(f), 'f', -1, 64)
|
|
},
|
|
func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
f, err := strconv.ParseFloat(s, 32)
|
|
optPanic("conversion of "+s+" to float32 failed", err)
|
|
binaryWrite(w, bo, float32(f))
|
|
}},
|
|
"float64": handling{8,
|
|
func(r io.Reader, bo binary.ByteOrder) string {
|
|
var f float64
|
|
binaryRead(r, bo, &f)
|
|
return strconv.FormatFloat(f, 'f', -1, 64)
|
|
},
|
|
func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
optPanic("conversion of "+s+" to float64 failed", err)
|
|
binaryWrite(w, bo, float64(f))
|
|
}},
|
|
}
|
|
|
|
type filePart struct {
|
|
name string
|
|
parttype string
|
|
handling handling
|
|
}
|
|
|
|
func (part *filePart) Line() string {
|
|
return part.name + " : " + part.parttype
|
|
}
|
|
|
|
type formatAssignment struct {
|
|
val int64
|
|
used bool
|
|
}
|
|
|
|
func splitVariableExpression(s string) (string, int64) {
|
|
split := strings.Split(s, "*")
|
|
name := trim(split[0])
|
|
if len(split) == 2 {
|
|
factor, err := strconv.ParseInt(split[1], 0, 64)
|
|
optPanic("parsing definition: "+s, err)
|
|
return name, factor
|
|
}
|
|
return name, 1
|
|
}
|
|
|
|
func (part *filePart) setByteFieldHandling(assignments map[string]*formatAssignment) {
|
|
tmp := trim(strings.TrimSuffix(strings.TrimPrefix(part.parttype, "byte["), "]"))
|
|
var bytes int64
|
|
var err error
|
|
if bytes, err = strconv.ParseInt(tmp, 10, 64); err != nil {
|
|
variable, factor := splitVariableExpression(tmp)
|
|
if ass, ok := assignments[variable]; ok {
|
|
bytes = ass.val * factor
|
|
ass.used = true
|
|
err = nil
|
|
} else {
|
|
msg := "byte field definition in format description: could neither parse size to int64 nor obtain value from -fvar" +
|
|
" (" + part.parttype + ")"
|
|
if part.name != "" {
|
|
msg += " - definition of " + part.name
|
|
}
|
|
err = errors.New(msg)
|
|
}
|
|
}
|
|
optPanic(part.name, err)
|
|
readFrom := func(r io.Reader, bo binary.ByteOrder) string {
|
|
buf := make([]byte, bytes)
|
|
var i int64
|
|
for i = 0; i < bytes; i++ {
|
|
binaryRead(r, bo, &buf[i])
|
|
}
|
|
return hex.EncodeToString(buf)
|
|
}
|
|
writeTo := func(w io.Writer, bo binary.ByteOrder, s string) {
|
|
s = strings.TrimPrefix(s, "0x")
|
|
buf, err := hex.DecodeString(s)
|
|
optPanic("invalid string of hex values: "+s, err)
|
|
if int64(len(buf)) > bytes {
|
|
panic(fmt.Sprintf("string %s is too long for byte array %s : want %d bytes", s, part.name, bytes))
|
|
}
|
|
binaryWrite(w, bo, buf)
|
|
}
|
|
part.handling = handling{bytes, readFrom, writeTo}
|
|
}
|
|
|
|
func (part *filePart) setHandling(assignments map[string]*formatAssignment) {
|
|
if strings.HasPrefix(part.parttype, "byte") {
|
|
part.setByteFieldHandling(assignments)
|
|
|
|
} else {
|
|
if handling, ok := handlings[part.parttype]; !ok {
|
|
panic(part.Line() + ": Don't know type")
|
|
} else {
|
|
part.handling = handling
|
|
}
|
|
}
|
|
}
|
|
|
|
type fileDescription struct {
|
|
byteOrder binary.ByteOrder
|
|
parts []filePart
|
|
}
|
|
|
|
func trim(s string) string {
|
|
return strings.TrimSpace(s)
|
|
}
|
|
|
|
var debug bool
|
|
|
|
func info(i string, s string) {
|
|
if debug {
|
|
fmt.Fprintln(os.Stderr, i, "-->", s)
|
|
}
|
|
}
|
|
|
|
func readPart(line string, assignments map[string]*formatAssignment) *filePart {
|
|
part := &filePart{}
|
|
splitline := strings.Split(line, ":")
|
|
if len(splitline) != 2 {
|
|
panic("invalid definition: " + line)
|
|
}
|
|
part.name = trim(splitline[0])
|
|
part.parttype = trim(splitline[1])
|
|
info("definition", part.Line())
|
|
part.setHandling(assignments)
|
|
return part
|
|
}
|
|
|
|
func checkFormatAssignments(assignments map[string]*formatAssignment) {
|
|
for name, ass := range assignments {
|
|
if !ass.used {
|
|
panic("assignment to unknown variable: " + name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func readFileDescription(path string, assignments map[string]*formatAssignment) fileDescription {
|
|
errmsg := "read format file"
|
|
descr := fileDescription{}
|
|
f, err := os.Open(path)
|
|
defer f.Close()
|
|
optPanic("open format file", err)
|
|
buf := bufio.NewReader(f)
|
|
/*----------------------------------*/
|
|
line, err := buf.ReadString('\n')
|
|
optPanic(errmsg, err)
|
|
line = trim(line)
|
|
for strings.HasPrefix(line, "#") {
|
|
info("comment", line)
|
|
line, err = buf.ReadString('\n')
|
|
optPanic(errmsg, err)
|
|
line = trim(line)
|
|
}
|
|
/*----------------------------------*/
|
|
line = trim(line)
|
|
if line == "big endian" {
|
|
descr.byteOrder = binary.BigEndian
|
|
} else if line == "little endian" {
|
|
descr.byteOrder = binary.LittleEndian
|
|
} else {
|
|
panic(errmsg + ": expected endianness ('little endian' or 'big endian'), got :" + line)
|
|
}
|
|
info("endianness", line)
|
|
/*----------------------------------*/
|
|
line, err = buf.ReadString('\n')
|
|
optPanic(errmsg, err)
|
|
line = trim(line)
|
|
for err != io.EOF && line != "" {
|
|
part := readPart(line, assignments)
|
|
descr.parts = append(descr.parts, *part)
|
|
line, err = buf.ReadString('\n')
|
|
line = trim(line)
|
|
}
|
|
line = trim(line)
|
|
if err == io.EOF && line != "" {
|
|
descr.parts = append(descr.parts, *readPart(line, assignments))
|
|
} else if err != io.EOF {
|
|
optPanic(errmsg, err)
|
|
}
|
|
/*--------------------------------*/
|
|
checkFormatAssignments(assignments)
|
|
return descr
|
|
}
|
|
|
|
func readAssignments(name string, s string) map[string]string {
|
|
m := make(map[string]string)
|
|
s = trim(s)
|
|
if s == "" {
|
|
return m
|
|
}
|
|
assignments := strings.Split(s, ",")
|
|
for _, assignment := range assignments {
|
|
kv := strings.Split(assignment, "=")
|
|
if len(kv) != 2 {
|
|
panic("weird parameters in " + name + ": " + assignment + "\nWant list of comma-separated key=value assignments")
|
|
}
|
|
key := trim(kv[0])
|
|
value := trim(kv[1])
|
|
m[key] = value
|
|
}
|
|
return m
|
|
}
|
|
|
|
func readFormatAssignments(s string) map[string]*formatAssignment {
|
|
tmp := readAssignments("fvar", s)
|
|
m := make(map[string]*formatAssignment)
|
|
for key, val := range tmp {
|
|
i, err := strconv.ParseInt(val, 0, 64)
|
|
optPanic("Could not parse format variable assignment "+val, err)
|
|
m[key] = &formatAssignment{i, false}
|
|
}
|
|
return m
|
|
}
|
|
|
|
func readSetAssignments(descr fileDescription, s string) map[string]string {
|
|
m := readAssignments("set", s)
|
|
for key, _ := range m {
|
|
found := false
|
|
for _, part := range descr.parts {
|
|
if key == part.name {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
panic("unknown key: " + key)
|
|
}
|
|
}
|
|
return m
|
|
}
|
|
|
|
func backup(binpath string) {
|
|
bin, err := os.Open(binpath)
|
|
defer bin.Close()
|
|
optPanic("failed to open binary", err)
|
|
timestamp := time.Now().Format("20060102150405")
|
|
fi, err := bin.Stat()
|
|
optPanic("failed to get permissions of binary", err)
|
|
backuppath := binpath + ".bak" + timestamp
|
|
backup, err := os.OpenFile(backuppath, os.O_RDWR|os.O_CREATE, fi.Mode())
|
|
defer backup.Close()
|
|
_, err = io.Copy(backup, bin)
|
|
optPanic("failed to create backup", err)
|
|
backup.Chmod(fi.Mode())
|
|
fmt.Println("Created backup " + backuppath + "\n")
|
|
}
|
|
|
|
func seekErr(i int, name string, inner error) string {
|
|
if inner != nil {
|
|
namepart := ""
|
|
if name != "" {
|
|
namepart = " name: " + name
|
|
}
|
|
return fmt.Sprintf("could not advance in file (stuck at part %d%s)", i, namepart)
|
|
} else {
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func setValues(descr fileDescription, binpath string, vals map[string]string) {
|
|
backup(binpath)
|
|
f, err := os.OpenFile(binpath, os.O_RDWR, 0666)
|
|
optPanic("failed to write-open binary", err)
|
|
defer f.Close()
|
|
for i, part := range descr.parts {
|
|
needSeek := true
|
|
if part.name != "" {
|
|
if val, ok := vals[part.name]; ok {
|
|
part.handling.writeTo(f, descr.byteOrder, val)
|
|
needSeek = false
|
|
}
|
|
}
|
|
if needSeek {
|
|
_, err = f.Seek(part.handling.bytes, 1)
|
|
optPanic(seekErr(i, part.name, err), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func getValues(descr fileDescription, binpath string) map[string]string {
|
|
vals := make(map[string]string)
|
|
f, err := os.Open(binpath)
|
|
defer f.Close()
|
|
optPanic("failed to open binary", err)
|
|
for i, part := range descr.parts {
|
|
if part.name == "" {
|
|
if i == len(descr.parts)-1 {
|
|
throwaway := make([]byte, part.handling.bytes)
|
|
_, err = f.Read(throwaway)
|
|
} else {
|
|
_, err = f.Seek(part.handling.bytes, 1)
|
|
}
|
|
optPanic(seekErr(i, part.name, err), err)
|
|
} else {
|
|
vals[part.name] = part.handling.readFrom(f, descr.byteOrder)
|
|
}
|
|
}
|
|
return vals
|
|
}
|
|
|
|
func fmtPadding(descr fileDescription, nopadding bool) (string, string, string) {
|
|
if nopadding {
|
|
return "", "", ""
|
|
}
|
|
pad := 0
|
|
for _, part := range descr.parts {
|
|
if n := len([]rune(part.name)); n > pad {
|
|
pad = n
|
|
}
|
|
}
|
|
return strconv.Itoa(pad), " ", " "
|
|
}
|
|
|
|
func printValues(descr fileDescription, vals map[string]string, nopadding bool) {
|
|
firstCol, space1, space2 := fmtPadding(descr, nopadding)
|
|
for _, part := range descr.parts {
|
|
if part.name != "" {
|
|
fmt.Printf("%"+firstCol+"s%s=%s%s\n", part.name, space1, space2, vals[part.name])
|
|
}
|
|
}
|
|
}
|
|
|
|
func printUsage() {
|
|
fmt.Fprintf(os.Stderr, "Usage: %s [Options] FORMATFILE BINARY\n", os.Args[0])
|
|
flag.PrintDefaults()
|
|
}
|
|
|
|
func main() {
|
|
var setAssignments string
|
|
var formatAssignments string
|
|
var nopadding bool
|
|
flag.StringVar(&setAssignments, "set", "", "set values in binary file to key=value pairs (e.g. \"key1=value1,key2=value2\")")
|
|
flag.StringVar(&formatAssignments, "fvar", "", "assign values to variables in format description with key=value pairs (e.g. \"key1=value1,key2=value2\")")
|
|
flag.BoolVar(&debug, "debug", false, "print recognized parts of the format file")
|
|
flag.BoolVar(&nopadding, "nopadding", false, "turn off column padding (useful for shell scripting)")
|
|
flag.Parse()
|
|
args := flag.Args()
|
|
if len(args) != 2 {
|
|
printUsage()
|
|
os.Exit(-1)
|
|
}
|
|
descr := readFileDescription(args[0], readFormatAssignments(formatAssignments))
|
|
if setAssignments != "" {
|
|
setValues(descr, args[1], readSetAssignments(descr, setAssignments))
|
|
}
|
|
printValues(descr, getValues(descr, args[1]), nopadding)
|
|
}
|