#!perl -w
use strict;
use IO::File;
#use DataDecoder;
#use CrcCalc;

my $ifn= shift or die "need ifn\n";

my $ifh= IO::File->new($ifn, "r") or die "$ifn: $!\n";
binmode $ifh;

my $hdr= readheader($ifh);

printheader($hdr);

for my $entry (@{$hdr->{entries}}) {

    if ($entry->{name} eq "SPL") {
        savespl($ifh, $entry);
    }
    else {
        saveentry($ifh, $entry);
    }
}

$ifh->close();
exit(0);
sub saveentry {
    my ($fh, $entry)= @_;

    my $fn= sprintf("%08lx-%s.nb", $entry->{offset}, $entry->{name});
    my $oh= IO::File->new($fn, "w") or die "$fn: $!\n";
    binmode $oh;

    my $index=0;
    for (my $ofs= 0 ; $ofs < $entry->{length} ; $ofs+=0x178) {
        my $wanted= $entry->{length}-$ofs;
        $wanted = 0x178 if ($wanted>0x178);
        my $data;
        $fh->read($data, $wanted);

        $oh->print(DataDecoder::decodedatachunk($data, $index));

        $index++;
    }
    $oh->close();
}

sub savespl {
    my ($fh, $entry)= @_;

    my $fn1= sprintf("%08lx-%s1.nb", $entry->{offset}, $entry->{name});
    my $oh1= IO::File->new($fn1, "w") or die "$fn1: $!\n";
    binmode $oh1;

    my $fn2= sprintf("%08lx-%s2.nb", $entry->{offset}, $entry->{name});
    my $oh2= IO::File->new($fn2, "w") or die "$fn2: $!\n";
    binmode $oh2;

    my $outchunksize= 0x200;
    my $outchunk="";
    my $index=0;
    for (my $ofs= 0 ; $ofs < $entry->{length} ; $ofs+=0x178) {
        my $wanted= $entry->{length}-$ofs;
        $wanted = 0x178 if ($wanted>0x178);
        my $data;

        $fh->read($data, $wanted);

        $outchunk .= DataDecoder::decodedatachunk($data, $index);

        if (length($outchunk)>=2*$outchunksize) {
            $oh1->print(substr($outchunk,0,$outchunksize));
            $oh2->print(substr($outchunk,$outchunksize,$outchunksize));

            $outchunk = substr($outchunk, 2*$outchunksize);
        }

        $index++;
    }
    if (length($outchunk)>0) {
        $oh1->print(substr($outchunk,0,$outchunksize));
        $oh2->print(substr($outchunk,$outchunksize,$outchunksize));
        $outchunk="";
    }
    $oh1->close();
    $oh2->close();
}
sub skipentry {
    my ($fh, $entry)= @_;

    $fh->seek($entry->{length}, SEEK_CUR);
}
sub printheader {
    my $hdr= shift;
    for my $key (sort keys %$hdr) {
        next if ($key eq "entries");
        printf("%-10s %s\n", $key, $hdr->{$key});
    }
    print "\n";
    for my $entry (@{$hdr->{entries}}) {
        printheaderentry($entry);
    }

}
sub printheaderentry {
    my $entry= shift;
    printf("%08lx %08lx %08lx %s\n", $entry->{offset}, $entry->{length}, $entry->{checksum}, $entry->{name});
}
sub readheader {
    my $fh= shift;
    $ifh->seek(0, SEEK_SET);

    my $data;
    $ifh->read($data, 512) or die "reading $ifn: $!\n";
    return parseheader(decodeheader($data));
}
sub decodeheader {
    my $data= shift;
    my @xor_bytes= (
        0x27, 0xBC, 0x9A, 0xD5, 0x0A, 0x93, 0x6D, 0x02, 
        0x2C, 0x61, 0x0E, 0x6E, 0xBA, 0x51, 0x09, 0x99, 
        0x29, 0x57, 0xDE, 0x54, 0x94, 0x2B, 0x6F, 0x2A,
        0x35, 0xA5, 0x63, 0x59, 0x12, 0x0B, 0x63, 0xE3,
        0xDB, 0x77, 0x6E, 0x3E, 0xA4, 0xB8, 0xDC, 0x79,
        0x1E, 0xE9, 0xD5, 0x80, 0xD6, 0xC9, 0xBB, 0xDB,
        0x93, 0x06, 0xD7, 0xCD, 0x49, 0x2D, 0xDA, 0x15,
        0x07, 0x2D, 0xB8, 0xB7, 0x99, 0x95, 0xBA, 0xCF,
    );
    my @bytes = unpack("C*", $data);
    my $decoded= pack("C*", map { (($bytes[$_]-$_)&0xff) ^ $xor_bytes[$_%@xor_bytes] } 0..$#bytes-4);

    my $storedcrc= unpack("V", substr($data, -4,4));
    my $calccrc= CrcCalc::crc32($decoded, 0);

    printf("crc l=%08lx data=%08lx calc=%08lx\n", length($decoded), $storedcrc, $calccrc);
    return $decoded;
}
sub parseheader {
    my $hdrdata=shift;
    my %hdr;
    my $list;
    my $rest;
    (
        $hdr{magic}, $hdr{device}, $hdr{version},
        $hdr{language}, $hdr{version2},
        $list, 
        $hdr{entrycount}, $hdr{othercount}, $hdr{operator},
        $rest,
    ) = unpack("A16A16A16A16A16a168VVA16a*", $hdrdata);

    for (my $i=0 ; $i<$hdr{entrycount} ; $i++) {
        push @{$hdr{entries}}, splithdrentry(substr($list,$i*28,28));
    }
    if ($hdr{entrycount}<6 
        && substr($list, $hdr{entrycount}*28) !~ /^\x00*$/) {
        $hdr{extralist}= substr($list, $hdr{entrycount}*28);
    }
    if ($rest !~ /^\x00+$/) {
        $hdr{rest}= $rest;
    }
    return \%hdr;
}
sub splithdrentry {
    my $data= shift;
    my %entry;

    (
        $entry{name}, $entry{offset}, $entry{length}, $entry{checksum}
    ) = unpack("A16VVV", $data);

    return \%entry;
}

package DataDecoder;

our @EXPORT= qw(decodedatachunk);
my $keytable;
INIT {
    $keytable= pack("C*", 
        0xA3, 0xC9, 0x84, 0x3C, 0x0A, 0xB3, 0xA9, 0x63, 
        0x15, 0x52, 0xCE, 0x33, 0x29, 0xC8, 0x99, 0x88, 
        0x43, 0x6A, 0x1F, 0xF8, 0x32, 0x21, 0xB1, 0x59, 
        0x04, 0x0F, 0xF9, 0x83, 0xBE, 0x76, 0x47, 0x57, 
        0x50, 0xD6, 0x25, 0x01, 0xC7, 0x56, 0x31, 0x79, 
        0xC5, 0x65, 0x2C, 0x50, 0x2E, 0xA1, 0x4E, 0xA9, 
        0x25, 0xF8, 0xDC, 0x86, 0x37, 0x0D, 0xFD, 0x45, 
        0xD5, 0xDD, 0xCA, 0x6A, 0x08, 0xA2, 0xA4, 0x9E, 
        0x60, 0xF8, 0xCA, 0xF8, 0x2E, 0x3E, 0x2A, 0x7C, 
        0xF2, 0x3F, 0x78, 0xE1, 0x17, 0x11, 0x7E, 0x98, 
        0xDE, 0x3E, 0xB3, 0x09, 0x99, 0x22, 0x0E, 0x20, 
        0x19, 0x94, 0x1C, 0x08, 0x69, 0xC9, 0x51, 0x36, 
        0xBD, 0x74, 0x9C, 0xAA, 0xA5, 0x32, 0x3D, 0x71, 
        0x86, 0x26, 0xDB, 0x6F, 0x4B, 0xE0, 0xD1, 0x5B, 
        0x58, 0x84, 0xCA, 0x0B, 0xBF, 0x28, 0x8D, 0xF2, 
        0xB7, 0x7D, 0x1A, 0xE7, 0x4A, 0xB2, 0xF5, 0x27, 
        0x4D, 0x95, 0xC2, 0x9E, 0x95, 0xFC, 0x12, 0x62, 
        0x68, 0x63, 0x7D, 0x81, 0x32, 0xD6, 0xF1, 0xFC, 
        0x79, 0x12, 0x49, 0x13, 0x13, 0xE7, 0x21, 0xC3, 
        0x97, 0xE0, 0xE9, 0x8E, 0x11, 0x26, 0x39, 0x7A, 
        0xFA, 0xA2, 0x64, 0x5C, 0x67, 0x62, 0x50, 0x56, 
        0x80, 0x3E, 0x85, 0x9F, 0x34, 0xBB, 0x83, 0x82, 
        0x2B, 0x2F, 0x59, 0xAA, 0xFC, 0x26, 0x73, 0x9C, 
        0xA1, 0x05, 0xB4, 0x85, 0x28, 0xED, 0xC5, 0x36, 
        0xAA, 0xE2, 0xAC, 0x6D, 0x81, 0x2D, 0xA0, 0x56, 
        0xB6, 0xFE, 0x1B, 0x51, 0xB9, 0x56, 0x32, 0xF6, 
        0x55, 0x24, 0x22, 0x56, 0xE2, 0xAF, 0x2B, 0x84, 
        0xBD, 0x33, 0xA1, 0x55, 0xF5, 0xCF, 0x40, 0x63, 
        0x47, 0x9E, 0xC0, 0x58, 0x4D, 0x25, 0xA9, 0x69, 
        0xF2, 0xED, 0x69, 0x20, 0x29, 0x70, 0xA3, 0x5F, 
        0xDF, 0x3A, 0xCB, 0xA2, 0x2D, 0x48, 0xF0, 0x84, 
        0xD3, 0xB4, 0xD8, 0x85, 0xE2, 0x94, 0x52, 0x0A, 
        0xB8, 0x1F, 0xC8, 0xA5, 0x31, 0x11, 0x14, 0x97, 
        0x1C, 0x51, 0x94, 0x93, 0xEC, 0xD1, 0x81, 0xC5, 
        0xB0, 0xB6, 0x7C, 0x13, 0x46, 0xBA, 0x6A, 0xA3, 
        0xCB, 0xCE, 0x82, 0x9D, 0x55, 0x03, 0xA4, 0x32, 
        0xE5, 0xAA, 0xED, 0xDD, 0x97, 0xBA, 0x88, 0xE8, 
        0x1C, 0x73, 0xC7, 0x33, 0x6A, 0x41, 0x72, 0x31, 
        0xB2, 0xE3, 0x5F, 0x33, 0x93, 0xCC, 0x42, 0xE9, 
        0x8D, 0xCA, 0xC7, 0x25, 0xB8, 0xE5, 0xDE, 0xE3, 
        0xB6, 0x8A, 0x56, 0x87, 0xE6, 0xE9, 0xAE, 0x65, 
        0xDA, 0x9B, 0x28, 0x86, 0x0C, 0x88, 0x1E, 0xA9, 
        0xCC, 0x08, 0x99, 0x89, 0x7D, 0x48, 0x20, 0x5E, 
        0x01, 0xF0, 0xCE, 0xA6, 0x72, 0x02, 0xA8, 0x25, 
        0x13, 0x05, 0x2D, 0x2B, 0x86, 0x64, 0x2E, 0x15, 
        0x3F, 0x11, 0xDF, 0x16, 0x38, 0x6D, 0x2F, 0x38, 
        0xE6, 0x6C, 0x55, 0x9D, 0x6D, 0xF4, 0xAB, 0x0B
    );
}
sub decodedatachunk {
    my ($data, $index)= @_;

    my @decoded;
    my @bytes = unpack("C*", $data);
    my $subdata = pack("C*", map { ($bytes[$_]-$_)&0xff } (0..$#bytes));
    if (length($data)>4) {
        my $crc= CrcCalc::crc32(substr($data,4), $index);

        return $subdata ^ substr(pack("Va*", $crc, $keytable),0,length($subdata));
    }
    else {
        return $subdata ^ substr($keytable,0,length($subdata));
    }
}


package CrcCalc; 
our @EXPORT= qw(crc32);
my @crc32tab;

INIT {
    initcrc(0xEDB88320);
}
sub calccrc32tab {
    my ($c, $crcBase)= @_;
    my $val = $c;
    for (my $i=0 ; $i<8 ; $i++)
    {
      $val = ($val>>1)^ ( ($val&1)?$crcBase:0 );
    }
    return $val;
}

sub initcrc {
    my ($crcBase)= @_;
    my $val;
    for (my $i=0 ; $i<256 ; $i++)
    {
        $val = calccrc32tab($i, $crcBase);
        $crc32tab[$i]=$val;
    }
}

sub crc32byte {
    my ($c, $crc)= @_;
    return ($crc>>8) ^ $crc32tab[($crc^ord($c))&0xff];
}

sub crc32 {
    my ($data, $crc)= @_;
    $crc ||= 0;

    for (my $i=0 ; $i<length($data) ; $i++) {
        $crc= crc32byte(substr($data,$i,1),$crc);
    }
    return $crc;
}


