|
|
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.