#!perl -w # (C) 2003-2007 Willem Jan Hengeveld # Web: http://www.xs4all.nl/~itsme/ # http://wiki.xda-developers.com/ # # $Id$ # # web: http://www.xs4all.nl/~itsme/projects/xda/wince-flashfile-formats.html # $Log$ # Revision 1.49 2005/07/17 08:52:38 itsme # changed such that it is now easy to convert to .bin # # Revision 1.48 2005/06/12 22:54:41 itsme # user supplied patches are now applied after all other modules are loaded. # # Revision 1.47 2004/10/24 16:07:10 itsme # changed such that saving romsectons works without them needing to be correct. changed endoffset to 0x82000000 # # Revision 1.46 2004/08/21 18:11:35 itsme # added 15200CHT 16601GER 17200FRA # # Revision 1.45 2004/07/09 20:33:20 itsme # added 1.72 roms # # Revision 1.44 2004/05/04 11:01:35 itsme # now truncated roms are recognized too # # Revision 1.43 2004/04/22 11:06:51 itsme # added 4.01.19 # # Revision 1.42 2004/04/04 15:56:49 itsme # added bl 1.06 # # Revision 1.41 2004/04/04 15:50:28 itsme # fixed rom checksum # # Revision 1.40 2004/04/04 13:16:41 itsme # added 1.66 rom # # Revision 1.39 2004/04/01 13:40:14 itsme # fixed himalay nbf detection # # Revision 1.38 2004/03/28 20:43:21 itsme # now also reads nbf with empty bootloader # # Revision 1.37 2004/03/11 11:46:11 itsme # notes about modularization plan # # Revision 1.36 2004/03/11 11:44:08 itsme # notes about modularization plan # # Revision 1.35 2004/02/27 18:53:17 itsme # fixed faulty 1.52 md5 # # Revision 1.34 2004/02/27 18:27:41 itsme # now really recognizes himalaya nk.nbf files # # Revision 1.33 2004/02/25 11:57:52 itsme # added himalaya rom versions, added support for himalaya nba filesaving, added support for himalaya sdcard reading # # Revision 1.32 2004/01/11 03:58:34 itsme # *** empty log message *** # # Revision 1.31 2003/12/18 13:51:00 itsme # added xda2-nbf support # # Revision 1.30 2003/11/17 10:33:23 itsme # added option to insert data from file # # Revision 1.29 2003/10/27 19:08:15 itsme # fixed usage page, added writing of strings to memory # # Revision 1.28 2003/10/27 18:00:20 itsme # fixed xdaser-1.2 description # # Revision 1.27 2003/10/22 20:07:47 itsme # allow .bin file to be specified as '-tBIN' # # Revision 1.26 2003/10/17 08:58:27 itsme # added option to save single xip block as .bin file # # Revision 1.25 2003/09/27 23:28:05 itsme # also support ipaq nbf files # # Revision 1.24 2003/09/26 15:57:08 itsme # added support for HTCFLASHKEY images # # Revision 1.23 2003/08/24 13:26:19 itsme # added option to specify start of bootloader and osrom # # Revision 1.22 2003/08/24 09:17:40 itsme # added option to extract rom sections # # Revision 1.21 2003/08/22 20:24:20 itsme # more roms # no longer using list::util # # Revision 1.20 2003/08/20 12:01:39 itsme # more rom signatures # # Revision 1.19 2003/08/16 14:28:59 itsme # fixed bmp offsets # # Revision 1.18 2003/08/16 14:14:26 itsme # added 4.00.10 rom, added bmp offset for known roms # # Revision 1.17 2003/07/22 12:59:35 itsme # implemented reading of sd card images # # Revision 1.16 2003/07/21 10:47:44 itsme # added option to manually insert data from the cmdline into the result rom # # Revision 1.15 2003/07/19 13:40:43 itsme # better handles self deduced xip offset # bitmap offset no longer has a default # it is now an error to extract an empty bootloader # # Revision 1.14 2003/07/18 06:36:10 itsme # added automatic xip region finding # # Revision 1.13 2003/07/17 21:43:19 itsme # added signature for empty bootloader # # Revision 1.12 2003/07/16 15:19:00 itsme # no message # # Revision 1.11 2003/07/16 15:18:02 itsme # adding history # # Date : 2003/7/16 # now allows insertion of arbitrary parts into the romimage. # now checks bootloader version # added check to see if all known xip regions really are found # # Date : 2003/7/11 11:14:22 # version is not always numeric # # Date : 2003/7/7 15:6:21 # added option to specify offsets for xipchain and bitmap # # Date : 2003/7/1 11:46:34 # now also finds xip block at 81ffd000 for ppc2003/wince4 rom # # Date : 2003/6/10 21:36:8 # more error checking, # extra test if operator rom is in the correct address range # # Date : 2003/6/6 1:20:7 # added display of xipchain in result # # Date : 2003/6/5 9:32:18 # now saves bitmap as bmp # # Date : 2003/6/4 19:14:19 # added support for writing various image types, # fixed (oops) incorrectly written NBF files # more robust against missing information # # Date : 2003/6/2 22:31:11 # tools to create and modify rom images # # use strict; use IO::File; use Dumpvalue; my $d= new Dumpvalue; $|=1; use Getopt::Long; use Digest::MD5; #use Archive::Zip; #use List::Util qw(min); sub min { my $min; for (@_) { $min= $_ if (!defined $min || $_ < $min); } return $min; } # for quick B000FF file extraction my $g_bin_start; my $g_bin_length; # test: # perl splitrom.pl NK.nbf -wx cfg\xipchain -wo cfg\os.nb1 -wb cfg\bitmap.bin -wl cfg\bootloader.nb0 -wi cfg\nbfinfo # perl splitrom.pl cfg\os.nb1 -wo cfg\os.nbf -rl cfg\bootloader.nb0 -ri cfg\nbfinfo -t NBF # these should report the same checksum: # md5sum NK.nbf cfg\os.nbf # todo, make it such that this works: # construct 3.x rom: # perl splitrom.pl nk.nbf -rx cfg/xipchain -rm tmp/xda1.img:0x81800000 -rb cfg/bootsplash.img -ob 0x81900000 -rm tmp/op.img:0x81940000 -rm tmp/xda2.img:0x81a00000 -wo nk.nbf # construct 4.x rom: # perl splitrom.pl nk.nbf -rx cfg/xipchain -rm tmp/xda1.img:0x81b00000 -rb cfg/bootsplash.img -ob 0x81ec0000 -wo nk.nbf # # # todo: # - split into several modules: # Windows::CE::Rom # - WriteFile # - ReadFile # - EnumXipSections # - subclassed by # WallabyNbf # HimalayaNbf # WallabySdImage # HimalayaSdImage # BinFile # Nb1File # Windows::CE::XipSection # - ListFiles # - SaveFile # - AddFile # - SaveImage my %md5_os_list= ( '5fce81938ab70a056675e52660dd975e'=> { desc=>'3.00.01 ENG 2002-05-18 02euro', bmpofs=>0x81900000 }, '9f00aad6276bd08c8de3703156c503ad'=> { desc=>'3.02.00 GER 2002-05-27 02de', bmpofs=>0x81900000 }, '390506bb206c69746419958a4a9def86'=> { desc=>'3.04 2002-06-12 o2/PPC2003PE' }, 'fd0b8c338c0928df04a4a5f4caa83945'=> { desc=>'3.12.07 ENG 2002-08-13 o2asia', bmpofs=>0x81900000 }, 'a51e3924b417182ea80869c25b91c2fa'=> { desc=>'3.13.07 GER 2002-09-10 tmobile', bmpofs=>0x81900000 }, 'f88a15831fdad726f14422b81d508aa9'=> { desc=>'3.14.13 ENG 2002-10-09 O2euro', bmpofs=>0x81900000 }, 'a41c258562c784c1a5655b536187b8ea'=> { desc=>'3.14.16 GER 2002-10-11 o2de', bmpofs=>0x81900000 }, '754cc7175f151082f6208e4900a91ad6'=> { desc=>'3.14.17 ENG 2002-10-11 O2asia', bmpofs=>0x81900000 }, 'c06909d3b3a80e6926d2be97be989d71'=> { desc=>'3.14.40 ENG 2002-11-26 TMobile-US', bmpofs=>0x81ec0000 }, 'bf3880df78c5932a812a6ff460611dd7'=> { desc=>'3.15.15 ENG 2002-12-11 O2euro', bmpofs=>0x81900000 }, '3dfdcdbe68ec16be572a43b053490aad'=> { desc=>'3.16.13 ENG 2003-01-09 Qtek', bmpofs=>0x81900000 }, '1e96c46df80176f6fa9de2220f27d1ca'=> { desc=>'3.16.43 ENG 2003-02-25 T-MobCZ-test', bmpofs=>0x81900000 }, '411632ff1afb8e2213ee7e416381eca6'=> { desc=>'3.16.52 ENG 2003-03-10 XDASER-10', bmpofs=>0x81900000 }, '0aefe8c7a303b59b2ba9e7cd073eb419'=> { desc=>'3.16.52 ENG 2003-03-10 XDASER-11', bmpofs=>0x81900000 }, '3a08e87551432c3ebaf9de76b434f104'=> { desc=>'3.16.52 ENG 2003-03-10 qtek', bmpofs=>0x81900000 }, 'c6208ebe96667e7955f53e109cbc1f8c'=> { desc=>'3.17.03 ENG 2003-05-15 o2euro', bmpofs=>0x81900000 }, 'ca41689053b028301ffa3914120adcca'=> { desc=>'3.17.03 ENG 2003-03-10 XDASER-12', bmpofs=>0x81900000 }, 'dfe90f07a7a9038d2d0e54f5b70cb74f'=> { desc=>'A.20.17 ENG 2003-05-15 SX56-AT&T', bmpofs=>0x81ec0000 }, 'c29b82c444eb5b4f5db68cafc376e74f'=> { desc=>'4.00.01 ENG 2003-05-16 PPC2003', bmpofs=>0x81ec0000 }, '8d88f5354955e77ebb815f33d1616d05'=> { desc=>'3.19.01 GER 2003-05-27 o2euro', bmpofs=>0x81900000 }, '4b99a0459776ea8e03d99a0736a2c430'=> { desc=>'3.19.03 GER 2003-05-31 o2euro', bmpofs=>0x81900000 }, 'd41d8cd98f00b204e9800998ecf8427e'=> { desc=>'No OS Image present'}, '368a3eacc902f446c3337004737bc624'=> { desc=>'4.00.05 ENG 2003-06-05 ppc2003', bmpofs=>0x81ec0000 }, 'dad2e3cad6095282bf1d58ccf12171e8'=> { desc=>'4.00.10 ENG 2003-06-20 ppc2003', bmpofs=>0x81ec0000 }, '07b57cd00ad1c028eb78882be942a50a'=> { desc=>'3.18.07 CHS 2003-05-26 dopod', bmpofs=>0x81ec0000 }, '1cd007bbffa268b12b7968cabb7cc75f'=> { desc=>'4.00.11 ENG 2003-06-23 ppc2003', bmpofs=>0x81ec0000 }, '8ac9fd0d069f71b6113785cf724315ac'=> { desc=>'4.00.16 ENG 2003-07-10 ppc2003', bmpofs=>0x81ec0000 }, 'fdd86b4c1815435a497e223f0bc4d17e'=> { desc=>'3.20.06 ENG 2003-07-30 o2asia', bmpofs=>0x81900000 }, '5997e3d491028f6dc7336e9ad8b4cbc1'=> { desc=>'4.00.33 GER 2003-09-15 ppc2003' }, 'aa187a457bc801c2d3f31a721a189495'=> { desc=>'4.01.00 ENG 2003-10-27 ppc2003' }, '71121a205908655c7cbaed50b5593c4e'=> { desc=>'A.30.07 ENG 2003-12-12 SX56-AT+T' }, '8f269afc9292e6fc5bcdc42eea048805'=> { desc=>'A.30.09 ENG 2004-01-01 SX56-AT+T' }, '7b7dd83fb3a639d826164d89350fb974'=> { desc=>'4.01.19 GER 2004-04-05 tmobile' }, '6582475979705a99e478c56161e48ef4'=> { desc=>'1.01.00 USA 2003-08-01 himalaya' }, '76f743f921bd527b7b5ef603e30893ab'=> { desc=>'1.03.00 USA 2003-08-01 himalaya' }, 'da700c008cbccb67467e25f309d49e5d'=> { desc=>'1.52.00 WWE 2003-10-14 himalaya' }, '438e00dcea426aa28cd32f5cf8a316ea'=> { desc=>'1.52.00 CHT 2003-10-14 himalaya' }, 'b35c7cf8584928e37d7acb76a9ff9187'=> { desc=>'1.60.00 WWE 2003-10-14 himalaya' }, '85d2f92397f593e0a25e5152514a505d'=> { desc=>'1.60.00 GER 2003-10-14 himalaya' }, '0f69eb5f152300a6f9f277d5a214218c'=> { desc=>'1.66.00 WWE 2004-02-20 himalaya' }, '3526670f48c7b439c58257217eef94e7'=> { desc=>'1.66.01 GER 2004-02-20 himalaya' }, 'c4726a29bcb0476d963dbeb9292ce473'=> { desc=>'1.66.04 ITA 2004-02-20 himalaya' }, 'bb419abecaf5e1736eea8f86e832d770'=> { desc=>'1.72.00 WWE 2004-03-15 himalaya' }, 'a3e2d20693ffc7910f3089da2fb8ef7f'=> { desc=>'1.72.00 ESN 2004-03-15 himalaya' }, '9834049c204f1ce69fc357091e169db2'=> { desc=>'1.72.00 FRA 2004-03-15 himalaya' }, ); my %md5_boot_list= ( '4ebb788f8ca96cc59a2d5cc5363cf99d'=>'V5.14 2002-04-18 12:25:54', '0f31faf04f069668e9dc93fba545bfa3'=>'V5.15 2002-06-06 20:29:17', '8a9c7166d7e9ea7e78c94b5a2360cd33'=>'V5.17 2002-08-13 18:19:23', '5c937d26ffe5d87d8895a556d8922954'=>'V5.22 2003-05-15 17:46:55', '866b7a2acf859a7137fd5ad1f04cbb1a'=>'V6.22 2003-05-15 18:26:47', 'ec87a838931d4d5d2e94a04644788a55'=>'No bootloader present', '9a6b0ecdafd2914ea464ecaff193f0f8'=>'hima 0.23 1.26 2003-07-09 21:14:16', '4003a006e156c041f22061f1d0a38bc6'=>'hima 0.26b 1.28c 2003-08-19 17:29:26', '2954f492f74688b4a2529702cf24623a'=>'hima 1.01 1.28 2003-09-15 22:22:13', 'd37e44ead0e1a3f25ae66b7424e70080'=>'hima 1.02 1.29 2003-09-24 18:17:06', '7032daf451e7c9a0600afd614aba3689'=>'hima 1.03 1.32b 2003-12-10 14:54:39', '5c4e95410b96b587afe428273a25550f'=>'hima 1.06 1.34b 2004-02-18 14:32:09', ); my $g_verbose= 0; my $g_bmp_offset; # where to look for bmp my $g_boot_offset= 0x80000000; my $g_os_offset= 0x80040000; my $g_end_offset= 0x82000000; my $g_nbf_header_size= 32; # 64 for himalaya nbf's my $xipchainsavename; my $xipchainname; my $osromsavename; my $bitmapsavename; my $bitmapname; my $nbfinfotext; my $infosavename; my $infoname; my $bootloadersavename; my $bootloadername; my $readnbfinfo; my $outputtype; my @saveromextensions; my @romextensions; my @editdata; my @savexipblocks; GetOptions( "wx=s" => \$xipchainsavename, "wp=s" => sub { push @savexipblocks, ParseXipSaveSpec( $_[1] ); }, "wo=s" => \$osromsavename, "wb=s" => \$bitmapsavename, "wi=s" => \$infosavename, "wm=s" => sub { push @saveromextensions, ParseRomExtension($_[1]); }, "ri=s" => \$infoname, "n=s" => \$nbfinfotext, "rl=s" => \$bootloadername, "rx=s" => \$xipchainname, "rb=s" => \$bitmapname, "rm=s" => sub { push @romextensions, ParseRomExtension($_[1]); }, "wl=s" => \$bootloadersavename, "nl=s" => sub { $g_nbf_header_size= eval($_[1]) }, "ol=s" => sub { $g_boot_offset= eval($_[1]) }, "oo=s" => sub { $g_os_offset= eval($_[1]) }, "ob=s" => sub { $g_bmp_offset= eval($_[1]) }, "oe=s" => sub { $g_end_offset= eval($_[1]) }, "dd=s" => sub { push @editdata, ParseData($_[1], 4); }, "dw=s" => sub { push @editdata, ParseData($_[1], 2); }, "db=s" => sub { push @editdata, ParseData($_[1], 1); }, "ds=s" => sub { push @editdata, ParseStringData($_[1], 1); }, "dl=s" => sub { push @editdata, ParseFileData($_[1]); }, "t=s" => \$outputtype, "v" => \$g_verbose, ) or die usage(); sub ParseXipSaveSpec { my ($spec)= @_; if ($spec =~ /^(.*?):(.*)/) { return { blockname=>$1, filename=>$2 }; } die "invalid xip save spec\n"; } sub ParseData { my ($ext, $datasize)= @_; if ($ext =~ /^(\w+):(.*)/) { my $ofs= hex($1); my @data= map { hex($_) } split /,/, $2; return { offset=>$ofs, data=>pack( $datasize==4?"V*": $datasize==2?"v*": $datasize==1?"C*":"?", @data), }; } die "Invalid data edit format $ext\n"; } sub ParseStringData { my ($ext, $datasize)= @_; if ($ext =~ /^(\w+):(.*)/) { my $ofs= hex($1); my $data= eval("\"$2\""); return { offset=>$ofs, data=>$data, }; } die "Invalid data edit format $ext\n"; } sub ParseFileData { my ($ext)= @_; if ($ext =~ /^(\w+):(.+):(\w+):(\w+)$/) { my $romofs= hex($1); my $filename= $2; my $fileofs= hex($3); my $filelen= hex($4); my $fh=IO::File->new($filename, "r") or die "$filename: $!\n"; binmode $fh; $fh->seek($fileofs, SEEK_SET); my $data; $fh->read($data, $filelen); $fh->close(); if (length($data) != $filelen) { die "error reading $filelen bytes from $filename\n"; } return { offset=>$romofs, data=>$data, }; } die "Invalid data edit format $ext\n"; } sub ParseRomExtension { my ($ext)= @_; if ($ext =~ /([^:]+)(?::(\w+))?(?::(\w+))?/) { return { filename=>$1, (defined $2)?(baseoffset=> eval($2)):(), (defined $3)?(length=>eval($3)):(), }; } die "Invalid rom extension spec $ext\n"; } sub usage { return <<__EOF__ Usage: splitrom [options] -wx xipchain where to write xipchain -wo osrom where to write output image -wb bitmap where to write bitmap -wl bootloader where to write bootloader -wm romsection:offset:length save romsection to file -wp xipsection:filename save xipsection to .bin file -wi infoname save NBF info to file -rl bootloader which bootloader to use for NBF -n nbfinfotext what NBF header to use [ex: PW10A1-ENG-3.16-007] -ri nbfinfofile or where to read NBF header info from -wi nbfinfofile where to save NBF header info -rx xipchain where to get xipchain from -rb bitmap where to get bitmap from -rm romsection:offset insert new romsection. -nl length size of the NBF header -ob offset where to find the bootup image -oe offset the end of the os image ( dfl: 0x81f00000 ) -ol offset where to find the bootloader ( dfl: 0x80000000 ) -oo offset the start of the os image ( dfl: 0x80040000 ) -t NBF | NBA | B000FF | NB? | IMG type of result image (default is NB1) -db offset:data,data,... write byte data to offset -dw offset:data,data,... write word data to offset -dd offset:data,data,... write dword data to offset -ds offset:string write string data to offset ( with \\ chars ) -dl offset:file:offset:length insert data from file at offset __EOF__ } if (@ARGV==0) { warn "need source file\n"; die usage(); } my $rommap= {}; for (@ARGV) { ReadRom($rommap, $_); } # check os version when it is still 'virgin' my $osmd5= CalcMD5($rommap, $g_os_offset, $g_end_offset-$g_os_offset); if (exists $md5_os_list{$osmd5}) { print "this rom seems to be $md5_os_list{$osmd5}{desc}\n"; $g_bmp_offset ||= $md5_os_list{$osmd5}{bmpofs}; } else { my $romversion= FindRomVersion($rommap, $g_os_offset, $g_end_offset-$g_os_offset); print "!!! your rom is not known to me: $romversion md5: $osmd5\n"; } sub FindRomVersion { my ($rommap, $start, $end)= @_; #strings -el os.nb1 | grep ".\...\... [A-Z]\|[0-9]/[0-9][0-9]/[0-9]" return ""; } if ($bitmapname) { LoadBitmap($rommap, $g_bmp_offset, $bitmapname); } if ($bootloadername) { LoadBootloader($rommap, $g_boot_offset, $bootloadername); } if ($infoname) { my $fh= IO::File->new($infoname, "r") or die "$infoname: $!\n"; $nbfinfotext= <$fh>; $fh->close(); } for (@romextensions) { LoadRomExtension($rommap, $_->{baseoffset}, $_->{filename}); } for (@saveromextensions) { if (!defined $_->{baseoffset} && defined $g_bin_start) { $_->{baseoffset}= $g_bin_start; $_->{length}= $g_bin_length; printf("saving %08lx - %08lx ( l=%08lx )\n", $g_bin_start, $g_bin_start+$g_bin_length, $g_bin_length); } SaveRomExtension($rommap, $_->{filename}, $_->{baseoffset}, $_->{length}); } # check bootloader after everything is loaded my $bootmd5= CalcMD5($rommap, $g_boot_offset, $g_os_offset-$g_boot_offset); if (exists $md5_boot_list{$bootmd5}) { print "this bootloader seems to be $md5_boot_list{$bootmd5}\n"; } else { my $bootversion= FindBootVersion($rommap, $g_boot_offset, $g_os_offset-$g_boot_offset); print "!!! your bootloader is not known to me: $bootversion md5: $bootmd5\n"; } sub FindBootVersion { my ($rommap, $start, $end)= @_; #strings bl.nb0 | grep "[0-9]:[0-9][0-9]:[0-9][0-9]\|[A-Z][a-z][a-z] \+[0-9]\+ [0-9][0-9][0-9][0-9]\|[0-9]\.[0-9][0-9]" return ""; } #print "rommap: ", join(", ", map { sprintf("%08lx-%08lx", $_->{start}, $_->{start}+$_->{length}) } values %$rommap), "\n"; # locate pieces my $xip_offset= FindXipChainOffset($rommap); # this is the only item that is inserted after the old one has been found. if ($xipchainname) { if (!$xip_offset) { die "did not find xip region\n"; } LoadXipChain($rommap, $xip_offset, $xipchainname); } my $xipchain= GetXipChain($rommap, $xip_offset) if ($xip_offset); if (!defined $xipchain) { print "no xipchain found\n"; } my $bootloader= FindBootloader($rommap, $g_boot_offset); # fixed at 0x80000000 if (!defined $bootloader) { print "no bootloader found\n"; } my $romchain= FindRomChain($rommap); # signature 0x43454345 if (!defined $romchain) { die "no romchain found\n"; } my $operatorrom= FindOperatorRom($rommap); # signature 0x34798243 if (!defined $operatorrom) { print "no operator rom found\n"; } my $bitmap= FindBitmap($rommap, $g_bmp_offset) if ($g_bmp_offset); if (!defined $bitmap) { print "no bitmap found\n"; } for (@editdata) { EditData($rommap, $_->{offset}, $_->{data}); } # calculate summary info my %info; my %romidx= map { $_->{physfirst}=>$_ } @$romchain; my %xipidx= map { $_->{pvAddr}=>$_ } @$xipchain if ($xipchain); my %xipnameidx= map { lc($_->{szName})=>$_ } @$xipchain if ($xipchain); for (@$romchain) { my $name; if (exists $xipidx{$_->{physfirst}}) { my $xipentry= $xipidx{$_->{physfirst}}; $name= sprintf("%2d %s", $xipentry->{usOrder}, $xipentry->{szName}); } else { $name= $_->{physfirst} == $g_boot_offset ? "-- bootloader" : "-- kernel"; } $info{$_->{physfirst}}= { start=>$_->{physfirst}, end=>$_->{physlast}, desc=>sprintf("%-32s %3d files %3d modules", $name, $_->{nrfiles}, $_->{nrmodules}) }; } my @xipnotfound= grep { ! exists $info{$_} } keys %xipidx; if (@xipnotfound) { die "xip regions not found: ", join(", ", map { sprintf("%08lx-%08lx=%s", $xipidx{$_}->{pvAddr}, $xipidx{$_}->{pvAddr}+$xipidx{$_}->{dwMaxLength}, $xipidx{$_}->{szName}); } @xipnotfound), "\n"; } if ($bitmap) { $info{$bitmap->{start}}= { start=>$bitmap->{start}, end=>$bitmap->{start}+$bitmap->{length}, desc=>sprintf("%-32s : %s .. %s", "-- bitmap", unpack("H*", substr($bitmap->{data}, 0, 4)), unpack("H*", substr($bitmap->{data}, $bitmap->{length}-4, 4))) }; } if ($operatorrom) { $info{$operatorrom->{start}}= { start=>$operatorrom->{start}, end=>$operatorrom->{start}+$operatorrom->{length}, desc=>sprintf("%-32s %3d files", "-- operator rom", $operatorrom->{nrfiles}) }; } if ($xipchain) { $info{$xip_offset}= { start=>$xip_offset, end=>$xip_offset+($#$xipchain+1)*0x290+4, desc=>sprintf("%-32s %3d xip entries", "-- xip chain", ($#$xipchain+1)) }; } # print info print map { sprintf("%08lx - %08lx %s\n", $info{$_}{start}, $info{$_}{end}, $info{$_}{desc}); } sort { $a<=>$b} keys %info; CheckForOverlap(\%info); # sanity checks if ($infosavename && !$readnbfinfo) { die "input file is not NBF, cannot save nbfinfo\n"; } if (!defined $outputtype) { $outputtype= "NB1"; } if ($outputtype =~ /^NBF$/i && !$nbfinfotext) { die "need nbfinfo when saving NBF file\n"; } if ($outputtype =~ /^NBA$/i && !$nbfinfotext) { die "need nbfinfo when saving NBA file\n"; } if ($outputtype =~ /^NBF$/i && !$bootloader) { die "need bootloader when saving NBF file\n"; } if ($outputtype =~ /^NBA$/i && !$bootloader) { die "need bootloader when saving NBA file\n"; } if ($outputtype =~ /^NB0?$/i && !$bootloader) { die "need bootloader when saving NB0 file\n"; } if ($outputtype =~ /^NB2$/i && !$bootloader) { die "need bootloader when saving NB2 file\n"; } # write images if ($bitmapsavename) { if ($bitmap) { my $fh= IO::File->new($bitmapsavename, "w+") or die "$bitmapsavename: $!\n"; binmode $fh; $fh->print(MakeBMPHeader()); $fh->print($bitmap->{data}); $fh->print(MakeBMPFooter()); $fh->close(); } else { die "not writing bitmap - none present in source image\n"; } } if ($xipchainsavename) { if ($xipchain) { my $fh= IO::File->new($xipchainsavename, "w+") or die "$xipchainsavename: $!\n"; binmode $fh; my $xipdata= GetMapData($rommap, $xip_offset, ($#$xipchain+1)*0x290+4); $fh->print($xipdata); $fh->close(); } else { die "not writing xipchain - none present in source image\n"; } } for (@savexipblocks) { if (!exists $xipnameidx{lc($_->{blockname})}) { warn "xip block $_->{blockname} not found\n"; next; } my $xipentry= $xipnameidx{lc($_->{blockname})}; if (!exists $romidx{$xipentry->{pvAddr}}) { warn "rom section for xip block $_->{blockname} not found\n"; next; } my $romentry= $romidx{$xipentry->{pvAddr}}; SaveXipBlock($rommap, $romentry, $_->{filename}); } if ($infosavename) { if ($readnbfinfo) { my $fh= IO::File->new($infosavename, "w+") or die "$infosavename: $!\n"; binmode $fh; $fh->print($readnbfinfo); $fh->close(); } else { die "not writing NBF info - source file is not NBF\n"; } } if ($bootloadersavename) { if ($bootloader) { my $fh= IO::File->new($bootloadersavename, "w+") or die "$bootloadersavename: $!\n"; binmode $fh; $fh->print($bootloader->{data}); $fh->close(); } else { die "not writing bootloader - none present in source image\n"; } } if ($osromsavename) { my $fh= IO::File->new($osromsavename, "w+") or die "$osromsavename: $!\n"; binmode $fh; if ($outputtype =~ /^NBF/i) { # for flashing via 'normal' method my $romdata; $romdata= GetMapData($rommap, $g_boot_offset, $g_end_offset-$g_boot_offset); $fh->print(MakeNBFHeader($nbfinfotext, $romdata)); $fh->print($romdata); } elsif ($outputtype =~ /^NBA/i) { # for flashing via 'normal' method my $romdata; # todo: find better solution to discepancy between range for md5 calc, # and rom content range. $romdata= GetMapData($rommap, $g_boot_offset, $g_end_offset+0x80000-$g_boot_offset); $fh->print(MakeNBAHeader($nbfinfotext, $romdata)); $fh->print($romdata); } elsif ($outputtype =~ /^NB0?$/i) { # just the bootloader my $romdata; $romdata= GetMapData($rommap, $g_boot_offset, $g_os_offset-$g_boot_offset); $fh->print($romdata); } elsif ($outputtype =~ /^NB1$/i) { # just the os image my $romdata; $romdata= GetMapData($rommap, $g_os_offset, $g_end_offset-$g_os_offset); $fh->print($romdata); } elsif ($outputtype =~ /^NB2$/i) { # bootloader + os image my $romdata; $romdata= GetMapData($rommap, $g_boot_offset, $g_end_offset-$g_boot_offset); $fh->print($romdata); } elsif ($outputtype =~ /^(?:B00|BIN)/i) { # ms flashformat my $start= $g_os_offset; my $length= $g_end_offset-$start; my $romdata; $romdata= GetMapData($rommap, $start, $length); $fh->print(MakeB000FFHeader($romdata, $start, $length)); $fh->print($romdata); $fh->print(MakeB000FFFooter($g_os_offset)); } else { warn "writing $outputtype not implemented yet\n"; } $fh->close(); } exit(0); sub SaveXipBlock { my ($map, $romentry, $filename)= @_; my $data= GetMapData($map, $romentry->{physfirst}, $romentry->{physlast} - $romentry->{physfirst}); #todo: save as .bin my $fh=IO::File->new($filename, "w+") or die "$filename: $!\n"; binmode $fh; $fh->print(MakeB000FFHeader($data, $romentry->{physfirst}, $romentry->{physlast} - $romentry->{physfirst})); $fh->print($data); $fh->print(MakeB000FFFooter($g_os_offset)); $fh->close(); } sub EditData { my ($rommap, $offset, $data)= @_; EraseArea($rommap, $offset, length($data)); $rommap->{$offset}= { start=>$offset, length=>length($data), data=>$data, }; } sub SaveRomExtension { my ($map, $name, $offset, $length)= @_; my $fh=IO::File->new($name, "w+") or die "$name: $!\n"; binmode $fh; my $data= GetMapData($map, $offset, $length); $fh->write($data); $fh->close(); } sub LoadRomExtension { my ($map, $offset, $name)= @_; my $fh=IO::File->new($name, "r") or die "$name: $!\n"; binmode $fh; my $data; $fh->read($data, -s $fh); $fh->close(); AddToMap($map, $offset, length($data), $data); } sub LoadBootloader { my ($map, $offset, $name)= @_; if (-s $name != 0x40000) { die "bootloader should be exactly 256k\n"; } LoadRomExtension($map, $offset, $name); } sub LoadBitmap { my ($map, $offset, $name)= @_; my $fh=IO::File->new($name, "r") or die "$name: $!\n"; binmode $fh; my $data; $fh->read($data, -s $fh); $fh->close(); if (length($data)!=0x25848) { die "bootsplash.bmp should be exactly 153672 bytes\n"; } AddToMap($map, $offset, 0x25800, substr($data, 70, 0x25800)); } sub LoadXipChain { my ($map, $offset, $name)= @_; LoadRomExtension($map, $offset, $name); } # create nbf header for wallaby nbf files sub MakeNBFHeader { my ($info, $data)= @_; # '%16' = calculate 16 bit checksum of following expression (= 'C*') my $sum= unpack("%16C*", $info) + unpack("%16C*", $data);; return sprintf("%s-%x--------", substr($info, 0, 19), $sum&0xffff); } # create nba header for himalaya nbf files sub MakeNBAHeader { my ($info, $data)= @_; # '%16' = calculate 16 bit checksum of following expression (= 'C*') my $sum= crc32($info); for (my $i=0 ; $inew($filename, "r") or die "$filename : $!\n"; binmode $fh; my $data; defined $fh->read($data, 64) or die "reading $filename : $!\n"; if ($data =~ /^B000FF/) { ReadB000FF($rommap, $fh, 0); } elsif ($data =~ /^(\w+-\w+-\w+\.\w+-\w+)-\w+-+/ # xda nbf || $data =~ /^([^-]+-\w+-\w+-\w+\.\w+)-\w+-+/) { # ipaq nbf $readnbfinfo= $1; ReadNBF($rommap, $fh); } elsif ($data =~ /^(.{10}-.{20}-.{10}-.{9})-.{8}---/) { # himalaya nbf $readnbfinfo= $1; $g_nbf_header_size= 64; ReadNBF($rommap, $fh); } elsif ($data =~ /^HIMALAYAS/) { ReadHIMALAYAS($rommap, $fh); } elsif ($data =~ /^HTC\$WALLABY(\d\d)/) { ReadHTCWALLABY($rommap, $fh, $1); } elsif ($data =~ /^HTC FLASH KEY/) { ReadHTCFLASHKEY($rommap, $fh); } else { if (-s $fh > 0x2b0) { $fh->seek(0x298, SEEK_SET); $fh->read($data, 32) or die "reading $filename : $!\n"; if ($data =~ /^B000FF/) { ReadB000FF($rommap, $fh, 0x298); } else { ReadBIN($rommap, $fh); } } else { ReadBIN($rommap, $fh); } } $fh->close(); } # # 4230303046460a "B000FF\n" # 00000480 start 0x80040000 # 00008001 len 0x01800000 # 00000480 start # 00008001 len # a7133080 checksum # # .nbf files: # # 32 byte header: "PW10A1-ENG-3.12-001-dc8---------" # followed by bootloader + rom # # if rom+0x40=="ECEC" && rom+0x44>0x8c000000 -> baseofs=0 # -> baseofs= 0x80040000 # sub MakeB000FFHeader { my ($romdata, $start, $length)= @_; return pack("A*V*", "B000FF\n", $start, $length, $start, $length, CalcB000FFChecksum($romdata)); } sub MakeB000FFFooter { my ($entrypoint)= @_; return pack("V*", 0, $entrypoint, 0); } sub CalcB000FFChecksum { my ($data)= @_; return unpack("%32C*", $data); } sub ReadB000FF { my ($rommap, $fh, $baseofs)= @_; my $imagestart= ReadDword($fh, $baseofs+7); my $imagelength= ReadDword($fh); $g_bin_start= $imagestart; $g_bin_length= $imagelength; my $nblocks=0; while (!$fh->eof) { my $blockstart= ReadDword($fh); my $blocklength= ReadDword($fh); my $blockchecksum= ReadDword($fh); if ($g_verbose) { printf("%08lx: chk%08lx: ", $fh->tell()-12, $blockchecksum); } if ( ($nblocks>0 && $blockstart==0) || ($blocklength==0) ) { printf("B000FF image: %08lx-%08lx, entrypoint: %08lx\n", $imagestart, $imagestart+$imagelength, $blocklength); last; } my $block; $fh->read($block, $blocklength); AddToMap($rommap, $blockstart, $blocklength, $block); $nblocks++; if ($imagestart>$blockstart || $imagestart+$imagelength<$blockstart+$blocklength) { # this means I don't understand the fileformat warn "block outside of image range\n"; } } } sub ReadNBF { my ($rommap, $fh)= @_; my $sig_bl= ReadDword($fh, $g_nbf_header_size+0x40); my $hdrofs= ReadDword($fh); my $sig_os= ReadDword($fh, $g_nbf_header_size+0x40040); if (($sig_bl!=0x43454345 && $sig_bl!=0) || $sig_os!=0x43454345) { die "cannot parse NBF rom image\n"; } my $start; if ($sig_bl==0x43454345 && $hdrofs>=0x8c000000) { $start= $g_boot_offset; } elsif ($sig_bl==0x43454345) { $start= $g_os_offset; # warning that this nbf is missing a bootloader. warn "THIS IMAGE WILL BREAK YOUR DEVICE IF FLASHED WITH RUU.EXE!!!\n"; } else { # this is the case where the image has an empty bootloader, # which is ok for himalaya upgrade tools. $start= $g_boot_offset; } my $data; $fh->seek($g_nbf_header_size, SEEK_SET); my $length= (-s $fh) - $g_nbf_header_size; $fh->read($data, $length); AddToMap($rommap, $start, $length, $data); } sub ReadHTCFLASHKEY { my ($rommap, $fh)= @_; $fh->seek(0xa00, SEEK_SET); my $data; $fh->read($data, 32) or die "reading : $!\n"; if ($data =~ /^HTC\$WALLABY(\d\d)/) { ReadHTCWALLABY($rommap, $fh, $1, 0xa00); } else { ReadHTCWALLABY($rommap, $fh, "22", 0xa00); } } sub ReadHTCWALLABY { my ($rommap, $fh, $type, $offset)= @_; $offset ||= 0; if ($type eq "00") { # bootloader my $data; $fh->seek($offset+0x400, SEEK_SET); $fh->read($data, 0x40000) or die "reading HTC\$WALLABY00: $!\n"; AddToMap($rommap, 0x80000000, length($data), $data); } elsif ($type eq "11") { # ce my $data; $fh->seek($offset+0x400, SEEK_SET); $fh->read($data, 0x01f00000-0x40000) or die "reading HTC\$WALLABY11: $!\n"; AddToMap($rommap, 0x80040000, length($data), $data); } elsif ($type eq "22") { # bootloader+ce my $data; $fh->seek($offset+0x400, SEEK_SET); $fh->read($data, 0x01f00000) or die "reading HTC\$WALLABY22: $!\n"; AddToMap($rommap, 0x80000000, length($data), $data); } else { die "unsupported htc\$wallaby type image\n"; } } sub ReadHIMALAYAS { my ($rommap, $fh)= @_; $fh->seek(0x180, SEEK_SET) or die "seeking to section header\n"; while (1) { my $himasection; $fh->read($himasection, 0x1c) or die "reading section header\n"; if ($himasection =~ /^HTCS(\w{8})(\w{8})(\w{8})/) { my ($start, $length, $checksum)= (hex($1), hex($2), hex($3)); my $data; $fh->read($data, $length) or die "reading HTCS data\n"; $start -= 0x20000000 if (($start&0xf0000000)==0xa0000000); AddToMap($rommap, $start, $length, $data); } elsif ($himasection =~ /^HTCE/) { last; } else { die "unknown section header\n"; } } } sub ReadBIN { my ($rommap, $fh)= @_; my $sig= ReadDword($fh, 64); if ($sig!=0x43454345) { die "cannot parse BIN rom image\n"; } my $hdrofs= ReadDword($fh); my $start; if ($hdrofs>=0x8c000000) { $start= $g_boot_offset; } else { $start= $g_os_offset; } my $data; $fh->seek(0, SEEK_SET); my $length= (-s $fh); $fh->read($data, $length); AddToMap($rommap, $start, $length, $data); } sub ReadDword { my ($fh, $ofs)= @_; $fh->seek($ofs, SEEK_SET) if (defined $ofs); my $data; $fh->read($data, 4); return unpack("V", $data); } sub FindRomChain { my ($rommap)= @_; my @chain; for (my $ofs= $g_boot_offset ; $ofs < $g_end_offset ; $ofs += 0x1000) { my $sig= GetMapDword($rommap, $ofs+0x40); if (defined $sig && $sig==0x43454345) { my $hdrofs= GetMapDword($rommap, $ofs+0x44); if ($hdrofs>0x8c000000 && $ofs==$g_boot_offset) { # bootloader push @chain, { physfirst=>$g_boot_offset, physlast=>$g_os_offset, nrmodules=>1, nrfiles=>0, }; } else { push @chain, ReadRomHdr($rommap, $hdrofs); } } } return \@chain; } sub ReadRomHdr { my ($rommap, $ofs)= @_; return { physfirst=>GetMapDword($rommap, $ofs+8), physlast=>GetMapDword($rommap, $ofs+12), nrmodules=>GetMapDword($rommap, $ofs+16), nrfiles=>GetMapDword($rommap, $ofs+48), }; } sub FindOperatorRom { my ($rommap)= @_; my $startofs= FindOperatorRomStart($rommap); if (!defined $startofs) { return undef; } my $totallength= 0; my $nrfiles= 0; my $ofs = $startofs + 12; while(1) { my $filename= GetMapString($rommap, $ofs); last if (!defined $filename); $ofs += length($filename)+1; my $offset= GetMapDword($rommap, $ofs); $ofs += 4; my $length= GetMapDword($rommap, $ofs); $ofs += 4; my $configid= GetMapString($rommap, $ofs); $ofs += length($configid)+1; $totallength += $length; $nrfiles++; #printf("%08lx %8d %8s %s\n", $offset, $length, $configid, $filename); } my $data= GetMapData($rommap, $ofs, $totallength); if (!defined $data) { return undef; } return { start=>$startofs, length=>$totallength+0x4000, data=>$data, nrfiles=>$nrfiles, }; } sub FindOperatorRomStart { my ($rommap)= @_; for (my $ofs= 0x82000000 ; $ofs>=0x80000000 ; $ofs -= 0x40000) { my $sig= GetMapDword($rommap, $ofs); if (defined $sig && $sig==0x34798243) { if ($ofs>=0x81400000 && $ofs<=0x81bc0000) { return $ofs; } else { warn sprintf("WARNING: Operator rom invisible to autoconfig, it should be between 8140 and 81bc, found at %04lx\n", $ofs>>16); } } } return undef; } # todo: this does not work when there are no rsa keys in the xipchain sub FindXipChainOffset { my ($rommap)= @_; # find all occurrences of 'RSA1' # '0x4c' = offset in struct + xip header my @pos; for (sort values %$rommap) { while ($_->{data} =~ /RSA1/g) { push @pos, $_->{start}+pos($_->{data})-0x4c; } } # look for sequence of 'RSA1' my %posscore; my $start; for (sort @pos) { if (!$start || $_ != $start+0x290) { $start= $_; $posscore{$start}++; } else { $posscore{$start}++; } } # try in descending nr of hits for (sort { $posscore{$b} <=> $posscore{$a} } keys %posscore) { next if ($_%0x1000); # only on page boundary my $nxips= GetMapDword($rommap, $_); if ($nxips >= $posscore{$_}) { return $_; } } return undef; } sub GetXipChain { my ($rommap, $xipoffset)= @_; my $nxips= GetMapDword($rommap, $xipoffset); if (!defined $nxips) { return undef; } if ($nxips<=0 || $nxips>=32) { return undef; } #printf("getting xipchain from %08lx - %d xips\n", $xipoffset, $nxips); my @chain; for my $i (0..$nxips-1) { my $data= GetMapData($rommap, $xipoffset+$i*0x290+4, 0x290); if (!defined $data) { return undef; } my @fields= unpack("V3v2VA32VVa*", $data); push @chain, { pvAddr=>$fields[0], dwLength=>$fields[1], dwMaxLength=>$fields[2], usOrder=>$fields[3], usFlags=>$fields[4], dwVersion=>$fields[5], szName=>$fields[6], dwAlgoFlags=>$fields[7], dwKeyLen=>$fields[8], byPublicKey=>$fields[9], }; #printf("%08lx-%08lx %s\n", $fields[0], $fields[0]+$fields[1], $fields[6]); } return ($xipoffset, \@chain); } sub FindBitmap { my ($rommap, $offset)= @_; my $start= $offset; # 81900000 for 3.x, 81ec0000 for 4.x my $length= 0x25800; my $data= GetMapData($rommap, $start, $length); if (!defined $data) { return undef; } return { start=>$start, length=>$length, data=>$data, }; } sub FindBootloader { my ($rommap)= @_; my $start= $g_boot_offset; my $length= 0x40000; my $data= GetMapData($rommap, $start, $length); if (!defined $data || length($data)!=$length) { return undef; } if ($data =~ /^\x00*$/) { return undef; } return { start=>$start, length=>$length, data=>$data, }; } sub GetMapDword { my ($rommap, $offset)= @_; my $data= GetMapData($rommap, $offset, 4); if (!defined $data) { return undef; } return unpack("V", $data); } sub GetMapString { my ($rommap, $offset)= @_; my $str= ""; while(1) { my $c= GetMapData($rommap, $offset++, 1); if ($c eq "\x00") { return $str; } elsif ($c eq "\xff") { return undef; } $str .= $c; } } sub GetMapData { my ($rommap, $offset, $length)= @_; my $data= ""; for (sort { $a->{start} <=> $b->{start} } values %$rommap) { if ($offset < $_->{start}) { # offset is below range my $blocklen= min($length, $_->{start}-$offset); $data .= "\x00" x $blocklen; $offset += $blocklen; $length -= $blocklen; } last if ($length==0); if ($_->{start} <= $offset && $offset < $_->{start}+$_->{length}) { my $blockofs= $offset-$_->{start}; my $blocklen= min($length, $_->{length}-$blockofs); if ($blocklen==0) { die sprintf("error - did not expect block %08lx(%08lx) @ %08lx(%08lx) looking for %08lx(%08lx)\n", $blockofs, $blocklen, $_->{start}, $_->{length}, $offset, $length); } $data .= substr($_->{data}, $blockofs, $blocklen); $offset += $blocklen; $length -= $blocklen; } last if ($length==0); } if ($length) { $data .= "\x00" x $length; } return $data; } sub MakeBMPHeader { return pack("H*", "424d4858020000000000460000003800"), pack("H*", "0000f0000000c0feffff010010000300"), pack("H*", "000000000000120b0000120b00000000"), pack("H*", "00000000000000f80000e00700001f00"), pack("H*", "000000000000"); } sub MakeBMPFooter { return pack("H*", "0000"); } sub AddToMap { my ($rommap, $blockstart, $blocklength, $block)= @_; if ($g_verbose) { printf("new block %08lx - %08lx L=%08lx\n", $blockstart, $blockstart+$blocklength, $blocklength); } EraseArea($rommap, $blockstart,$blocklength); $rommap->{$blockstart}= { start=>$blockstart, length=>$blocklength, data=>$block, }; } sub EraseArea { my ($rommap, $offset, $length)= @_; my @newblocks; my @tobedeleted; if (!defined $offset) { die "undef offset\n"; } for (sort { $a->{start} <=> $b->{start} } values %$rommap) { if ($offset < $_->{start}) { # offset is below range my $blocklen= min($length, $_->{start}-$offset); $offset += $blocklen; $length -= $blocklen; } last if ($length==0); if ($_->{start} <= $offset && $offset+$length <= $_->{start}+$_->{length}) { my $blockofs= $offset-$_->{start}; my $blocklen= min($length, $_->{length}-$blockofs); my $block; $block= { start=>$_->{start}, length=>$blockofs, data=>substr($_->{data}, 0, $blockofs), }; push(@newblocks, $block) if ($block->{length}); $block= { start=>$_->{start}+$blockofs+$blocklen, length=>$_->{length}-$blockofs-$blocklen, data=>substr($_->{data}, $blockofs+$blocklen), }; push(@newblocks, $block) if ($block->{length}); push @tobedeleted, $_; $offset += $blocklen; $length -= $blocklen; } last if ($length==0); } if ($g_verbose && @tobedeleted) { print "deleting blocks ", join(", ", map { sprintf("%08lx-%08lx", $_->{start}, $_->{start}+$_->{length}); } @tobedeleted), "\n"; } delete $rommap->{$_->{start}} for (@tobedeleted); if ($g_verbose && @newblocks) { print "new blocks: ", join(", ", map { sprintf("%08lx-%08lx", $_->{start}, $_->{start}+$_->{length}); } sort { $a->{start} <=> $b->{start} } @newblocks), "\n"; } $rommap->{$_->{start}}= $_ for (@newblocks); } sub CalcMD5 { my ($rommap, $offset, $length)= @_; my $md5= Digest::MD5->new(); for (sort { $a->{start} <=> $b->{start} } values %$rommap) { if ($offset < $_->{start}) { # offset is below range my $blocklen= min($length, $_->{start}-$offset); $md5->add("\x00" x $blocklen); $offset += $blocklen; $length -= $blocklen; } last if ($length==0); if ($_->{start} <= $offset && $offset < $_->{start}+$_->{length}) { my $blockofs= $offset-$_->{start}; my $blocklen= min($length, $_->{length}-$blockofs); if ($blocklen==0) { die sprintf("error - did not expect block %08lx(%08lx) @ %08lx(%08lx) looking for %08lx(%08lx)\n", $blockofs, $blocklen, $_->{start}, $_->{length}, $offset, $length); } $md5->add(substr($_->{data}, $blockofs, $blocklen)); $offset += $blocklen; $length -= $blocklen; } last if ($length==0); } if ($length) { $md5->add("\x00" x $length); $offset+=$length; $length= 0; } return $md5->hexdigest; } sub CheckForOverlap { my ($info)= @_; my $last; for (sort { $a->{start} <=> $b->{start} } values %$info) { if ($last && $last->{end} > $_->{start}) { die "OVERLAP DETECTED:\n$last->{desc}\n$_->{desc}\n"; } $last= $_; } } sub crc32 { return 0; # Archive::Zip::computeCRC32(shift, 0); }