Initial commit
This commit is contained in:
commit
a2f357eb33
16
LICENSE
Normal file
16
LICENSE
Normal file
|
@ -0,0 +1,16 @@
|
|||
LaymansHex: A layman's hex editor.
|
||||
Copyright (C) 2020 Alexander Weinhold
|
||||
|
||||
Unless explicitly stated otherwise, the following applies:
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as published by
|
||||
the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
50
README
Normal file
50
README
Normal file
|
@ -0,0 +1,50 @@
|
|||
LaymansHex
|
||||
==========
|
||||
|
||||
LaymansHex takes a (partial) file description and allows tinkering with
|
||||
the values in a binary file. It thus serves as a sort of layman's
|
||||
hex editor.
|
||||
|
||||
|
||||
File description format
|
||||
-----------------------
|
||||
|
||||
In EBNF:
|
||||
```
|
||||
description =
|
||||
{ comment }
|
||||
endianness
|
||||
definition
|
||||
{ definition }
|
||||
.
|
||||
|
||||
comment = '#' { LETTER | DIGIT } '\n'.
|
||||
|
||||
endianness = ("little endian" | "big endian") '\n'.
|
||||
|
||||
definition = name ':' type '\n'.
|
||||
|
||||
name = { LETTER | DIGIT }.
|
||||
|
||||
type = ( "byte[" INTEGER "]" )
|
||||
| int8 | int16 | int32 | int64
|
||||
| uint8 | uint16 | uint32 | uint64
|
||||
| float32 | float64
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
To get values: laymanshex FORMATFILE BINARY
|
||||
To set values: laymanshex -set "key123=value123" FORMATFILE BINARY
|
||||
|
||||
|
||||
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.
|
380
laymanshex.go
Normal file
380
laymanshex.go
Normal file
|
@ -0,0 +1,380 @@
|
|||
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, 10, 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, 10, 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, 10, 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, 10, 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, 10, 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, 10, 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, 10, 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, 10, 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)
|
||||
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 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 _, 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 {
|
||||
f.Seek(part.handling.bytes, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 _, part := range descr.parts {
|
||||
if part.name == "" {
|
||||
f.Seek(part.handling.bytes, 1)
|
||||
} else {
|
||||
vals[part.name] = part.handling.readFrom(f, descr.byteOrder)
|
||||
}
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
func padding(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("%"+padding(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]))
|
||||
}
|
Loading…
Reference in New Issue
Block a user