123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- #!/usr/bin/perl -w
- use strict;
- use FileHandle;
- use integer;
-
- sub unsigned_little_endian_to_value
- {
- # Assumes the data is initially little endian
- my ($buffer) = @_;
- my $bytes = length($buffer);
- my $value = 0;
- my $i;
- for($i = $bytes -1; $i >= 0; $i--) {
- my $byte = unpack('C', substr($buffer, $i, 1));
- $value = ($value * 256) + $byte;
- }
- return $value;
- }
-
- sub decode_fixed_string
- {
- my ($data, $bytes) = @_;
- return $data;
- }
-
- sub decode_pstring
- {
- my ($buf_ref, $offset_ref) = @_;
- # Decode a pascal string
- my $offset = ${$offset_ref};
- my $len = unpack('C',substr(${$buf_ref}, $offset, 1));
- my $data = substr(${$buf_ref}, $offset +1, $len);
- ${$offset_ref} = $offset + $len +1;
- return $data;
- }
-
- sub decode_cstring
- {
- # Decode a c string
- my ($buf_ref, $offset_ref) = @_;
- my ($data, $byte);
- my $index = ${$offset_ref};
- while(1) {
- $byte = substr(${$buf_ref}, $index, 1);
- if (!defined($byte) || ($byte eq "\0")) {
- last;
- }
- $data .= $byte;
- $index++;
- }
- ${$offset_ref} = $index;
- return $data;
- }
-
- sub type_size
- {
- my ($entry) = @_;
- my %type_length = (
- byte => 1,
- half => 2,
- word => 4,
- xword => 8,
- 'fixed-string' => $entry->[2],
- pstring => 0,
- cstring => 0,
- );
- my $type = $entry->[0];
- if (!exists($type_length{$type})) {
- die "unknown type $type";
- }
- my $length = $type_length{$type};
- return $length;
- }
-
- sub decode_fixed_type
- {
- my ($type, $data, $bytes) = @_;
- my %decoders = (
- 'byte' => \&unsigned_little_endian_to_value,
- 'half' => \&unsigned_little_endian_to_value,
- 'word' => \&unsigned_little_endian_to_value,
- 'xword' => \&unsigned_little_endian_to_value,
- 'fixed-string' => \&decode_fixed_string,
- );
- my $decoder = $decoders{$type} or die "unknow fixed type $type";
- return $decoder->($data, $bytes);
- }
-
- sub decode_variable_type
- {
- my ($type, $buf_ref, $offset_ref) = @_;
- my %decoders = (
- 'pstring' => \&decode_pstring,
- 'cstring' => \&decode_cstring,
- );
- my $decoder = $decoders{$type} or die "unknow variable type $type";
- return $decoder->($buf_ref, $offset_ref);
- }
-
- sub decode_struct
- {
- my ($buf_ref, $offset, $layout) = @_;
- my $initial_offset = $offset;
- my ($entry, %results);
- foreach $entry (@$layout) {
- my ($type, $name) = @$entry;
- my $bytes = type_size($entry);
- if ($bytes > 0) {
- my $data = substr(${$buf_ref}, $offset, $bytes);
- $results{$name} = decode_fixed_type($type, $data, $bytes);
- $offset += $bytes;
- } else {
- $results{$name} = decode_variable_type($type, $buf_ref, \$offset);
- }
- }
- return (\%results, $offset - $initial_offset);
- }
-
- sub print_big_hex
- {
- my ($min_digits, $value) = @_;
- my @digits;
- while($min_digits > 0 || ($value > 0)) {
- my $digit = $value%16;
- $value /= 16;
- unshift(@digits, $digit);
- $min_digits--;
- }
- my $digit;
- foreach $digit (@digits) {
- printf("%01x", $digit);
- }
- }
-
-
-
- my %lha_signatures = (
- '-com-' => 1,
- '-lhd-' => 1,
- '-lh0-' => 1,
- '-lh1-' => 1,
- '-lh2-' => 1,
- '-lh3-' => 1,
- '-lh4-' => 1,
- '-lh5-' => 1,
- '-lzs-' => 1,
- '-lz4-' => 1,
- '-lz5-' => 1,
- '-afx-' => 1,
- '-lzf-' => 1,
- );
-
- my %lha_os = (
- 'M' => 'MS-DOS',
- '2' => 'OS/2',
- '9' => 'OS9',
- 'K' => 'OS/68K',
- '3' => 'OS/386',
- 'H' => 'HUMAN',
- 'U' => 'UNIX',
- 'C' => 'CP/M',
- 'F' => 'FLEX',
- 'm' => 'Mac',
- 'R' => 'Runser',
- 'T' => 'TownOS',
- 'X' => 'XOSK',
- 'A' => 'Amiga',
- 'a' => 'atari',
- ' ' => 'Award ROM',
- );
-
-
- my @lha_level_1_header = (
- [ 'byte', 'header_size' ], # 1
- [ 'byte', 'header_sum', ], # 2
- [ 'fixed-string', 'method_id', 5 ], # 7
- [ 'word', 'skip_size', ], # 11
- [ 'word', 'original_size' ], # 15
- [ 'half', 'dos_time' ], # 17
- [ 'half', 'dos_date' ], # 19
- [ 'byte', 'fixed' ], # 20
- [ 'byte', 'level' ], # 21
- [ 'pstring', 'filename' ], # 22
- [ 'half', 'crc' ],
- [ 'fixed-string', 'os_id', 1 ],
- [ 'half', 'ext_size' ],
- );
-
- # General lha_header
- my @lha_header = (
- [ 'byte', 'header_size' ],
- [ 'byte', 'header_sum', ],
- [ 'fixed-string', 'method_id', 5 ],
- [ 'word', 'skip_size', ],
- [ 'word', 'original_size' ],
- [ 'half', 'dos_time' ],
- [ 'half', 'dos_date' ],
- [ 'half', 'rom_addr' ],
- [ 'half', 'rom_flags' ],
- [ 'byte', 'fixed' ],
- [ 'byte', 'level' ],
- [ 'pstring', 'filename' ],
- [ 'half', 'crc' ],
- [ 'lha_os', 'os_id', 1 ],
- [ 'half', 'ext_size' ],
- [ 'byte', 'zero' ],
- [ 'byte', 'total_checksum' ],
- [ 'half', 'total_size' ],
- );
-
- sub print_struct
- {
- my ($layout, $self) = @_;
- my $entry;
- my $width = 0;
- foreach $entry(@$layout) {
- my ($type, $name) = @$entry;
- if (length($name) > $width) {
- $width = length($name);
- }
- }
- foreach $entry (@$layout) {
- my ($type, $name) = @$entry;
- printf("%*s = ", $width, $name);
- my $value = $self->{$name};
- if (!defined($value)) {
- print "undefined";
- }
- elsif ($type eq "lha_os") {
- print "$lha_os{$value}";
- }
- elsif ($type =~ m/string/) {
- print "$value";
- }
- else {
- my $len = type_size($entry);
- print "0x";
- print_big_hex($len *2, $value);
- }
- print "\n";
- }
- }
-
- sub checksum
- {
- my ($buf_ref, $offset, $length) = @_;
- my ($i, $sum);
- $sum = 0;
- for($i = 0; $i < $length; $i++) {
- my $byte = unpack('C', substr($$buf_ref, $offset + $i, 1));
- $sum = ($sum + $byte) %256;
- }
- return $sum;
- }
-
- sub decode_lha_header
- {
- my ($buf_ref, $offset) = @_;
- my $level = unpack('C',substr(${$buf_ref}, $offset + 20, 1));
-
- my %self;
- my ($struct, $bytes);
- if ($level == 1) {
- ($struct, $bytes)
- = decode_struct($buf_ref, $offset, \@lha_level_1_header);
- %self = %$struct;
- if ($self{fixed} != 0x20) {
- die "bad fixed value";
- }
- $self{total_size} = $self{header_size} + 2 + $self{skip_size};
- if ($bytes != $self{header_size} +2) {
- die "$bytes != $self{header_size} +2";
- }
- my $checksum = checksum($buf_ref, $offset +2, $self{header_size});
- if ($checksum != $self{header_sum}) {
- printf("WARN: Header bytes checksum to %02lx\n",
- $checksum);
- }
- # If we are an award rom...
- if ($self{os_id} eq ' ') {
- @self{qw(zero total_checksum)} =
- unpack('CC', substr($$buf_ref,
- $offset + $self{total_size}, 2));
- if ($self{zero} != 0) {
- warn "Award ROM without trailing zero";
- }
- else {
- $self{total_size}++;
- }
- my $checksum =
- checksum($buf_ref, $offset, $self{total_size});
- if ($self{total_checksum} != $checksum) {
- printf("WARN: Image bytes checksum to %02lx\n",
- $checksum);
- }
- else {
- $self{total_size}++;
- }
- $self{rom_addr} = $self{dos_time};
- $self{rom_flags} = $self{dos_date};
- delete @self{qw(dos_time dos_date)};
- }
- }
- else {
- die "Unknown header type";
- }
- return \%self;
- }
-
- sub main
- {
- my ($filename, $rom_length) = @_;
- my $fd = new FileHandle;
- if (!defined($rom_length)) {
- my ($dev, $ino, $mode, $nlink, $uid, $gid,$rdev,$size,
- $atime, $mtime, $ctime, $blksize, $blocks)
- = stat($filename);
- $rom_length = $size;
- }
- $fd->open("<$filename") or die "Cannot ope $filename";
- my $data;
- $fd->read($data, $rom_length);
- $fd->close();
-
- my $i;
- for($i = 0; $i < $rom_length; $i++) {
- my $sig = substr($data, $i, 5);
- if (exists($lha_signatures{$sig})) {
- my $start = $i -2;
- my $header = decode_lha_header(\$data, $start);
-
- my $length = $header->{total_size};
- print "AT: $start - @{[$start + $length -1]}, $length bytes\n";
- print_struct(\@lha_header, $header);
- print "\n";
-
- }
- }
- }
-
- main(@ARGV);
|