#!perl -w # (C) 2003-2007 Willem Jan Hengeveld # Web: http://www.xs4all.nl/~itsme/ # http://wiki.xda-developers.com/ # # $Id: yakumo-img-info.pl 1502 2007-04-15 07:54:20Z itsme $ # use strict; use integer; use IO::File; use IO::Dir; use POSIX; use Getopt::Long; $|=1; my @mainsections= qw(osrom step usb diag eboot oem); sub usage { return <<__EOF__; Usage: p300img [createoptions | -x] imagename -load PART=FILENAME construct image from parts PART must be one of: osrom, step, usb, diag, eboot, oem, logo, version, timestamp, flag -x extract sections from image -d OEMDIR extract oem files to directory __EOF__ } my %g_input; my $action; my $g_oemdir; GetOptions( "load=s"=> \%g_input, "x" => sub { $action= \&do_extract; }, "d=s" => sub { $g_oemdir= $_[1]; }, ) or die usage(); my $fn= shift or die usage(); if (keys %g_input) { die "specify either extract, or create options\n" if ($action); $g_input{version} ||= ""; $g_input{timestamp} ||= POSIX::strftime("%Y/%m/%d %H:%M", localtime time); $action= \&do_create; } elsif (!$action) { die "can only specify -d with extract or create\n" if ($g_oemdir); $action= \&do_list } die "$g_oemdir does not exist\n" if ($g_oemdir && ! -e $g_oemdir); die "$g_oemdir is not a directory\n" if ($g_oemdir && ! -d $g_oemdir); $action->($fn); exit(0); sub addtolensum { my ($ls, $len, $sum)= @_; $ls->[0] += $len; $ls->[1] += $sum; } sub addtolen { my ($ls, $len, $sum)= @_; $ls->[0] += $len; } sub checksum { my $data= shift; my $ldata= length(ref $data ? $$data : $data); my $sum= 0; for (my $i = 0 ; $i < $ldata ; $i+=1024) { my @vals= unpack("V*", substr((ref $data ? $$data : $data), $i, 1024)); for (@vals) { if ($_ != 0xffffffff) { $sum += $_&0xff; } } } return $sum; } sub readfile { my $fn= shift; my $data= shift; die "readfile: no filename\n" if (!$fn); my $fh= IO::File->new($fn, "r") or die "$fn: $!\n"; binmode $fh; $fh->read($$data, -s $fn); $fh->close(); } sub write_logo_version { my $fh= shift; my @lensum= (0,0); my $data; readfile($g_input{logo}, \$data); if (length($data) > 0x7e00) { die "error creating image, logo is too large\n"; } addtolensum(\@lensum, printblock($fh, $data, 0x7e00, "\x00\x00\x00\x00")); # only the jpg is checksummed. addtolen(\@lensum, printblock($fh, $g_input{version}, 0x40, "\x00\x00\x00\x00")); addtolen(\@lensum, printblock($fh, $g_input{timestamp}, 0x40, "\x00\x00\x00\x00")); addtolen(\@lensum, printblock($fh, pack("VV", 0x45e, 0x45e), 0x180, "\xff\xff\xff\xff")); return @lensum; } sub write_oem_data { my ($fh, $dirname)= @_; my @lensum= (0,0); my $dh= IO::Dir->new($dirname) or die "$dirname: $!\n"; my @files= grep { -f "$dirname/$_" } $dh->read(); $dh->close(); my $oemhdr= pack("V", scalar @files); $fh->print($oemhdr); addtolensum(\@lensum, length($oemhdr), checksum($oemhdr)); for (@files) { my $data; readfile("$dirname/$_", \$data); my $filehdr= pack("a260V", $_, length($data)); $fh->print($filehdr); $fh->print($data); addtolensum(\@lensum, length($filehdr), checksum($filehdr)); addtolensum(\@lensum, length($data), checksum(\$data)); } my $blocksize= 0x8000; my $padding = ($lensum[0] % $blocksize) ? $blocksize - ($lensum[0] % $blocksize) : 0; my $pad= "\xff" x $padding; $fh->print($pad); printf("eof= %08lx\n", $fh->tell()); # todo: figure out if padding counts for checksum. addtolensum(\@lensum, $padding, 0); return @lensum; } sub printblock { my ($fh, $data, $blocksize, $paddata)= @_; $fh->print(ref $data ? $$data : $data); my $ldata= length(ref $data ? $$data : $data); my ($len, $sum)= ($ldata, checksum($data)); my $padding = ($ldata % $blocksize) ? $blocksize - ($ldata % $blocksize) : 0; if (defined $paddata && $padding==0 && $ldata==0) { # when padding specified, allways write blocksize data. $padding= $blocksize; } if (!defined $paddata || $paddata =~ /^\x00+$/) { $fh->seek($padding, SEEK_CUR); } else { my $pad= substr($paddata x ($padding / length($paddata)+1), 0, $padding); $fh->print($pad); # todo: figure out if padding counts for checksum. #$sum += checksum($pad); } $len += $padding; return ($len, $sum); } sub do_create { my $fn= shift; die "do_create: no filename\n" if (!$fn); my $fh= IO::File->new($fn, "w") or die "$fn: $!\n"; binmode $fh; my @lengths; my @checksums; $fh->seek(0x38, SEEK_SET); for (@mainsections) { if ($_ eq "oem") { # insert bootsplash + version info before oem area my @lensum= (0,0); addtolensum(\@lensum, write_logo_version($fh)); # oem data is not checksummed. addtolen(\@lensum, write_oem_data($fh, $g_input{$_})); push @lengths, $lensum[0]; push @checksums, $lensum[1]; } elsif (exists $g_input{$_}) { my $data; readfile($g_input{$_}, \$data); my ($len, $sum)= printblock($fh, \$data, $_ eq "step" ? 0x4000 : 0x200, "\xff\xff\xff\xff"); push @lengths, $len; push @checksums, $sum; } else { push @lengths, 0; push @checksums, 0; } } my $mask=0; $mask |= 0x01 if (exists $g_input{osrom}); $mask |= 0x02 if (exists $g_input{step}); $mask |= 0x04 if (exists $g_input{usb}); $mask |= 0x08 if (exists $g_input{diag}); $mask |= 0x10 if (exists $g_input{eboot}); $mask |= 0x20 if (exists $g_input{logo}); $mask |= 0x40 if (exists $g_input{oem}); $mask |= 0x80 if (exists $g_input{flag}); my $hdr= pack("V*", 0x4e455445, $mask, @lengths, @checksums); $fh->seek(0, SEEK_SET); $fh->print($hdr); $fh->seek(0, SEEK_END); $fh->close(); } sub do_extract { my $fn= shift; die "do_extract: no filename\n" if (!$fn); my $fh= IO::File->new($fn, "r") or die "$fn: $!\n"; binmode $fh; my $hdr; $fh->read($hdr, 0x38) or die "$fn: $!\n"; my %lengths; my %checksums; my ($magic, $mask); ($magic, $mask, @lengths{@mainsections}, @checksums{@mainsections}) = unpack("V*", $hdr); if ($magic!=0x4e455445) { printf("not a ETEN image file\n"); exit(0); } my %offsets= (osrom=>0x38); for (1..$#mainsections) { $offsets{$mainsections[$_]} = $offsets{$mainsections[$_-1]} + $lengths{$mainsections[$_-1]}; } my $end_of_file= $offsets{$mainsections[-1]} + $lengths{$mainsections[-1]}; my @extractors= ( \&extract_osrominfo, \&extract_stepinfo, \&extract_usbinfo, \&extract_diaginfo, \&extract_ebootinfo, \&extract_oemareainfo); my %extractors= map { ($mainsections[$_] => $extractors[$_]) } (0..$#mainsections); for (@mainsections) { $extractors{$_}->($fh, $offsets{$_}, $lengths{$_}); } $fh->seek(0, SEEK_END); $fh->close(); } sub extract_osrominfo { my ($fh, $ofs, $len)= @_; extractfile($fh, $ofs, $len, "031_osrom.nb"); } sub extract_stepinfo { my ($fh, $ofs, $len)= @_; extractfile($fh, $ofs, $len, "000_stepstone.nb"); } sub extract_usbinfo { my ($fh, $ofs, $len)= @_; extractfile($fh, $ofs, $len, "001_usbloader.nb"); } sub extract_diaginfo { my ($fh, $ofs, $len)= @_; extractfile($fh, $ofs, $len, "007_diagnostics.nb"); } sub extract_ebootinfo { my ($fh, $ofs, $len)= @_; extractfile($fh, $ofs, $len, "025_ebooter.nb"); } sub extract_oemareainfo { my ($fh, $ofs, $len)= @_; extractfile($fh, $ofs, 0x7e00, "bootsplash.jpg"); print_versioninfoinfo($fh, $ofs+0x7e00, 0x200); extractoemfiles($fh, $ofs+0x8000, $len-0x8000, $g_oemdir) if ($g_oemdir && -d $g_oemdir); } sub extractfile { my ($fh, $ofs, $len, $name)= @_; return if ($len==0); $fh->seek($ofs, SEEK_SET); my $data; $fh->read($data, $len); savefile($name, $data); } sub savefile { my $name= shift; my $data= shift; die "savefile: no filename\n" if (!$fn); my $out= IO::File->new($name, "w") or die "$name: $!\n"; binmode $out; $out->print($data); $out->close(); } sub extractoemfiles { my ($fh, $ofs, $len, $dirname)= @_; return if ($len==0); my $hdr; $fh->seek($ofs, SEEK_SET); $fh->read($hdr, 4); my $nfiles= unpack("V", $hdr); for (1..$nfiles) { my $fpos= $fh->tell(); my $name; $fh->read($name, 260); $name= unpack("A*", $name); my $size; $fh->read($size, 4); $size= unpack("V", $size); my $data; $fh->read($data, $size); savefile("$dirname/$name", $data); } } sub do_list { my $fn= shift; die "do_list: no filename\n" if (!$fn); my $fh= IO::File->new($fn, "r") or die "$fn: $!\n"; binmode $fh; my $hdr; $fh->read($hdr, 0x38) or die "$fn: $!\n"; my %lengths; my %checksums; my ($magic, $mask); ($magic, $mask, @lengths{@mainsections}, @checksums{@mainsections}) = unpack("V*", $hdr); printf("magic=%08lx mask=%08lx\n", $magic, $mask); if ($magic!=0x4e455445) { printf("not a ETEN image file\n"); exit(0); } my %offsets= (osrom=>0x38); for (1..$#mainsections) { $offsets{$mainsections[$_]} = $offsets{$mainsections[$_-1]} + $lengths{$mainsections[$_-1]}; } my $end_of_file= $offsets{$mainsections[-1]} + $lengths{$mainsections[-1]}; my @printers= (\&print_osrominfo, \&print_stepinfo, \&print_usbinfo, \&print_diaginfo, \&print_ebootinfo, \&print_oeminfo); my %printers= map { ($mainsections[$_] => $printers[$_]) } (0..$#mainsections); for (@mainsections) { $printers{$_}->($fh, $offsets{$_}, $lengths{$_}); } $fh->seek(0, SEEK_END); printf(" eof=%08lx filesize=%08lx\n", $end_of_file, $fh->tell()) if ($end_of_file != $fh->tell()); $fh->close(); } sub print_osrominfo { my ($fh, $ofs, $len)= @_; printf("%08lx l=%08lx PPC 2003 OS\n", $ofs, $len); return if ($len==0); my $hdr; $fh->seek($ofs, SEEK_SET); $fh->read($hdr, 0x80); my @hdr= unpack("V*", $hdr); printf(" %08lx %08lx %08lx\n", $hdr[0], $hdr[16], $hdr[17]); } sub print_stepinfo { my ($fh, $ofs, $len)= @_; printf("%08lx l=%08lx stepping stone loader\n", $ofs, $len); return if ($len==0); my $hdr; $fh->seek($ofs, SEEK_SET); $fh->read($hdr, $len); my $step1= substr($hdr, 0, 0x1000); my $step2= substr($hdr, 0x1000); $step1 =~ s/\xff+$//; $step2 =~ s/\xff+$//; my ($version1, $date1)= ($step1 =~ /(\[P300.*?\])\W+(\w+\s\w+\s\w+)/); my ($version2, $date2)= ($step2 =~ /(\[P300.*?\])\W+(\w+\s\w+\s\w+)/); if (defined $version1) { printf(" %08lx l=%08lx %s %s\n", 0, length($step1), $date1, $version1); printf(" %08lx l=%08lx %s %s\n", 0x33fa0000, length($step2), $date2, $version2); } } sub print_usbinfo { my ($fh, $ofs, $len)= @_; printf("%08lx l=%08lx usb loader\n", $ofs, $len); return if ($len==0); my $hdr; $fh->seek($ofs, SEEK_SET); $fh->read($hdr, $len); $hdr =~ s/\x00+$//; my ($date, $version)= ($hdr =~ /\x00(\w+\s\w+\s\w+).*?(P300.*?)\s\(/s); if (defined $date) { printf(" %08lx l=%08lx %s %s\n", 0x33fe0000, length($hdr), $date, $version); } } sub print_diaginfo { my ($fh, $ofs, $len)= @_; printf("%08lx l=%08lx diagnostics code\n", $ofs, $len); return if ($len==0); my $hdr; $fh->seek($ofs, SEEK_SET); $fh->read($hdr, $len); $hdr =~ s/\xff+$//; my ($date, $version)= ($hdr =~ /\x00(\w+\s\w+\s\w+).*?P300.*(P300 v\. \S+?)\x00/s); if (defined $date) { printf(" %08lx l=%08lx %s %s\n", 0x30000000, length($hdr), $date, $version); } } sub print_ebootinfo { my ($fh, $ofs, $len)= @_; printf("%08lx l=%08lx ebooter\n", $ofs, $len); return if ($len==0); my $hdr; $fh->seek($ofs, SEEK_SET); $fh->read($hdr, $len); my @hdr= unpack("V*", substr($hdr, 0, 0x80)); printf(" %08lx %08lx %08lx\n", $hdr[0], $hdr[16], $hdr[17]); $hdr =~ s/\xff+$//; my ($date)= ($hdr =~ /Built (\w+ \w+ \w+ \S+)/); if (defined $date) { printf(" %08lx l=%08lx %s %s\n", 0x30000000, length($hdr), $date, "CE Ethernet Bootloader"); } } sub print_oeminfo { my ($fh, $ofs, $len)= @_; print_logoinfo($fh, $ofs, 0x7e00); print_versioninfoinfo($fh, $ofs+0x7e00, 0x200); print_oemareainfo($fh, $ofs+0x8000, $len-0x8000); } sub print_logoinfo { my ($fh, $ofs, $len)= @_; printf("%08lx l=%08lx bootsplash image\n", $ofs, $len); return if ($len==0); my $jpeg; $fh->seek($ofs, SEEK_SET); $fh->read($jpeg, $len); $jpeg =~ s/\x00+$//; printf(" l=%08lx image : %s .. %s\n", length($jpeg), unpack("H*", substr($jpeg, 0, 4)), unpack("H*", substr($jpeg, length($jpeg)-2, 2))); } sub print_versioninfoinfo { my ($fh, $ofs, $len)= @_; printf("%08lx l=%08lx version info\n", $ofs, $len); return if ($len==0); my $hdr; $fh->seek($ofs, SEEK_SET); $fh->read($hdr, 0x200); my ($version, $date, $l1, $l2)= unpack("A64A64VV", $hdr); printf(" %08lx %08lx %s %s\n", $l1, $l2, $date, $version); } sub print_oemareainfo { my ($fh, $ofs, $len)= @_; printf("%08lx l=%08lx oem customization\n", $ofs, $len); return if ($len==0); my $hdr; $fh->seek($ofs, SEEK_SET); $fh->read($hdr, 4); my $nfiles= unpack("V", $hdr); for (1..$nfiles) { my $fpos= $fh->tell(); my $name; $fh->read($name, 260); $name= unpack("A*", $name); my $size; $fh->read($size, 4); $size= unpack("V", $size); $fh->seek($size, SEEK_CUR); printf(" %08lx l=%08lx %s\n", $fpos, $size, $name); } printf(" %08lx - end\n", $fh->tell()); }