blob: a659ac0008f5e0e3be83cfa8cf760059b8ca0b63 [file] [log] [blame]
#! /usr/pkg/bin/perl -w
# $Id: tme-binary-struct.pl.in,v 1.2 2005/01/14 11:40:50 fredette Exp $
# tools/tme-binary-struct.pl.in - common framework for scripts that
# manipulate files containing binary structures:
#
# Copyright (c) 2004 Matt Fredette
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
# must display the following acknowledgement:
# This product includes software developed by Matt Fredette.
# 4. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# silence perl -w:
#
undef($bad);
undef($packed);
undef(%name_to_values);
# globals:
#
$0 =~ /^(.*\/)?([^\/]+)$/; $PROG = $2;
# check our command line:
#
$usage = 0;
$verbose = 0;
$all = 0;
undef($format_input);
undef($format_output);
for (; @ARGV > 0 && $ARGV[0] =~ /^-/; ) {
$option = shift(@ARGV);
if ($option eq '--verbose') {
$verbose++;
}
elsif ($option eq '--all') {
$all = 1;
}
elsif ($option =~ /^--format-input=(\S+)$/) {
$format_input = $1;
}
elsif ($option =~ /^--format-output=(\S+)$/) {
$format_output = $1;
}
else {
if ($option ne "-h"
&& $option ne "--help"
&& $option ne "-?") {
print STDERR "$PROG error: unknown option `$option'\n";
}
$usage = 1;
last;
}
}
if (defined($format_input)
&& $format_input ne 'text'
&& $format_input ne 'binary') {
print STDERR "$PROG error: unknown input format $format_input\n";
$usage = 1;
}
if (defined($format_output)
&& $format_output ne 'text'
&& $format_output ne 'binary') {
print STDERR "$PROG error: unknown input format $format_output\n";
$usage = 1;
}
if (@ARGV > 0) {
print STDERR "$PROG error: `$ARGV[0]' unexpected\n";
$usage = 1;
}
if ($usage) {
print STDERR <<"EOF;";
usage: $PROG [ OPTIONS ]
where OPTIONS are:
--verbose include comments in text output
--all display normally hidden fields in text output
--format-input=FORMAT set the input format to FORMAT, one of: text binary
--format-output=FORMAT set the output format to FORMAT, one of: text binary
EOF;
exit (1);
}
# the set of related types:
#
%types_related = split(/[\r\n\s]+/, <<'EOF;');
generic_char_hex generic_integral
generic_char_dec generic_integral
generic_shorteb_hex generic_integral
generic_shorteb_dec generic_integral
generic_shortel_hex generic_integral
generic_shortel_dec generic_integral
generic_longeb_hex generic_integral
generic_longeb_dec generic_integral
generic_longel_hex generic_integral
generic_longel_dec generic_integral
EOF;
# get the structure definition:
#
$struct_definition = &binary_struct();
# process the structure definition and make the default input:
#
$input_default = "";
@comments = ("");
$comments_new = 0;
for ($line_start = 0;
$line_start < length($struct_definition); ) {
# get the offset of the next line separator:
#
$line_end = index($struct_definition, "\n", $line_start);
if ($line_end < 0) {
$line_end = length($struct_definition) + 1;
}
# get the next line:
#
$_ = substr($struct_definition, $line_start, $line_end - $line_start);
$line_start = $line_end + 1;
# ignore comments and blank lines:
#
if ($_ !~ /\S/ || /^\s*\#/) {
if ($comments_new) {
push(@comments, "");
$comments_new = 0;
}
$comments[$#comments] .= $_."\n";
next;
}
# tokenize this line:
#
($offset, $name, $type, $values) = split(' ', $_, 4);
# make sure this name isn't multiply-defined:
#
if (defined($name_to_offset{$name})) {
print STDERR "$PROG internal error: $name multiply defined\n";
exit (1);
}
# convert the offset:
#
$offset = hex($offset);
# canonicalize the type and count:
#
if ($type =~ /^(.*\D)(\d+)$/) {
($type, $count) = ($1, $2);
}
else {
$count = 1;
}
# make sure this type is known:
#
$func = $types_related{$type};
if (!defined($func)) {
$func = $type;
}
unless (eval("defined(\&type_${func}_pack);")) {
print STDERR "$PROG internal error: unknown type $func\n";
exit (1);
}
# remember this name:
#
push (@names, $name);
$name_to_offset{$name} = $offset;
$name_to_type{$name} = $type;
$name_to_count{$name} = $count;
$name_to_values{$name} = $values;
$name_to_func{$name} = $func;
$name_to_comments{$name} = $#comments;
# get the default value for this field:
#
eval("(\$value) = \&type_${func}_values(\$type, \$count, \$values);");
# if the default value has an alias, use the alias:
#
if ($value =~ s/=([^=]+)$//) {
$value = $1;
}
# add this value to the default input:
#
$input_default .= "$name $value\n";
# the next comment starts a new comment:
#
$comments_new = 1;
}
# if our standard input is a terminal:
#
if (-t STDIN) {
# if the user specified the input format, and it's not text, that's an error:
#
if (defined($format_input)
&& $format_input ne 'text') {
print STDERR "$PROG error: the input format can't be $format_input when standard input is a terminal\n";
exit (1);
}
$format_input = 'text';
# there is no standard input:
#
$input = "";
}
# otherwise, our standard input is not a terminal:
#
else {
# read in standard input:
#
$input = "";
for (;;) {
undef($_);
$size = sysread(STDIN, $_, 1024);
if (!defined($size)) {
print STDERR "fatal: could not read stdin: $!\n";
exit (1);
}
elsif ($size == 0) {
last;
}
$input .= $_;
}
# if we don't know if the input format is text or binary, try to
# figure it out:
#
if (!defined($format_input)) {
$format_input = ($input =~ /[\000-\011\013-\036]/ ? 'binary' : 'text');
print STDERR "$PROG notice: input format is $format_input\n";
}
}
# if we don't know the output format, it's the opposite of the input format:
#
if (!defined($format_output)) {
$format_output = ($format_input eq 'text' ? 'binary' : 'text');
print STDERR "$PROG notice: output format is $format_output\n";
}
# if the output format is binary, --verbose and --all don't make sense:
#
if ($format_output eq 'binary'
&& ($verbose
|| $all)) {
print STDERR "$PROG error: --verbose and --all don't make sense for binary output\n";
exit (1);
}
# if our input is text:
#
if ($format_input eq 'text') {
# prepend the default input to the input, to provide values for
# any names that the user doesn't provide:
#
$input = $input_default."\n".$input;
# process the lines of the input:
#
for ($line_start = 0;
$line_start < length($input); ) {
# get the offset of the next line separator:
#
$line_end = index($input, "\n", $line_start);
if ($line_end < 0) {
$line_end = length($input) + 1;
}
# get the next line:
#
$_ = substr($input, $line_start, $line_end - $line_start);
$line_start = $line_end + 1;
# ignore comments and blank lines:
#
if ($_ !~ /\S/ || /^\s*\#/) {
next;
}
# tokenize this line:
#
($name, $value) = split(' ', $_, 2);
# if this name is unknown:
#
if (!defined($name_to_offset{$name})) {
print STDERR "$PROG error: unknown name `$name'\n";
exit (1);
}
# save this value:
#
$name_to_value{$name} = $value;
}
}
# otherwise, if our input is binary:
#
elsif ($format_input eq 'binary') {
# extract values from the image:
#
foreach $name (@names) {
# get this name's type, function, count, and offset:
#
$type = $name_to_type{$name};
$func = $name_to_func{$name};
$count = $name_to_count{$name};
$offset = $name_to_offset{$name};
# unpack this value:
#
eval("\$value = \&type_${func}_unpack(\$type, \$count, substr(\$input, \$offset));");
# save this value:
#
$name_to_value{$name} = $value;
}
}
# loop over the names:
#
$image = "";
foreach $name (@names) {
# get everything about this name:
#
$type = $name_to_type{$name};
$func = $name_to_func{$name};
$count = $name_to_count{$name};
$offset = $name_to_offset{$name};
$value = $name_to_value{$name};
eval("\@values = \&type_${func}_values(\$type, \$count, \$name_to_values{\$name});");
# pack the possibilities and get any aliases:
#
@aliases = ();
@packeds = ();
undef($wild_alias);
foreach $_ (@values) {
# strip any alias:
#
if (/^(.*)=([^=]+)$/) {
$_ = $1;
push (@aliases, $2);
}
else {
push (@aliases, '');
}
# if this is the wildcard:
#
if ($_ eq '*'
&& $aliases[$#aliases] ne '') {
$wild_alias = $aliases[$#aliases];
push(@packeds, '');
}
# otherwise, this is not the wildcard:
#
else {
# this value must pack:
#
eval("(\$bad, \$packed) = \&type_${func}_pack(\$type, \$count, \$_);");
if (defined($bad)
|| !defined($packed)) {
print STDERR "$PROG internal error: bad value for $name ($_)\n";
exit (1);
}
push (@packeds, $packed);
}
}
# try to pack this value:
#
eval("(\$value_packed_bad, \$value_packed) = \&type_${func}_pack(\$type, \$count, \$value);");
# see if this value is on the list of possibilities, and is an
# alias or has an alias:
#
$value_ok = 0;
$value_alias = '';
for ($value_i = 0; $value_i < @values; $value_i++) {
# if this possibility has an alias, and the given value matches
# the alias, stop now:
#
if ($aliases[$value_i] ne ''
&& $value eq $aliases[$value_i]) {
$value_ok = 1;
$value_alias = $aliases[$value_i];
$value_packed = $packeds[$value_i];
last;
}
# if this value packed, and it matches this packed
# possibility, remember that this value is on the list of
# possibilities, and any alias:
#
if (!defined($value_packed_bad)
&& $value_packed eq $packeds[$value_i]) {
$value_ok = 1;
$value_alias = $aliases[$value_i];
}
}
# if there is a list of possible values:
#
if (@values > 1) {
# if this value isn't one of them:
#
if (!$value_ok) {
# if the wildcard is accepted:
#
if ($wild_alias ne '') {
$value_alias = $wild_alias;
}
# otherwise, complain:
#
else {
print STDERR "$PROG error: bad value `$value' for $name, must be one of:";
for ($value_i = 0; $value_i < @values; $value_i++) {
print STDERR ' '.($aliases[$value_i] ne '' ? $aliases[$value_i] : $values[$value_i]);
}
if (defined($value_packed_bad)) {
print STDERR " (bad $value_packed_bad)";
}
print STDERR "\n";
exit (1);
}
}
}
# otherwise, there isn't a list of possible values. if this value
# failed to pack:
#
elsif (defined($value_packed_bad)) {
print STDERR "$PROG error: bad value `$value' for $name\n";
exit (1);
}
# if our output is text:
#
if ($format_output eq 'text') {
# display this variable if it's not normally hidden, or if
# we're displaying all variables:
#
if ($name !~ /^\./ || $all) {
# if we're being verbose, display this variable's comment:
#
if ($verbose) {
print $comments[$name_to_comments{$name}];
$comments[$name_to_comments{$name}] = '';
}
# display the variable and its alias or value:
#
print "$name ".($value_alias ne '' ? $value_alias : $value)."\n";
}
}
# otherwise, if our output is binary:
#
else {
# add this packed value to the image:
#
if (length($image) < ($offset + length($value_packed))) {
$image .= pack('C', 0) x ($offset + length($value_packed) - length($image));
}
substr($image, $offset, length($value_packed)) = $value_packed;
}
}
# if our output is binary, output the image:
#
if ($format_output eq 'binary') {
print $image;
}
# done:
#
exit(0);
# this parses a set of integral values:
#
sub type_generic_integral_values {
my ($type, $count, $values) = @_;
if (!defined($values)) {
('');
}
else {
split(' ', $values);
}
}
# this returns the Perl pack template character for an integral type:
#
sub type_generic_integral_template {
my ($type) = @_;
if ($type =~ /^generic_char_/) {
$type = 'C';
}
elsif ($type =~ /^generic_shorteb_/) {
$type = 'n';
}
elsif ($type =~ /^generic_longeb_/) {
$type = 'N';
}
else {
print STDERR "$PROG fatal: unknown integral type $type\n";
exit (1);
}
$type;
}
# this packs an integral value:
#
sub type_generic_integral_pack {
my ($type, $count, $value) = @_;
my ($template, $bad, @parts);
@parts = split(/,/, $value);
for (; @parts < $count; ) { push(@parts, '0'); }
foreach (@parts) {
if (/^0x[0-9A-Fa-f]+$/) {
$_ = hex($_) + 0;
}
elsif (/^\'(.)\'$/) {
$_ = ord($_) + 0;
}
elsif (/^\d+$/) {
$_ += 0;
}
else {
$bad = $_;
$_ = 0;
}
}
$template = &type_generic_integral_template($type);
($bad, pack("$template$count", @parts));
}
# this unpacks an integral value:
#
sub type_generic_integral_unpack {
my ($type, $count, $packed) = @_;
my ($template, @parts);
$template = &type_generic_integral_template($type);
@parts = unpack("$template$count", $packed);
for (; @parts > ($count > 1 ? 0 : 1) && $parts[$#parts] == 0; ) { pop(@parts); }
if ($type =~ /_hex$/) {
foreach (@parts) {
$_ = sprintf("0x%0".(length(pack($template, 0)) * 2)."x", $_);
}
}
else {
foreach (@parts) {
$_ = "$_";
}
}
join(',', @parts);
}
# this parses a set of generic string buffer values:
#
sub type_generic_string_buffer_values {
if (!defined($values)) {
$values = '';
}
($values);
}
# this packs a generic string buffer value:
#
sub type_generic_string_buffer_pack {
my ($type, $count, $value) = @_;
my ($bad);
if (length($value) < $count) {
$value .= pack('C', 0) x ($count - length($value));
}
elsif (length($value) > $count) {
$bad = $value;
}
($bad, $value);
}
# this unpacks a generic string buffer value:
#
sub type_generic_string_buffer_unpack {
my ($type, $count, $packed) = @_;
$lc = index($packed, pack('C', 0));
if ($lc >= 0) {
$packed = substr($packed, 0, $lc);
}
$packed;
}