diff --git a/README b/README index 1e2b993..7540ed8 100644 --- a/README +++ b/README @@ -22,32 +22,56 @@ comment = '#' { LETTER | DIGIT } '\n'. endianness = ("little endian" | "big endian") '\n'. -definition = name ':' type '\n'. +definition = [ IDENT ] ':' type '\n'. -name = { LETTER | DIGIT }. - -type = ( "byte[" INTEGER "]" ) +type = bytefield | int8 | int16 | int32 | int64 | uint8 | uint16 | uint32 | uint64 - | float32 | float64 + | float32 | float64 +. + +bytefield = "byte[" ( INTEGER | IDENT ) "]". ``` -The name of a definition line can be left empty to ignore that particular value. -Portions of the binary file not covered be the file description are ignored. +The identifier of a definition line can be left empty to ignore that particular value. +Portions of the binary file not covered by the file description are ignored. Byte field +size can be variable - the specific value then needs to be set at program invocation (via -fvar). Usage ----- -To get values: laymanshex FORMATFILE BINARY +To get values: `laymanshex FORMATFILE BINARY` -To set values: laymanshex -set "key1=value1,key2=value2" FORMATFILE BINARY +To set values: `laymanshex -set "key1=value1,key2=value2" FORMATFILE BINARY` + +To set a variable 'offset' to 113 in the file description and get values: `laymanshex -fvar="offset=113" FORMATFILE BINARY` Examples of format descriptions can be found in the [laymanshex-files repo](/laymanshex-files/). -TODO ----- -There is no way to express offset values inside a file yet. Since this is -relatively common, I will add it at some point. Also: Repeated data limited by -marker or size field. +Advanced +-------- + +The variable byte field size allows to script around laymanshex and use a sliding frame +to obtain sequences. E.g. you can define a partial file format that only covers one +element of the sequence at a certain offset: + +``` +little endian + : byte[offset] +value : int32 +``` + +Script: + +``` +#/bin/bash + +for i in `seq 99`; do + laymanshex -fvar="offset=$((i*4))" FORMATFILE BINARY +done +``` + +If your sequence ends with a marker, add that marker to the file description and check +its value after each invocation of laymanshex. diff --git a/laymanshex.go b/laymanshex.go index ef4e284..84d71c6 100644 --- a/laymanshex.go +++ b/laymanshex.go @@ -159,29 +159,48 @@ func (part *filePart) Line() string { return part.name + " : " + part.parttype } -func (part *filePart) setHandling() { +type formatAssignment struct { + val int64 + used bool +} + +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 { + if ass, ok := assignments[tmp]; ok { + bytes = ass.val + ass.used = true + err = nil + } else { + err = errors.New("Could neither parse size to int64 nor obtain value from -fvar") + } + } + 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") { - 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) { - 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} + part.setByteFieldHandling(assignments) } else { if handling, ok := handlings[part.parttype]; !ok { @@ -209,7 +228,7 @@ func info(i string, s string) { } } -func readPart(line string) *filePart { +func readPart(line string, assignments map[string]*formatAssignment) *filePart { part := &filePart{} splitline := strings.Split(line, ":") if len(splitline) != 2 { @@ -218,11 +237,19 @@ func readPart(line string) *filePart { part.name = trim(splitline[0]) part.parttype = trim(splitline[1]) info("definition", part.Line()) - part.setHandling() + part.setHandling(assignments) return part } -func readFileDescription(path string) fileDescription { +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) @@ -254,39 +281,62 @@ func readFileDescription(path string) fileDescription { optPanic(errmsg, err) line = trim(line) for err != io.EOF && line != "" { - part := readPart(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)) + descr.parts = append(descr.parts, *readPart(line, assignments)) } else if err != io.EOF { optPanic(errmsg, err) } + /*--------------------------------*/ + checkFormatAssignments(assignments) return descr } -func readAssignments(descr fileDescription, s string) map[string]string { - assignments := strings.Split(s, ",") +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 set: " + assignment + "\nWant list of comma-separated key=value assignments") + 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 { - m[key] = value - } else { + if !found { panic("unknown key: " + key) } } @@ -381,8 +431,10 @@ func printUsage() { } func main() { - var assignments string - flag.StringVar(&assignments, "set", "", "key=value pairs (e.g. \"key1=value1,key2=value2\")") + var setAssignments string + var formatAssignments string + flag.StringVar(&setAssignments, "set", "", "key=value pairs (e.g. \"key1=value1,key2=value2\")") + flag.StringVar(&formatAssignments, "fvar", "", "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() @@ -390,9 +442,9 @@ func main() { printUsage() os.Exit(-1) } - descr := readFileDescription(args[0]) - if assignments != "" { - setValues(descr, args[1], readAssignments(descr, assignments)) + descr := readFileDescription(args[0], readFormatAssignments(formatAssignments)) + if setAssignments != "" { + setValues(descr, args[1], readSetAssignments(descr, setAssignments)) } printValues(descr, getValues(descr, args[1])) }