2020-04-26 22:57:19 +02:00
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 {
2020-04-27 20:09:58 +02:00
if msg != "" {
panic ( msg + ": " + err . Error ( ) )
} else {
panic ( err )
}
2020-04-26 22:57:19 +02:00
}
}
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 ) {
2020-04-27 15:40:27 +02:00
i , err := strconv . ParseInt ( s , 0 , 8 )
2020-04-26 23:46:55 +02:00
optPanic ( "conversion of " + s + " to int8 failed" , err )
2020-04-26 22:57:19 +02:00
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 ) {
2020-04-27 15:40:27 +02:00
i , err := strconv . ParseUint ( s , 0 , 8 )
2020-04-26 23:46:55 +02:00
optPanic ( "conversion of " + s + " to uint8 failed" , err )
2020-04-26 22:57:19 +02:00
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 ) {
2020-04-27 15:40:27 +02:00
i , err := strconv . ParseInt ( s , 0 , 16 )
2020-04-26 23:46:55 +02:00
optPanic ( "conversion of " + s + " to int16 failed" , err )
2020-04-26 22:57:19 +02:00
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 ) {
2020-04-27 15:40:27 +02:00
i , err := strconv . ParseUint ( s , 0 , 16 )
2020-04-26 23:46:55 +02:00
optPanic ( "conversion of " + s + " to uint16 failed" , err )
2020-04-26 22:57:19 +02:00
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 ) {
2020-04-27 15:40:27 +02:00
i , err := strconv . ParseInt ( s , 0 , 32 )
2020-04-26 23:46:55 +02:00
optPanic ( "conversion of " + s + " to int32 failed" , err )
2020-04-26 22:57:19 +02:00
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 ) {
2020-04-27 15:40:27 +02:00
i , err := strconv . ParseUint ( s , 0 , 32 )
2020-04-26 23:46:55 +02:00
optPanic ( "conversion of " + s + " to uint32 failed" , err )
2020-04-26 22:57:19 +02:00
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 ) {
2020-04-27 15:40:27 +02:00
i , err := strconv . ParseInt ( s , 0 , 64 )
2020-04-26 23:46:55 +02:00
optPanic ( "conversion of " + s + " to int64 failed" , err )
2020-04-26 22:57:19 +02:00
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 ) {
2020-04-27 15:40:27 +02:00
i , err := strconv . ParseUint ( s , 0 , 64 )
2020-04-26 23:46:55 +02:00
optPanic ( "conversion of " + s + " to uint64 failed" , err )
2020-04-26 22:57:19 +02:00
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 )
2020-04-26 23:46:55 +02:00
optPanic ( "conversion of " + s + " to float32 failed" , err )
2020-04-26 22:57:19 +02:00
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 )
2020-04-26 23:46:55 +02:00
optPanic ( "conversion of " + s + " to float64 failed" , err )
2020-04-26 22:57:19 +02:00
binaryWrite ( w , bo , float64 ( f ) )
} } ,
}
type filePart struct {
name string
parttype string
handling handling
}
func ( part * filePart ) Line ( ) string {
return part . name + " : " + part . parttype
}
2020-04-27 19:33:47 +02:00
type formatAssignment struct {
val int64
used bool
}
2020-04-30 16:08:10 +02:00
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
}
2020-04-27 19:33:47 +02:00
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 {
2020-04-30 16:08:10 +02:00
variable , factor := splitVariableExpression ( tmp )
if ass , ok := assignments [ variable ] ; ok {
bytes = ass . val * factor
2020-04-27 19:33:47 +02:00
ass . used = true
err = nil
} else {
2020-04-30 16:08:10 +02:00
msg := "byte field definition in format description: could neither parse size to int64 nor obtain value from -fvar" +
" (" + part . parttype + ")"
2020-04-27 20:09:58 +02:00
if part . name != "" {
2020-04-27 20:13:22 +02:00
msg += " - definition of " + part . name
2020-04-27 20:09:58 +02:00
}
err = errors . New ( msg )
2020-04-26 22:57:19 +02:00
}
2020-04-27 19:33:47 +02:00
}
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 ] )
2020-04-26 22:57:19 +02:00
}
2020-04-27 19:33:47 +02:00
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 )
2020-04-26 22:57:19 +02:00
} 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 )
}
}
2020-04-27 19:33:47 +02:00
func readPart ( line string , assignments map [ string ] * formatAssignment ) * filePart {
2020-04-26 22:57:19 +02:00
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 ( ) )
2020-04-27 19:33:47 +02:00
part . setHandling ( assignments )
2020-04-26 22:57:19 +02:00
return part
}
2020-04-27 19:33:47 +02:00
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 {
2020-04-26 22:57:19 +02:00
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 != "" {
2020-04-27 19:33:47 +02:00
part := readPart ( line , assignments )
2020-04-26 22:57:19 +02:00
descr . parts = append ( descr . parts , * part )
line , err = buf . ReadString ( '\n' )
line = trim ( line )
}
line = trim ( line )
if err == io . EOF && line != "" {
2020-04-27 19:33:47 +02:00
descr . parts = append ( descr . parts , * readPart ( line , assignments ) )
2020-04-26 22:57:19 +02:00
} else if err != io . EOF {
optPanic ( errmsg , err )
}
2020-04-27 19:33:47 +02:00
/*--------------------------------*/
checkFormatAssignments ( assignments )
2020-04-26 22:57:19 +02:00
return descr
}
2020-04-27 19:33:47 +02:00
func readAssignments ( name string , s string ) map [ string ] string {
2020-04-26 22:57:19 +02:00
m := make ( map [ string ] string )
2020-04-27 19:33:47 +02:00
s = trim ( s )
if s == "" {
return m
}
assignments := strings . Split ( s , "," )
2020-04-26 22:57:19 +02:00
for _ , assignment := range assignments {
kv := strings . Split ( assignment , "=" )
if len ( kv ) != 2 {
2020-04-27 19:33:47 +02:00
panic ( "weird parameters in " + name + ": " + assignment + "\nWant list of comma-separated key=value assignments" )
2020-04-26 22:57:19 +02:00
}
key := trim ( kv [ 0 ] )
value := trim ( kv [ 1 ] )
2020-04-27 19:33:47 +02:00
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 {
2020-04-26 22:57:19 +02:00
found := false
for _ , part := range descr . parts {
if key == part . name {
found = true
}
}
2020-04-27 19:33:47 +02:00
if ! found {
2020-04-26 22:57:19 +02:00
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" )
}
2020-04-27 17:58:34 +02:00
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 ""
}
}
2020-04-26 22:57:19 +02:00
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 ( )
2020-04-27 00:18:47 +02:00
for i , part := range descr . parts {
2020-04-26 22:57:19 +02:00
needSeek := true
if part . name != "" {
if val , ok := vals [ part . name ] ; ok {
part . handling . writeTo ( f , descr . byteOrder , val )
needSeek = false
}
}
if needSeek {
2020-04-27 00:18:47 +02:00
_ , err = f . Seek ( part . handling . bytes , 1 )
2020-04-27 17:58:34 +02:00
optPanic ( seekErr ( i , part . name , err ) , err )
2020-04-26 22:57:19 +02:00
}
}
}
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 )
2020-04-27 00:18:47 +02:00
for i , part := range descr . parts {
2020-04-26 22:57:19 +02:00
if part . name == "" {
2020-04-27 22:49:23 +02:00
if i == len ( descr . parts ) - 1 {
throwaway := make ( [ ] byte , part . handling . bytes )
_ , err = f . Read ( throwaway )
} else {
_ , err = f . Seek ( part . handling . bytes , 1 )
}
2020-04-27 17:58:34 +02:00
optPanic ( seekErr ( i , part . name , err ) , err )
2020-04-26 22:57:19 +02:00
} else {
vals [ part . name ] = part . handling . readFrom ( f , descr . byteOrder )
}
}
return vals
}
2020-04-28 20:49:15 +02:00
func fmtPadding ( descr fileDescription , nopadding bool ) ( string , string , string ) {
if nopadding {
return "" , "" , ""
}
2020-04-26 22:57:19 +02:00
pad := 0
for _ , part := range descr . parts {
if n := len ( part . name ) ; n > pad {
pad = n
}
}
2020-04-28 20:49:15 +02:00
return strconv . Itoa ( pad ) , " " , " "
2020-04-26 22:57:19 +02:00
}
2020-04-28 20:49:15 +02:00
func printValues ( descr fileDescription , vals map [ string ] string , nopadding bool ) {
firstCol , space1 , space2 := fmtPadding ( descr , nopadding )
2020-04-26 22:57:19 +02:00
for _ , part := range descr . parts {
if part . name != "" {
2020-04-28 20:49:15 +02:00
fmt . Printf ( "%" + firstCol + "s%s=%s%s\n" , part . name , space1 , space2 , vals [ part . name ] )
2020-04-26 22:57:19 +02:00
}
}
}
func printUsage ( ) {
fmt . Fprintf ( os . Stderr , "Usage: %s [Options] FORMATFILE BINARY\n" , os . Args [ 0 ] )
flag . PrintDefaults ( )
}
func main ( ) {
2020-04-27 19:33:47 +02:00
var setAssignments string
var formatAssignments string
2020-04-28 20:49:15 +02:00
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\")" )
2020-04-26 22:57:19 +02:00
flag . BoolVar ( & debug , "debug" , false , "print recognized parts of the format file" )
2020-09-16 11:27:00 +02:00
flag . BoolVar ( & nopadding , "nopadding" , false , "turn off column padding (useful for shell scripting)" )
2020-04-26 22:57:19 +02:00
flag . Parse ( )
args := flag . Args ( )
if len ( args ) != 2 {
printUsage ( )
os . Exit ( - 1 )
}
2020-04-27 19:33:47 +02:00
descr := readFileDescription ( args [ 0 ] , readFormatAssignments ( formatAssignments ) )
if setAssignments != "" {
setValues ( descr , args [ 1 ] , readSetAssignments ( descr , setAssignments ) )
2020-04-26 22:57:19 +02:00
}
2020-04-28 20:49:15 +02:00
printValues ( descr , getValues ( descr , args [ 1 ] ) , nopadding )
2020-04-26 22:57:19 +02:00
}