Annotation of tme/tools/tme-binary-struct.pl.in, revision 1.1.1.1

1.1       root        1: #! /usr/pkg/bin/perl -w
                      2: 
                      3: # $Id: tme-binary-struct.pl.in,v 1.2 2005/01/14 11:40:50 fredette Exp $
                      4: 
                      5: # tools/tme-binary-struct.pl.in - common framework for scripts that
                      6: # manipulate files containing binary structures:
                      7: #
                      8: 
                      9: # Copyright (c) 2004 Matt Fredette
                     10: # All rights reserved.
                     11: #
                     12: # Redistribution and use in source and binary forms, with or without
                     13: # modification, are permitted provided that the following conditions
                     14: # are met:
                     15: # 1. Redistributions of source code must retain the above copyright
                     16: #    notice, this list of conditions and the following disclaimer.
                     17: # 2. Redistributions in binary form must reproduce the above copyright
                     18: #    notice, this list of conditions and the following disclaimer in the
                     19: #    documentation and/or other materials provided with the distribution.
                     20: # 3. All advertising materials mentioning features or use of this software
                     21: #    must display the following acknowledgement:
                     22: #      This product includes software developed by Matt Fredette.
                     23: # 4. The name of the author may not be used to endorse or promote products
                     24: #    derived from this software without specific prior written permission.
                     25: #
                     26: # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
                     27: # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
                     28: # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
                     29: # DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
                     30: # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
                     31: # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
                     32: # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
                     33: # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
                     34: # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
                     35: # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
                     36: # POSSIBILITY OF SUCH DAMAGE.
                     37: 
                     38: # silence perl -w:
                     39: #
                     40: undef($bad);
                     41: undef($packed);
                     42: undef(%name_to_values);
                     43: 
                     44: # globals:
                     45: #
                     46: $0 =~ /^(.*\/)?([^\/]+)$/; $PROG = $2;
                     47: 
                     48: # check our command line:
                     49: #
                     50: $usage = 0;
                     51: $verbose = 0;
                     52: $all = 0;
                     53: undef($format_input);
                     54: undef($format_output);
                     55: for (; @ARGV > 0 && $ARGV[0] =~ /^-/; ) {
                     56:     $option = shift(@ARGV);
                     57:     if ($option eq '--verbose') {
                     58:        $verbose++;
                     59:     }
                     60:     elsif ($option eq '--all') {
                     61:        $all = 1;
                     62:     }
                     63:     elsif ($option =~ /^--format-input=(\S+)$/) {
                     64:        $format_input = $1;
                     65:     }
                     66:     elsif ($option =~ /^--format-output=(\S+)$/) {
                     67:        $format_output = $1;
                     68:     }
                     69:     else {
                     70:        if ($option ne "-h"
                     71:            && $option ne "--help"
                     72:            && $option ne "-?") {
                     73:            print STDERR "$PROG error: unknown option `$option'\n";
                     74:        }
                     75:        $usage = 1;
                     76:        last;
                     77:     }
                     78: }
                     79: if (defined($format_input)
                     80:     && $format_input ne 'text'
                     81:     && $format_input ne 'binary') {
                     82:     print STDERR "$PROG error: unknown input format $format_input\n";
                     83:     $usage = 1;
                     84: }
                     85: if (defined($format_output)
                     86:     && $format_output ne 'text'
                     87:     && $format_output ne 'binary') {
                     88:     print STDERR "$PROG error: unknown input format $format_output\n";
                     89:     $usage = 1;
                     90: }
                     91: if (@ARGV > 0) {
                     92:     print STDERR "$PROG error: `$ARGV[0]' unexpected\n";
                     93:     $usage = 1;
                     94: }
                     95: if ($usage) {
                     96:     print STDERR <<"EOF;";
                     97: usage: $PROG [ OPTIONS ]
                     98: where OPTIONS are:
                     99:   --verbose                 include comments in text output
                    100:   --all                     display normally hidden fields in text output
                    101:   --format-input=FORMAT     set the input format to FORMAT, one of: text binary
                    102:   --format-output=FORMAT    set the output format to FORMAT, one of: text binary
                    103: EOF;
                    104:     exit (1);
                    105: }
                    106: 
                    107: # the set of related types:
                    108: #
                    109: %types_related = split(/[\r\n\s]+/, <<'EOF;');
                    110: generic_char_hex       generic_integral
                    111: generic_char_dec       generic_integral
                    112: generic_shorteb_hex    generic_integral
                    113: generic_shorteb_dec    generic_integral
                    114: generic_shortel_hex    generic_integral
                    115: generic_shortel_dec    generic_integral
                    116: generic_longeb_hex     generic_integral
                    117: generic_longeb_dec     generic_integral
                    118: generic_longel_hex     generic_integral
                    119: generic_longel_dec     generic_integral
                    120: EOF;
                    121: 
                    122: # get the structure definition:
                    123: #
                    124: $struct_definition = &binary_struct();
                    125: 
                    126: # process the structure definition and make the default input:
                    127: #
                    128: $input_default = "";
                    129: @comments = ("");
                    130: $comments_new = 0;
                    131: for ($line_start = 0;
                    132:      $line_start < length($struct_definition); ) {
                    133: 
                    134:     # get the offset of the next line separator:
                    135:     #
                    136:     $line_end = index($struct_definition, "\n", $line_start);
                    137:     if ($line_end < 0) {
                    138:        $line_end = length($struct_definition) + 1;
                    139:     }
                    140:     
                    141:     # get the next line:
                    142:     #
                    143:     $_ = substr($struct_definition, $line_start, $line_end - $line_start);
                    144:     $line_start = $line_end + 1;
                    145: 
                    146:     # ignore comments and blank lines:
                    147:     #
                    148:     if ($_ !~ /\S/ || /^\s*\#/) {
                    149:        if ($comments_new) {
                    150:            push(@comments, "");
                    151:            $comments_new = 0;
                    152:        }
                    153:        $comments[$#comments] .= $_."\n";
                    154:        next;
                    155:     }
                    156: 
                    157:     # tokenize this line:
                    158:     #
                    159:     ($offset, $name, $type, $values) = split(' ', $_, 4);
                    160: 
                    161:     # make sure this name isn't multiply-defined:
                    162:     #
                    163:     if (defined($name_to_offset{$name})) {
                    164:        print STDERR "$PROG internal error: $name multiply defined\n";
                    165:        exit (1);
                    166:     }
                    167: 
                    168:     # convert the offset:
                    169:     #
                    170:     $offset = hex($offset);
                    171: 
                    172:     # canonicalize the type and count:
                    173:     #
                    174:     if ($type =~ /^(.*\D)(\d+)$/) {
                    175:        ($type, $count) = ($1, $2);
                    176:     }
                    177:     else {
                    178:        $count = 1;
                    179:     }
                    180: 
                    181:     # make sure this type is known:
                    182:     #
                    183:     $func = $types_related{$type};
                    184:     if (!defined($func)) {
                    185:        $func = $type;
                    186:     }
                    187:     unless (eval("defined(\&type_${func}_pack);")) {
                    188:        print STDERR "$PROG internal error: unknown type $func\n";
                    189:        exit (1);
                    190:     }
                    191: 
                    192:     # remember this name:
                    193:     #
                    194:     push (@names, $name);
                    195:     $name_to_offset{$name} = $offset;
                    196:     $name_to_type{$name} = $type;
                    197:     $name_to_count{$name} = $count;
                    198:     $name_to_values{$name} = $values;
                    199:     $name_to_func{$name} = $func;
                    200:     $name_to_comments{$name} = $#comments;
                    201: 
                    202:     # get the default value for this field:
                    203:     #
                    204:     eval("(\$value) = \&type_${func}_values(\$type, \$count, \$values);");
                    205: 
                    206:     # if the default value has an alias, use the alias:
                    207:     #
                    208:     if ($value =~ s/=([^=]+)$//) {
                    209:        $value = $1;
                    210:     }
                    211: 
                    212:     # add this value to the default input:
                    213:     #
                    214:     $input_default .= "$name $value\n";
                    215: 
                    216:     # the next comment starts a new comment:
                    217:     #
                    218:     $comments_new = 1;
                    219: }
                    220: 
                    221: # if our standard input is a terminal:
                    222: #
                    223: if (-t STDIN) {
                    224: 
                    225:     # if the user specified the input format, and it's not text, that's an error:
                    226:     #
                    227:     if (defined($format_input)
                    228:        && $format_input ne 'text') {
                    229:        print STDERR "$PROG error: the input format can't be $format_input when standard input is a terminal\n";
                    230:        exit (1);
                    231:     }
                    232:     $format_input = 'text';
                    233: 
                    234:     # there is no standard input:
                    235:     #
                    236:     $input = "";
                    237: }
                    238: 
                    239: # otherwise, our standard input is not a terminal:
                    240: #
                    241: else {
                    242: 
                    243:     # read in standard input:
                    244:     #
                    245:     $input = "";
                    246:     for (;;) {
                    247:        undef($_);
                    248:        $size = sysread(STDIN, $_, 1024);
                    249:        if (!defined($size)) {
                    250:            print STDERR "fatal: could not read stdin: $!\n";
                    251:            exit (1);
                    252:        }
                    253:        elsif ($size == 0) {
                    254:            last;
                    255:        }
                    256:        $input .= $_;
                    257:     }
                    258: 
                    259:     # if we don't know if the input format is text or binary, try to
                    260:     # figure it out:
                    261:     #
                    262:     if (!defined($format_input)) {
                    263:        $format_input = ($input =~ /[\000-\011\013-\036]/ ? 'binary' : 'text');
                    264:        print STDERR "$PROG notice: input format is $format_input\n";
                    265:     }
                    266: }
                    267: 
                    268: # if we don't know the output format, it's the opposite of the input format:
                    269: #
                    270: if (!defined($format_output)) {
                    271:     $format_output = ($format_input eq 'text' ? 'binary' : 'text');
                    272:     print STDERR "$PROG notice: output format is $format_output\n";
                    273: }
                    274: 
                    275: # if the output format is binary, --verbose and --all don't make sense:
                    276: #
                    277: if ($format_output eq 'binary'
                    278:     && ($verbose
                    279:        || $all)) {
                    280:     print STDERR "$PROG error: --verbose and --all don't make sense for binary output\n";
                    281:     exit (1);
                    282: }
                    283: 
                    284: # if our input is text:
                    285: #
                    286: if ($format_input eq 'text') {
                    287: 
                    288:     # prepend the default input to the input, to provide values for
                    289:     # any names that the user doesn't provide:
                    290:     #
                    291:     $input = $input_default."\n".$input;
                    292: 
                    293:     # process the lines of the input:
                    294:     #
                    295:     for ($line_start = 0;
                    296:         $line_start < length($input); ) {
                    297: 
                    298:        # get the offset of the next line separator:
                    299:        #
                    300:        $line_end = index($input, "\n", $line_start);
                    301:        if ($line_end < 0) {
                    302:            $line_end = length($input) + 1;
                    303:        }
                    304:     
                    305:        # get the next line:
                    306:        #
                    307:        $_ = substr($input, $line_start, $line_end - $line_start);
                    308:        $line_start = $line_end + 1;
                    309: 
                    310:        # ignore comments and blank lines:
                    311:        #
                    312:        if ($_ !~ /\S/ || /^\s*\#/) {
                    313:            next;
                    314:        }
                    315:     
                    316:        # tokenize this line:
                    317:        #
                    318:        ($name, $value) = split(' ', $_, 2);
                    319: 
                    320:        # if this name is unknown:
                    321:        #
                    322:        if (!defined($name_to_offset{$name})) {
                    323:            print STDERR "$PROG error: unknown name `$name'\n";
                    324:            exit (1);
                    325:        }
                    326: 
                    327:        # save this value:
                    328:        #
                    329:        $name_to_value{$name} = $value;
                    330:     }
                    331: }
                    332: 
                    333: # otherwise, if our input is binary:
                    334: #
                    335: elsif ($format_input eq 'binary') {
                    336: 
                    337:     # extract values from the image:
                    338:     #
                    339:     foreach $name (@names) {
                    340: 
                    341:        # get this name's type, function, count, and offset:
                    342:        #
                    343:        $type = $name_to_type{$name};
                    344:        $func = $name_to_func{$name};
                    345:        $count = $name_to_count{$name};
                    346:        $offset = $name_to_offset{$name};
                    347: 
                    348:        # unpack this value:
                    349:        #
                    350:        eval("\$value = \&type_${func}_unpack(\$type, \$count, substr(\$input, \$offset));");
                    351:        
                    352:        # save this value:
                    353:        #
                    354:        $name_to_value{$name} = $value;
                    355:     }
                    356: }
                    357: 
                    358: # loop over the names:
                    359: #
                    360: $image = "";
                    361: foreach $name (@names) {
                    362: 
                    363:     # get everything about this name:
                    364:     #
                    365:     $type = $name_to_type{$name};
                    366:     $func = $name_to_func{$name};
                    367:     $count = $name_to_count{$name};
                    368:     $offset = $name_to_offset{$name};
                    369:     $value = $name_to_value{$name};
                    370:     eval("\@values = \&type_${func}_values(\$type, \$count, \$name_to_values{\$name});");
                    371: 
                    372:     # pack the possibilities and get any aliases:
                    373:     #
                    374:     @aliases = ();
                    375:     @packeds = ();
                    376:     undef($wild_alias);
                    377:     foreach $_ (@values) {
                    378:        
                    379:        # strip any alias:
                    380:        #
                    381:        if (/^(.*)=([^=]+)$/) {
                    382:            $_ = $1;
                    383:            push (@aliases, $2);
                    384:        }
                    385:        else {
                    386:            push (@aliases, '');
                    387:        }
                    388: 
                    389:        # if this is the wildcard:
                    390:        #
                    391:        if ($_ eq '*'
                    392:            && $aliases[$#aliases] ne '') {
                    393:            $wild_alias = $aliases[$#aliases];
                    394:            push(@packeds, '');
                    395:        }
                    396: 
                    397:        # otherwise, this is not the wildcard:
                    398:        #
                    399:        else {
                    400: 
                    401:            # this value must pack:
                    402:            #
                    403:            eval("(\$bad, \$packed) = \&type_${func}_pack(\$type, \$count, \$_);");
                    404:            if (defined($bad)
                    405:                || !defined($packed)) {
                    406:                print STDERR "$PROG internal error: bad value for $name ($_)\n";
                    407:                exit (1);
                    408:            }
                    409:            push (@packeds, $packed);
                    410:        }
                    411:     }
                    412: 
                    413:     # try to pack this value:
                    414:     #
                    415:     eval("(\$value_packed_bad, \$value_packed) = \&type_${func}_pack(\$type, \$count, \$value);");
                    416: 
                    417:     # see if this value is on the list of possibilities, and is an
                    418:     # alias or has an alias:
                    419:     #
                    420:     $value_ok = 0;
                    421:     $value_alias = '';
                    422:     for ($value_i = 0; $value_i < @values; $value_i++) {
                    423:        
                    424:        # if this possibility has an alias, and the given value matches
                    425:        # the alias, stop now:
                    426:        #
                    427:        if ($aliases[$value_i] ne ''
                    428:            && $value eq $aliases[$value_i]) {
                    429:            $value_ok = 1;
                    430:            $value_alias = $aliases[$value_i];
                    431:            $value_packed = $packeds[$value_i];
                    432:            last;
                    433:        }
                    434: 
                    435:        # if this value packed, and it matches this packed
                    436:        # possibility, remember that this value is on the list of
                    437:        # possibilities, and any alias:
                    438:        #
                    439:        if (!defined($value_packed_bad)
                    440:            && $value_packed eq $packeds[$value_i]) {
                    441:            $value_ok = 1;
                    442:            $value_alias = $aliases[$value_i];
                    443:        }
                    444:     }
                    445: 
                    446:     # if there is a list of possible values:
                    447:     #
                    448:     if (@values > 1) {
                    449: 
                    450:        # if this value isn't one of them:
                    451:        #
                    452:        if (!$value_ok) {
                    453: 
                    454:            # if the wildcard is accepted:
                    455:            #
                    456:            if ($wild_alias ne '') {
                    457:                $value_alias = $wild_alias;
                    458:            }
                    459: 
                    460:            # otherwise, complain:
                    461:            #
                    462:            else {
                    463:                print STDERR "$PROG error: bad value `$value' for $name, must be one of:";
                    464:                for ($value_i = 0; $value_i < @values; $value_i++) {
                    465:                    print STDERR ' '.($aliases[$value_i] ne '' ? $aliases[$value_i] : $values[$value_i]);
                    466:                }
                    467:                if (defined($value_packed_bad)) {
                    468:                    print STDERR " (bad $value_packed_bad)";
                    469:                }
                    470:                print STDERR "\n";
                    471:                exit (1);
                    472:            }
                    473:        }
                    474:     }
                    475: 
                    476:     # otherwise, there isn't a list of possible values.  if this value
                    477:     # failed to pack:
                    478:     #
                    479:     elsif (defined($value_packed_bad)) {
                    480:        print STDERR "$PROG error: bad value `$value' for $name\n";
                    481:        exit (1);
                    482:     }
                    483: 
                    484:     # if our output is text:
                    485:     #
                    486:     if ($format_output eq 'text') {
                    487: 
                    488:        # display this variable if it's not normally hidden, or if
                    489:        # we're displaying all variables:
                    490:        #
                    491:        if ($name !~ /^\./ || $all) {
                    492:            
                    493:            # if we're being verbose, display this variable's comment:
                    494:            #
                    495:            if ($verbose) {
                    496:                print $comments[$name_to_comments{$name}];
                    497:                $comments[$name_to_comments{$name}] = '';
                    498:            }
                    499: 
                    500:            # display the variable and its alias or value:
                    501:            #
                    502:            print "$name ".($value_alias ne '' ? $value_alias : $value)."\n";
                    503:        }
                    504:     }
                    505: 
                    506:     # otherwise, if our output is binary:
                    507:     #
                    508:     else {
                    509: 
                    510:        # add this packed value to the image:
                    511:        #
                    512:        if (length($image) < ($offset + length($value_packed))) {
                    513:            $image .= pack('C', 0) x ($offset + length($value_packed) - length($image));
                    514:        }
                    515:        substr($image, $offset, length($value_packed)) = $value_packed;
                    516:     }
                    517: }
                    518: 
                    519: # if our output is binary, output the image:
                    520: #
                    521: if ($format_output eq 'binary') {
                    522:     print $image;
                    523: }
                    524: 
                    525: # done:
                    526: #
                    527: exit(0);
                    528: 
                    529: # this parses a set of integral values:
                    530: #
                    531: sub type_generic_integral_values {
                    532:     my ($type, $count, $values) = @_;
                    533:     if (!defined($values)) {
                    534:        ('');
                    535:     }
                    536:     else {
                    537:        split(' ', $values);
                    538:     }
                    539: }
                    540: 
                    541: # this returns the Perl pack template character for an integral type:
                    542: #
                    543: sub type_generic_integral_template {
                    544:     my ($type) = @_;
                    545: 
                    546:     if ($type =~ /^generic_char_/) {
                    547:        $type = 'C';
                    548:     }
                    549:     elsif ($type =~ /^generic_shorteb_/) {
                    550:        $type = 'n';
                    551:     }
                    552:     elsif ($type =~ /^generic_longeb_/) {
                    553:        $type = 'N';
                    554:     }
                    555:     else {
                    556:        print STDERR "$PROG fatal: unknown integral type $type\n";
                    557:        exit (1);
                    558:     }
                    559:     $type;
                    560: }
                    561: 
                    562: # this packs an integral value:
                    563: #
                    564: sub type_generic_integral_pack {
                    565:     my ($type, $count, $value) = @_;
                    566:     my ($template, $bad, @parts);
                    567:     
                    568:     @parts = split(/,/, $value);
                    569:     for (; @parts < $count; ) { push(@parts, '0'); }
                    570:     foreach (@parts) {
                    571:        if (/^0x[0-9A-Fa-f]+$/) {
                    572:            $_ = hex($_) + 0;
                    573:        }
                    574:        elsif (/^\'(.)\'$/) {
                    575:            $_ = ord($_) + 0;
                    576:        }
                    577:        elsif (/^\d+$/) {
                    578:            $_ += 0;
                    579:        }
                    580:        else {
                    581:            $bad = $_;
                    582:            $_ = 0;
                    583:        }
                    584:     }
                    585:     $template = &type_generic_integral_template($type);
                    586:     ($bad, pack("$template$count", @parts));
                    587: }
                    588: 
                    589: # this unpacks an integral value:
                    590: #
                    591: sub type_generic_integral_unpack {
                    592:     my ($type, $count, $packed) = @_;
                    593:     my ($template, @parts);
                    594:     
                    595:     $template = &type_generic_integral_template($type);
                    596:     @parts = unpack("$template$count", $packed);
                    597:     for (; @parts > ($count > 1 ? 0 : 1) && $parts[$#parts] == 0; ) { pop(@parts); }
                    598:     if ($type =~ /_hex$/) {
                    599:        foreach (@parts) {
                    600:            $_ = sprintf("0x%0".(length(pack($template, 0)) * 2)."x", $_);
                    601:        }
                    602:     }
                    603:     else {
                    604:        foreach (@parts) {
                    605:            $_ = "$_";
                    606:        }
                    607:     }
                    608:     join(',', @parts);
                    609: }
                    610: 
                    611: # this parses a set of generic string buffer values:
                    612: #
                    613: sub type_generic_string_buffer_values {
                    614:     if (!defined($values)) {
                    615:        $values = '';
                    616:     }
                    617:     ($values);
                    618: }
                    619: 
                    620: # this packs a generic string buffer value:
                    621: #
                    622: sub type_generic_string_buffer_pack {
                    623:     my ($type, $count, $value) = @_;
                    624:     my ($bad);
                    625:     if (length($value) < $count) {
                    626:        $value .= pack('C', 0) x ($count - length($value));
                    627:     }
                    628:     elsif (length($value) > $count) {
                    629:        $bad = $value;
                    630:     }
                    631:     ($bad, $value);
                    632: }
                    633: 
                    634: # this unpacks a generic string buffer value:
                    635: #
                    636: sub type_generic_string_buffer_unpack {
                    637:     my ($type, $count, $packed) = @_;
                    638:     $lc = index($packed, pack('C', 0));
                    639:     if ($lc >= 0) {
                    640:        $packed = substr($packed, 0, $lc);
                    641:     }
                    642:     $packed;
                    643: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.