#!/usr/bin/perl
######################################################################
#
#   File          : split_updata.pl
#   Author(s)     : McSpoon
#   Description   : Unpack a Huawei U8220 'UPDATA.APP' file.
#                   http://pulse.modaco.com
#
#   Last Modified : Thu 24 December 2009
#   By            : McSpoon
#
#   Last Modified : Wed 18 June 2010
#   By            : ZeBadger (z e b a d g e r @ h o t m a i l . c o m)
#   Comment       : Added filename selection
#
#   Last Modified : Wed 19 June 2010
#   By            : ZeBadger (z e b a d g e r @ h o t m a i l . c o m)
#   Comment       : Added CRC checking
#
######################################################################
 
use strict;
use warnings;
 
my %fileHash=(	"\x00\x00\x00\x10","appsboot.mbn",
		"\x00\x00\x00\x20","file25.mbn",
		"\x00\x00\x00\x30","boot.img",
		"\x00\x00\x00\x40","system.img",
		"\x00\x00\x00\x50","userdata.img",
		"\x00\x00\x00\x60","recovery.img",
		"\x00\x00\x00\xC0","file11.mbn",
		"\x00\x00\x00\xD0","file08.mbn",
		"\x00\x00\x00\xE0","file05.mbn",
		"\x00\x00\x00\xF0","file04.mbn",
		"\x00\x00\x00\xF1","file07.mbn",
		"\x00\x00\x00\xF2","splash.raw565",
		"\x00\x00\x00\xF3","file01.mbn",
		"\x00\x00\x00\xF4","file02.mbn",
		"\x00\x00\x00\xF5","file14.mbn",
		"\x00\x00\x00\xF6","boot_versions.txt",
		"\x00\x00\x00\xF7","upgradable_versions.txt",
		"\x00\x00\x00\xF8","file09.mbn",
		"\x00\x00\x00\xF9","version.txt",
		"\x00\x00\x00\xFA","file20.mbn",
		"\x00\x00\x00\xFB","appsboothd.mbn",
		"\x00\x00\x00\xFC","file23.mbn",
		"\x00\x00\x00\xFD","file16.mbn",
		"\x00\x00\x00\xFE","file18.mbn",
		"\x00\x00\x00\xFF","file21.mbn",
	);

my $unknown_count=0;

# Turn on print flushing.
$|++;
 
# Unsigned integers are 4 bytes.
use constant UINT_SIZE => 4;
 
# If a filename wasn't specified on the commmand line then
# assume the file to be unpacked is called "UPDATA.APP". 
my $FILENAME = undef;
if ($#ARGV == -1) {
	$FILENAME = "UPDATA.APP";
}
else {
	$FILENAME = $ARGV[0];
}
 
open(INFILE, $FILENAME) or die "Cannot open $FILENAME: $!\n";
binmode INFILE;
 
# Skip the first 92 bytes, they're blank.
#seek(INFILE, 92, 0);
 
# We'll dump the files into a folder called "output".
my $fileLoc=0;
my $BASEPATH = "output/";
mkdir $BASEPATH;

while (!eof(INFILE))
{
	$fileLoc=&find_next_file($fileLoc);
	seek(INFILE, $fileLoc, 0);
	$fileLoc=&dump_file();
}

close INFILE;
 

# Find the next file block in the main file
sub find_next_file
{
	my ($_fileLoc) = @_;
	my $_buffer = undef;
	my $_skipped=0;

	read(INFILE, $_buffer, UINT_SIZE);
	while ($_buffer ne "\x55\xAA\x5A\xA5" && !eof(INFILE))
	{
		read(INFILE, $_buffer, UINT_SIZE);
		$_skipped+=UINT_SIZE;
	}

	return($_fileLoc + $_skipped);
}
 
# Unpack a file block and output the payload to a file.
sub dump_file {
    my $buffer = undef;
    my $outfilename = undef;
    my $fileSeq;
    my $calculatedcrc = undef;
    my $sourcecrc = undef;
    my $fileChecksum;
 
    # Verify the identifier matches.
    read(INFILE, $buffer, UINT_SIZE); # Packet Identifier
    unless ($buffer eq "\x55\xAA\x5A\xA5") { die "Unrecognised file format. Wrong identifier.\n"; }
    read(INFILE, $buffer, UINT_SIZE); # Packet Length.
    my ($headerLength) = unpack("V", $buffer);
    read(INFILE, $buffer, 4);         # Always 1
    read(INFILE, $buffer, 8);         # Hardware ID
    read(INFILE, $fileSeq, 4);        # File Sequence
    if (exists($fileHash{$fileSeq})) {
	$outfilename=$fileHash{$fileSeq};
    } else {
	$outfilename="unknown_file.$unknown_count";
	$unknown_count++;
    }
    
    read(INFILE, $buffer, UINT_SIZE); # Data file length
    my ($dataLength) = unpack("V", $buffer);
    read(INFILE, $buffer, 16);        # File date
    read(INFILE, $buffer, 16);        # File time
    read(INFILE, $buffer, 16);        # The word INPUT ?
    read(INFILE, $buffer, 16);        # Blank
    read(INFILE, $buffer, 2);         # Checksum of the header maybe?
    read(INFILE, $buffer, 2);         # Always 1?
    read(INFILE, $buffer, 2);         # Blank

    # Grab the checksum of the file
    read(INFILE, $fileChecksum, $headerLength-98);
    $sourcecrc=slimhexdump($fileChecksum);
    
    # Dump the payload.
    read(INFILE, $buffer, $dataLength);
    open(OUTFILE, ">$BASEPATH$outfilename") or die "Unable to create $outfilename: $!\n";
    binmode OUTFILE;
    print OUTFILE $buffer;
    close OUTFILE;

    $calculatedcrc=`./crc $BASEPATH$outfilename`;
    chomp($calculatedcrc);

    print STDOUT "Extracted $outfilename";
    if ($calculatedcrc eq $sourcecrc)
	{
		print " - CRC Okay\n";
	}
	else
	{
		print " - ERROR: CRC did not match\n";
	}
    
    # Ensure we finish on a 4 byte boundary alignment.
    my $remainder = UINT_SIZE - (tell(INFILE) % UINT_SIZE);
    if ($remainder < UINT_SIZE) {
    	# We can ignore the remaining padding.
    	read(INFILE, $buffer, $remainder);
    }
    
    return (tell(INFILE));
}

sub hexdump ()
{
        my $num=0;
        my $i;
        my $rhs;
        my $lhs;
        my ($buf) = @_;
        my $ret_str="";

        foreach $i ($buf =~ m/./gs)
        {
                # This loop is to process each character at a time.
                #
                $lhs .= sprintf(" %02X",ord($i));

                if ($i =~ m/[ -~]/)
                {
                        $rhs .= $i;
                }
                else
                {
                        $rhs .= ".";
                }

                $num++;
                if (($num % 16) == 0)
                {
                        $ret_str.=sprintf("%-50s %s\n",$lhs,$rhs);
                        $lhs="";
                        $rhs="";
                }
        }
        if (($num % 16) != 0)
        {
                $ret_str.=sprintf("%-50s %s\n",$lhs,$rhs);
        }

	return ($ret_str);
}
        
sub slimhexdump ()
{
        my $i;
        my ($buf) = @_;
        my $ret_str="";

        foreach $i ($buf =~ m/./gs)
        {
                # This loop is to process each character at a time.
                #
                $ret_str .= sprintf("%02X",ord($i));
        }

	return ($ret_str);
}
        
