#!/usr/bin/perl -w # (C) 2003-2007 Willem Jan Hengeveld # Web: http://www.xs4all.nl/~itsme/ # http://wiki.xda-developers.com/ # # $Id$ # use strict; use Getopt::Long; use IO::File; use IO::Dir; use Time::localtime; my $g_romfilesdir; my $g_bootsplashbmp; my $g_outputfile; my $g_devicetype= ""; my $g_operator= ""; my $g_language= ""; my $g_formagician; sub usage { return <<__EOF__ Usage: makeextrom [-f filesdir] [-b splashbmp] [-w outputfile] -st DEVICETYPE -so OPERATORNAME -sl LANGUAGE -m create extrom for the magician, instead of himalaya __EOF__ } GetOptions( "f=s"=> \$g_romfilesdir, "b=s"=> \$g_bootsplashbmp, "w=s"=> \$g_outputfile, "sl=s"=> \$g_language, "so=s"=> \$g_operator, "st=s"=> \$g_devicetype, "m" => \$g_formagician, ) or die usage(); my %g_fat= ( OEMID=>'CP200 ', BytesPerSector=>512, SectorsPerCluster=>4, ReservedSectors=>1, NumberOfFats=>1, RootEntries=>512, NumberOfSectors=>$g_formagician?38912:32768, MediaDescriptor=>248, SectorsPerFAT=>$g_formagician?38:32, SectorsPerHead=>$g_formagician?38:32, HeadsPerCylinder=>1, HiddenSectors=>0, BigNumberOfSectors=>0, PhysicalDrive=>128, CurrentHead=>0, Signature=>41, SerialNumber=>0, VolumeLabel=>'', FSID=>'FAT16 ', ); my @g_files= ReadFiles($g_romfilesdir); my $g_identity= CreateIdentity(); my $g_bootimg= CreateBootSector(\%g_fat); my $g_fatimg= CreateFat(\@g_files, \%g_fat); my $g_rootimg= CreateRootdir(\@g_files, \%g_fat); my $g_splash= ReadSplash($g_bootsplashbmp); my $g_baseoffset= ($g_outputfile =~ /\.nb[af]$/) ? 0x40 : 0; my $g_fh= IO::File->new($g_outputfile, "w") or die "$g_outputfile: $!\n"; binmode $g_fh; my $g_bootsectorofs; if ($g_formagician) { $g_bootsectorofs= $g_baseoffset; $g_fh->seek($g_baseoffset, SEEK_SET); $g_fh->write($g_bootimg); $g_fh->write($g_fatimg) for (1..$g_fat{NumberOfFats}); $g_fh->write($g_rootimg); } else { # for himalaya $g_fh->seek($g_baseoffset, SEEK_SET); $g_fh->write($g_identity); $g_fh->seek($g_baseoffset+0x8000, SEEK_SET); $g_fh->write($g_splash); $g_fh->seek($g_baseoffset+0x30000, SEEK_SET); $g_fh->write($g_splash); $g_fh->seek($g_baseoffset+0x70000, SEEK_SET); $g_bootsectorofs= $g_baseoffset+0x70000; $g_fh->write($g_bootimg); $g_fh->write($g_fatimg) for (1..$g_fat{NumberOfFats}); $g_fh->write($g_rootimg); } my $g_cluster2ofs= $g_fh->tell(); my $g_clustersize= $g_fat{BytesPerSector} * $g_fat{SectorsPerCluster}; for (@g_files) { if (($g_fh->tell()-$g_cluster2ofs)/$g_clustersize+2 != $_->{startcluster}) { warn "incorrect startcluster for $_->{name}\n"; } $g_fh->write($_->{data}); $g_fh->write("\x00" x $_->{clusterfillsize}) if ($_->{clusterfillsize}); } $g_fh->write("\x00" x ($g_fat{BytesPerSector}*$g_fat{NumberOfSectors}-($g_fh->tell() - $g_bootsectorofs)) ); if ($g_formagician) { $g_fh->seek($g_baseoffset+0x01380000, SEEK_SET); $g_fh->write($g_splash); $g_fh->write("\x00" x (0x40000 - length($g_splash))); } if ($g_outputfile =~ /\.nb[af]$/) { warn "nbf header writing not yet implemented, use xda2nbftool to fix the header\n"; } $g_fh->close(); exit(0); # reads data for all files. # returns list of file entries # { data=>'binarydata', name=>'filename', size=>filesize } sub ReadFiles { my ($dirname)= @_; if (!-d $dirname) { die "$dirname does not exist\n"; } my %dir; tie %dir, 'IO::Dir', $dirname; return map { { data=>ReadFile("$dirname/$_"), name=>$_, size=> $dir{$_}->size, created=> $dir{$_}->mtime, } } grep -f "$dirname/$_", keys %dir; } sub ReadFile { my ($name)= @_; my $fh= IO::File->new($name, "r") or die "$name: $!\n"; binmode $fh; my $data; $fh->read($data, -s $fh); $fh->close(); return $data; } # constructs bootsector from fatinfo # returns 512 byte sector sub CreateBootSector { my ($fatinfo)= @_; my @fieldnames= qw(Jump OEMID BytesPerSector SectorsPerCluster ReservedSectors NumberOfFats RootEntries NumberOfSectors MediaDescriptor SectorsPerFAT SectorsPerHead HeadsPerCylinder HiddenSectors BigNumberOfSectors PhysicalDrive CurrentHead Signature SerialNumber VolumeLabel FSID); my $data= pack("A3A8vCvCvvCvvvVVCCCVA11A8", map { exists $fatinfo->{$_} ? $fatinfo->{$_} : '' } @fieldnames); #printf("bootsize= %d\n", length($data)); $data .= "\x00" x (510 - length($data)); $data .= "\x55\xaa"; return $data; } # allocates clusters for all files # returns fat image. # adds 'startcluster' property to each file # and 'clusterfillsize' sub CreateFat { my ($files, $fatinfo)= @_; my $clustersize= $fatinfo->{BytesPerSector} * $fatinfo->{SectorsPerCluster}; #printf("clustersize= %d\n", $clustersize); my $cluster= 2; my @fat= ( -8, -1 ); for (@$files) { $_->{startcluster}= $cluster; if ($_->{size} % $clustersize) { $_->{clusterfillsize}= $clustersize - ($_->{size} % $clustersize); } else { $_->{clusterfillsize}= 0; } if ($_->{size}) { my $nrofclusters = ($_->{size}+$_->{clusterfillsize})/$clustersize; $_->{clusterlist}= [ $cluster .. ($cluster+$nrofclusters-1) ]; $fat[$_]= $_+1 for ( $cluster .. ($cluster+$nrofclusters-2) ); $fat[($cluster+$nrofclusters-1)]= -1; #printf("nc=%d s=%d fill=%d\n", $nrofclusters, $_->{size}, $_->{clusterfillsize}); $cluster += $nrofclusters; } else { # if we always need to allocate 1 cluster uncomment this: #$cluster++; #$_->{clusterfillsize}= $clustersize; # empty files have clusternr 0 $_->{startcluster} = 0; } } my $fatimg= pack("v*", @fat); #printf("fatsize= %d maxcluster= %d : %d %d %d %d\n", length($fatimg), $cluster, @fat[0..3]); my $fatbytesize= $fatinfo->{SectorsPerFAT}*$fatinfo->{BytesPerSector}; $fatimg .= "\x00" x ($fatbytesize - length($fatimg)); #printf("fatimg= %d\n", length($fatimg)); return $fatimg; } # creates directory image from # list of files sub CreateRootdir { my ($files, $fatinfo)= @_; my %namelist; my $rootimg; for (@$files) { $rootimg .= MakeDirectoryEntry($_, \%namelist); } #printf("rootsize= %d\n", length($rootimg)); my $rootbytesize= $fatinfo->{RootEntries} * 32; $rootimg .= "\x00" x ($rootbytesize - length($rootimg)); return $rootimg; } sub MakeDirectoryEntry { my ($file, $namelist)= @_; my $filename8dot3= Make8dot3Name($file->{name}, $namelist); my $checksum8dot3= CalcChecksum($filename8dot3); my $direntry; my @unicodename= unpack("U*", $file->{name}); if (@unicodename%13) { push @unicodename, 0; } while (@unicodename%13) { push @unicodename, -1; } my @lfn; my $lfnid= 1; for (my $i= 0 ; $i<@unicodename ; $i += 13) { my $last= $i+13>=@unicodename ? 0x40 : 0; push @lfn, pack("Cv5vCv6vv2", $lfnid|$last, # ofs 00 @unicodename[$i..$i+4], # ofs 01 15, # ofs 0b attributes $checksum8dot3, # ofs 0d @unicodename[$i+5..$i+10], # ofs 0e 0, # ofs 1a @unicodename[$i+11..$i+12]); # ofs 1c $lfnid++; } $direntry= join "", reverse @lfn; $direntry .= pack("A11Ca10VvV", $filename8dot3, # ofs 00 0, # ofs 0b attributes '', # ofs 0c reserved DOSPackTime($file->{created}), # ofs 16 time,date $file->{startcluster}, # ofs 1a $file->{size}); # ofs 1c #printf("direntry: %d bytes for %-13s %s\n", length($direntry), $filename8dot3, $file->{name}); return $direntry; } # extracts the bootsplash bitmap from a bmp file. sub ReadSplash { my ($splashname)= @_; my $fh=IO::File->new($splashname, "r") or die "$splashname: $!\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"; } return substr($data, 70, 0x25800); } sub DOSPackTime { my $timestamp = shift; my $t= localtime $timestamp; my $dos= ($t->mday) | (($t->mon+1)<<5) | (($t->year+1900-1980)<<9) | (($t->sec/2)<<16) | (($t->min)<<21) | (($t->hour)<<27); #printf("%08lx -> %d-%d-%d %d:%d:%d = %08lx\n", $timestamp, $t->year+1900-1980, $t->mon+1, $t->mday, $t->hour, $t->min, $t->sec, $dos); return $dos; } sub Make8dot3Name { my ($longname, $namelist)= @_; my $validchars= '$%`\'-@{}~!#()&_^'; if ($longname =~ /^([${validchars}a-zA-Z0-9]{0,8})\.([${validchars}a-zA-Z0-9]{0,3})$/) { my ($basename, $ext)= ($1, $2); my $name8dot3= sprintf("%-8s%-3s", $basename, $ext); if (!exists $namelist->{$name8dot3}) { $namelist->{$name8dot3}= 2; #printf("8.3name equals longname : %s == %s\n", $name8dot3, $longname); return $name8dot3; } } my ($basename, $ext)= ("file", ""); if ($longname =~ /(\w{4})/) { $basename= $1; } if ($longname =~ /\.(\w{1,3})[^.]*$/) { $ext= $1; } #printf("finding name: base=%s ext=%s\n", $basename, $ext); my $id= 1; my $name= sprintf("%-8s%3s", sprintf("%s~%03d", $basename, $id), $ext); while (exists $namelist->{$name}) { $id++; $name= sprintf("%-8s%3s", sprintf("%s~%03d", $basename, $id), $ext); } $namelist->{$name}= 1; return $name; } sub CalcChecksum { my ($name)= @_; my $sum= 0; for (unpack("U*", $name)) { $sum = (($sum&1)<<7) | (($sum>>1)&0x7f); $sum += $_; } return $sum & 0xff; } sub CreateIdentity { return pack("a10a20a10", $g_devicetype, $g_operator, $g_language) . ("\xff" x 32728); }