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 { panic(errors.New(msg + ": " + err.Error())) } } 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 } func (part *filePart) setHandling() { if strings.HasPrefix(part.parttype, "byte") { tmp := strings.TrimRight(strings.TrimLeft(part.parttype, "byte["), "]") bytes, err := strconv.ParseInt(tmp, 10, 64) 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) { 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} } 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) *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() return part } func readFileDescription(path string) 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) 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)) } else if err != io.EOF { optPanic(errmsg, err) } return descr } func readAssignments(descr fileDescription, s string) map[string]string { assignments := strings.Split(s, ",") m := make(map[string]string) for _, assignment := range assignments { kv := strings.Split(assignment, "=") if len(kv) != 2 { panic("weird parameters in set: " + assignment + "\nWant list of comma-separated key=value assignments") } key := trim(kv[0]) value := trim(kv[1]) found := false for _, part := range descr.parts { if key == part.name { found = true } } if found { m[key] = value } else { 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 == "" { _, 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) string { pad := 0 for _, part := range descr.parts { if n := len(part.name); n > pad { pad = n } } return strconv.Itoa(pad) } func printValues(descr fileDescription, vals map[string]string) { for _, part := range descr.parts { if part.name != "" { fmt.Printf("%"+fmtPadding(descr)+"s = %s\n", part.name, vals[part.name]) } } } func printUsage() { fmt.Fprintf(os.Stderr, "Usage: %s [Options] FORMATFILE BINARY\n", os.Args[0]) flag.PrintDefaults() } func main() { var assignments string flag.StringVar(&assignments, "set", "", "key=value pairs (e.g. \"key1=value1,key2=value2\")") flag.BoolVar(&debug, "debug", false, "print recognized parts of the format file") flag.Parse() args := flag.Args() if len(args) != 2 { printUsage() os.Exit(-1) } descr := readFileDescription(args[0]) if assignments != "" { setValues(descr, args[1], readAssignments(descr, assignments)) } printValues(descr, getValues(descr, args[1])) }