allow variable byte field size
* sequences of certain sizes or delimited with a marker can now be obtained through multiple invocations
This commit is contained in:
parent
88c7d63adc
commit
279429d0bf
50
README
50
README
|
@ -22,32 +22,56 @@ comment = '#' { LETTER | DIGIT } '\n'.
|
||||||
|
|
||||||
endianness = ("little endian" | "big endian") '\n'.
|
endianness = ("little endian" | "big endian") '\n'.
|
||||||
|
|
||||||
definition = name ':' type '\n'.
|
definition = [ IDENT ] ':' type '\n'.
|
||||||
|
|
||||||
name = { LETTER | DIGIT }.
|
type = bytefield
|
||||||
|
|
||||||
type = ( "byte[" INTEGER "]" )
|
|
||||||
| int8 | int16 | int32 | int64
|
| int8 | int16 | int32 | int64
|
||||||
| uint8 | uint16 | uint32 | uint64
|
| 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.
|
The identifier 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.
|
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
|
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/).
|
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
|
Advanced
|
||||||
relatively common, I will add it at some point. Also: Repeated data limited by
|
--------
|
||||||
marker or size field.
|
|
||||||
|
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.
|
||||||
|
|
|
@ -159,10 +159,24 @@ func (part *filePart) Line() string {
|
||||||
return part.name + " : " + part.parttype
|
return part.name + " : " + part.parttype
|
||||||
}
|
}
|
||||||
|
|
||||||
func (part *filePart) setHandling() {
|
type formatAssignment struct {
|
||||||
if strings.HasPrefix(part.parttype, "byte") {
|
val int64
|
||||||
tmp := strings.TrimRight(strings.TrimLeft(part.parttype, "byte["), "]")
|
used bool
|
||||||
bytes, err := strconv.ParseInt(tmp, 10, 64)
|
}
|
||||||
|
|
||||||
|
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)
|
optPanic(part.name, err)
|
||||||
readFrom := func(r io.Reader, bo binary.ByteOrder) string {
|
readFrom := func(r io.Reader, bo binary.ByteOrder) string {
|
||||||
buf := make([]byte, bytes)
|
buf := make([]byte, bytes)
|
||||||
|
@ -182,6 +196,11 @@ func (part *filePart) setHandling() {
|
||||||
binaryWrite(w, bo, buf)
|
binaryWrite(w, bo, buf)
|
||||||
}
|
}
|
||||||
part.handling = handling{bytes, readFrom, writeTo}
|
part.handling = handling{bytes, readFrom, writeTo}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (part *filePart) setHandling(assignments map[string]*formatAssignment) {
|
||||||
|
if strings.HasPrefix(part.parttype, "byte") {
|
||||||
|
part.setByteFieldHandling(assignments)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if handling, ok := handlings[part.parttype]; !ok {
|
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{}
|
part := &filePart{}
|
||||||
splitline := strings.Split(line, ":")
|
splitline := strings.Split(line, ":")
|
||||||
if len(splitline) != 2 {
|
if len(splitline) != 2 {
|
||||||
|
@ -218,11 +237,19 @@ func readPart(line string) *filePart {
|
||||||
part.name = trim(splitline[0])
|
part.name = trim(splitline[0])
|
||||||
part.parttype = trim(splitline[1])
|
part.parttype = trim(splitline[1])
|
||||||
info("definition", part.Line())
|
info("definition", part.Line())
|
||||||
part.setHandling()
|
part.setHandling(assignments)
|
||||||
return part
|
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"
|
errmsg := "read format file"
|
||||||
descr := fileDescription{}
|
descr := fileDescription{}
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
|
@ -254,39 +281,62 @@ func readFileDescription(path string) fileDescription {
|
||||||
optPanic(errmsg, err)
|
optPanic(errmsg, err)
|
||||||
line = trim(line)
|
line = trim(line)
|
||||||
for err != io.EOF && line != "" {
|
for err != io.EOF && line != "" {
|
||||||
part := readPart(line)
|
part := readPart(line, assignments)
|
||||||
descr.parts = append(descr.parts, *part)
|
descr.parts = append(descr.parts, *part)
|
||||||
line, err = buf.ReadString('\n')
|
line, err = buf.ReadString('\n')
|
||||||
line = trim(line)
|
line = trim(line)
|
||||||
}
|
}
|
||||||
line = trim(line)
|
line = trim(line)
|
||||||
if err == io.EOF && 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 {
|
} else if err != io.EOF {
|
||||||
optPanic(errmsg, err)
|
optPanic(errmsg, err)
|
||||||
}
|
}
|
||||||
|
/*--------------------------------*/
|
||||||
|
checkFormatAssignments(assignments)
|
||||||
return descr
|
return descr
|
||||||
}
|
}
|
||||||
|
|
||||||
func readAssignments(descr fileDescription, s string) map[string]string {
|
func readAssignments(name string, s string) map[string]string {
|
||||||
assignments := strings.Split(s, ",")
|
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
|
s = trim(s)
|
||||||
|
if s == "" {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
assignments := strings.Split(s, ",")
|
||||||
for _, assignment := range assignments {
|
for _, assignment := range assignments {
|
||||||
kv := strings.Split(assignment, "=")
|
kv := strings.Split(assignment, "=")
|
||||||
if len(kv) != 2 {
|
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])
|
key := trim(kv[0])
|
||||||
value := trim(kv[1])
|
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
|
found := false
|
||||||
for _, part := range descr.parts {
|
for _, part := range descr.parts {
|
||||||
if key == part.name {
|
if key == part.name {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found {
|
if !found {
|
||||||
m[key] = value
|
|
||||||
} else {
|
|
||||||
panic("unknown key: " + key)
|
panic("unknown key: " + key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,8 +431,10 @@ func printUsage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var assignments string
|
var setAssignments string
|
||||||
flag.StringVar(&assignments, "set", "", "key=value pairs (e.g. \"key1=value1,key2=value2\")")
|
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.BoolVar(&debug, "debug", false, "print recognized parts of the format file")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
|
@ -390,9 +442,9 @@ func main() {
|
||||||
printUsage()
|
printUsage()
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
}
|
}
|
||||||
descr := readFileDescription(args[0])
|
descr := readFileDescription(args[0], readFormatAssignments(formatAssignments))
|
||||||
if assignments != "" {
|
if setAssignments != "" {
|
||||||
setValues(descr, args[1], readAssignments(descr, assignments))
|
setValues(descr, args[1], readSetAssignments(descr, setAssignments))
|
||||||
}
|
}
|
||||||
printValues(descr, getValues(descr, args[1]))
|
printValues(descr, getValues(descr, args[1]))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user