#!/usr/bin/perl -w # (C) 2003-2007 Willem Jan Hengeveld # Web: http://www.xs4all.nl/~itsme/ # http://wiki.xda-developers.com/ # # $Id: sdtool.pl 1502 2007-04-15 07:54:20Z itsme $ # # script to manipulate Himalaya SDCard Images # # the images can be read or written using the 'sdread' and 'sdwrite' tools. # # # sdtool -l offset:file:fileoff:length ... -s imagename # # .... the sd card header: # 0x0000 'MAGICIAN ' # 0x0010 '0000000000000000' # 0x0020 '1.06 ' # 0x0030 DCD sum of section checksums # 0x007C DCD crc of header 00-7C # 0x0080 .... see typhoonnbfdecode.pl - seclevel ( depends on cardid-crc ) # 0x00E0 .... see typhoonnbfdecode.pl - cid ( depends on cardid ) # 0x0100 .... see typhoonnbfdecode.pl - timeout values # # # example: # # perl sdtool.pl # -so 0 -sl 0 -bl 1.06 -dev himalayas # -rh c:\local\cvsprj\xda-devtools\xda2nbftool\sdks-hima.img # -rm 0x80040000:os\1-75-00-WWE\os-1.75.00.nb # -rm 0x60010000:radio\1-18-00\radio_11800.nb:0x10000:0x2f0000 # -wi sdx.img # # the card specific header can be written later with: # # psdwrite f: c:\local\cvsprj\xda-devtools\xda2nbftool\sdks-hima.img 0x80 0x100 -s 0x80 # # use strict; $|=1; use Getopt::Long; use Compress::Zlib; use IO::File; use integer; my @readdatablocks; my $writename; my $savename; my $loadname; my $hdrname; my $b_check_radio_encoded= 0; my $device_name; my $bootloader_version; my $skipofs=0; my $skiplen=0; sub usage { return <<__EOF__ Usage: sdtool [options] -re Note that radio should be an encoded image -rm offset:file[:fileoff[:len]] ... read module -wm offset:len:file write module -wi outname write image -rh hdrfile read header -ri image name read image -bl version set header bl version -dev name set header device name -so [val] specify skipoffset -sl [val] specify skiplength __EOF__ } GetOptions( "rm=s" => sub { push @readdatablocks, ParseReadDataBlock($_[1]); }, "wm=s" => sub { $writename= $_[1]; }, "wi=s" => sub { $savename=$_[1]; }, "rh=s" => sub { $hdrname=$_[1]; }, "ri=s" => sub { $loadname=$_[1]; }, "re" => \$b_check_radio_encoded, "dev=s"=> \$device_name, "bl=s"=> \$bootloader_version, "so:s"=> sub { $skipofs=parse_skipval($_[1]) }, "sl:s"=> sub { $skiplen=parse_skipval($_[1]) }, ) or die usage(); sub parse_skipval { return if (!defined $_[0]); return if ($_[0] eq ""); return if ($_[0] eq "UNDEF"); return eval($_[0]); } # if no options are specified, default to just load the image for testing. if (!@readdatablocks && !$writename && !$savename && !$hdrname && !$loadname) { $loadname= shift; } die "need either load or save image name\n", usage() if (!$savename && !$loadname); die "need datablocks when creating image\n", usage() if ($savename && !@readdatablocks); sub ParseReadDataBlock { my ($spec)= @_; if ($spec =~ /^(\w+):(.*?)(?::(\w+)(?::(\w+))?)?$/) { my ($offset, $filename, $fileoffset, $length)= ( eval($1), $2, defined $3 ? eval($3) : undef, defined $4 ? eval($4) : undef); return { offset=>$offset, filename=>$filename, fileoffset=>$fileoffset, length=>$length }; } else { die "invalid read datablock spec\n"; } } my $hdrdata= $hdrname ? ReadFileData($hdrname, 0, 0x180) : ("\x00" x 0x180); if (length($hdrdata)<0x180) { $hdrdata .= "\x00" x (0x180-length($hdrdata)); } elsif (length($hdrdata)>0x180) { $hdrdata= substr($hdrdata, 0, 0x180); } if ($savename) { CreateImage($hdrdata, @readdatablocks); } elsif ($loadname) { DumpImage($loadname, $writename); } exit(0); sub DumpImage { my ($loadname)= @_; my $fh= IO::File->new($loadname, "r") or die "opening $loadname: $!\n"; binmode $fh; $fh->seek(0x180, SEEK_SET); my $crcsum= 0; while (1) { my $magic; if (!$fh->read($magic, 4)) { warn "Unexpected EOF - missing magic\n"; last; } last if ($magic eq "HTCE"); my $data; if (!$fh->read($data, 24)) { warn "Unexpected EOF - header truncated\n"; last; } my $ofs= $fh->tell(); my ($start, $len, $checksum)= unpack("N*", pack("H*", $data)); $crcsum += $checksum; printf("%08lx: %08lx %08lx %08lx\n", $ofs, $start, $len, $checksum); my $block; if (!$fh->read($block, $len)) { warn "Unexpected EOF - data truncated\n"; last; } CheckForWeirdData($block, $start); if ($writename) { my $name= GetUniqueName(sprintf("%s-%08lx.nb", $writename, $start)); SaveData($name, $block); } } $fh->close(); printf("crcsum= %08lx\n", $crcsum); } sub GetUniqueName { my $name= shift; my $fn= $name; my $i= 1; while (-e $fn) { $fn= sprintf("%s-%d", $name, $i++); } return $fn; } sub CreateImage { my ($hdrdata, @datablocks)= @_; my $fh= IO::File->new($savename, "w+") or die "creating $savename: $!\n"; binmode $fh; $fh->seek(0x180, SEEK_SET); my $checksum_sum=0; for (@datablocks) { my $data= ReadFileData($_->{filename}, $_->{fileoffset}, $_->{length}); CheckForWeirdData($data, $_->{offset}); $checksum_sum += WriteSection($fh, $_->{offset}, $data); } WriteEnd($fh); if ($device_name) { substr($hdrdata, 0x00, 16)= pack('A16', uc($device_name)); } if (defined $skipofs || defined $skiplen) { substr($hdrdata, 0x10, 16)= sprintf("%08lX%08lX", $skipofs||0, $skiplen||0); } if ($bootloader_version) { substr($hdrdata, 0x20, 16)= pack('A16', $bootloader_version); } substr($hdrdata, 0x30, 4)= pack("V", $checksum_sum); substr($hdrdata, 0x7c, 4)= pack("V", himacrc32(substr($hdrdata, 0, 0x7c))); $fh->seek(0, SEEK_SET); WriteHeader($fh, $hdrdata); $fh->seek(0, SEEK_END); $fh->close(); } sub SaveData { my $fn=shift; my $data= shift; my $fh= IO::File->new($fn, "w+") or die "opening $fn: $!\n"; binmode $fh; $fh->print($data); $fh->close(); } sub ReadFileData { my ($fn, $ofs, $len)= @_; $ofs ||= 0; my $fh= IO::File->new($fn, "r") or die "opening $fn: $!\n"; binmode $fh; $fh->seek($ofs, SEEK_SET) or die "seeking $fn: $!\n"; $len ||= (-s $fh) - $ofs; my $data; $fh->read($data, $len) or die "reading $fn: $!\n"; $fh->close(); return $data; } sub WriteHeader { my ($fh, $hdrdata)= @_; #$fh->printf("%-16s%08lx%08lx", "HIMALAYAS", 0, 0); #$fh->print("\x00" x 0x160); $fh->print($hdrdata); } sub WriteSection { my ($fh, $offset, $data)= @_; my $checksum= himacrc32($data); $fh->printf("HTCS%08lx%08lx%08lx", $offset, length($data), $checksum); $fh->print($data); return $checksum; } sub WriteEnd { my ($fh)= @_; $fh->print("HTCE"); my $align= $fh->tell()&0x1ff; $fh->print("\x00" x (0x200-$align)) if ($align); } sub himacrc32 { my ($data)= @_; return ~Compress::Zlib::crc32($data, -1) } sub CheckForWeirdData { my $data= shift; my $offset= shift; if ($offset<=0x70080036 && 0x70080036 < $offset + length($data)) { if (substr($data, 0x70080036 - $offset, 8) ne "FAT16 ") { warn "data at 0x70080036 does not look like a FAT bootsector\n"; } } if ($offset<=0x80040040 && 0x80040040 < $offset + length($data)) { my ($sig, $ofs)= unpack("V*", substr($data, 0x80040040 - $offset, 8)); if ($sig ne 0x43454345 || $ofs>0x88000000 || $ofs < 0x80000000) { printf("romheader sig=%08lx ( shuld be 0x43454345 ) hdrptr=%08lx\n", $sig, $ofs); warn "data at 0x80040000 does not look like an OS rom\n"; } } if ($offset<=0x80000040 && 0x80000040 < $offset + length($data)) { my ($sig, $ofs)= unpack("V*", substr($data, 0x80000040 - $offset, 8)); if ($sig ne 0x43454345 || $ofs<0x88000000 || $ofs >= 0xa0000000) { warn "data at 0x80000000 does not look like an bootloader\n"; } } if ($offset<=0x60000000 && 0x60000000 < $offset + length($data)) { my @jmps= unpack("V*", substr($data, 0x60000004 - $offset, 28)); if (grep {$_!=($b_check_radio_encoded?0xde13100d:0xea003ffd)} @jmps) { warn "data at 0x60000000 does not look like a radio bootloader\n"; } } if ($offset<=0x60010000 && 0x60010000 < $offset + length($data)) { my @jmps= unpack("V*", substr($data, 0x60010000 - $offset, 32)); if (grep {$b_check_radio_encoded?($_<0xde131300 || $_>0xde131400):($_<0xea000000 || $_>0xea001000)} @jmps) { warn "data at 0x60010000 does not look like a radio rom\n"; } } }