|
|
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: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.