#!/usr/bin/perl -w
# By Tom Hargreaves <hex@freezone.co.uk>, public domain.
# usage: fixrar.pl corrupt.rar > fixed.rar

use Digest::CRC qw[crc32];

my $f;
open($f,$ARGV[0]||'-') or die "$!";
binmode $f;

eval {
    use Sys::Mmap;
    mmap $_, 0, PROT_READ, MAP_SHARED, $f, 0 or die;
};
if ($@) {
    print STDERR "Warning: mmap failed, slurping...\n";
    local $/=undef;
    $_=<$f>;
}

my $headok=1;
my $magic = /^Rar!\x1a\x07\x00/g;

if ($magic) {
    my $head = substr $_, pos, 13;
    my ($crc, $type, $flags, $size, $res1, $res2) = unpack"vCvvvV", $head;
    $headok=0 if $size<13;
    
    $head = substr $_, pos, $size;
    $headok=0 if $crc!=(crc32(substr($head,2,$size-2)) &0xffff);

    if ($headok) {
	print "Rar!\x1a\x07\x00$head";
	pos() += $size;
    }
} else {
    $headok=0;
}

if (!$headok) {
    printf STDERR "Warning: archive header missing or corrupt, attempting replacement.\n";
    
    print "Rar!\x1a\x07\x00\xcf\x90\x73\x00\x00\x0d\x00\x00\x00\x00\x00\x00\x00";
}

while (1) {
    $pos=pos;
    if (my $size=checkhead($pos)) {
	print substr $_, $pos, $size;
	pos() = $pos+$size;
    } else {
	printf STDERR "Searching... ";
	# look for likely header positions
	pos()+=37;
	if (/[\x00\x01\x02\x03].{11}\x00..\x00\x00[ -~]{5}/gc) {
	    pos() -= 37;
	    printf STDERR "trying offset 0x%x\n", pos; 
	} else {
	    printf STDERR "that's all.\n";
	    last;
	}
    }
}

sub checkhead {
    my $pos=shift;

    my $head = substr $_, $pos, 28;
    my ($crc, $type, $flags, $size, $psize, $usize,
	$os, $fcrc, $time, $uver, $meth, $nsize, $fattr)
	= unpack "vCvvVVCVVCCvV", $head;

    return 0 if $size<28 || $size>1024;

    $head = substr $_, pos, $size;
    return 0 if $crc!=(crc32(substr($head,2,$size-2)) &0xffff);

    printf STDERR "%s\n",unpack"Z*", substr $_,$pos+32,$size-32;

    return $size+$psize;
}
