#!/usr/bin/env perl -w
# $Source: /home/cvs/extraction_tools/unwad.pl,v $
# $Id: unwad.pl,v 1.3 2007-12-07 08:42:58 tarragon Exp $

use strict;
use Getopt::Std;
use File::Spec;

sub usage {
	print <<EOF;

This script will extract the files from a GH3 hed/wad archive.

Usage: $0 -h <hed file> -w <wad file> [-d <output directory>] [-v] [-t]

  -h : the hed (header) file to be scanned (required)
  -w : the wad (binary data) file to be extracted (required)
  -d : directory to extract files to (defaults to current directory)
  -v : print lots of diagnostic info
  -t : do everything except actually write the files

EOF

exit;
}

our $opt_h = ''; # hed file
our $opt_w = ''; # wad file
our $opt_d = '.'; # output directory
our $opt_v = 0; # verbose
our $opt_t = 0; # test
getopts('h:w:d:vt');
usage if $opt_h eq '' ;
usage if $opt_w eq '' ;

# check output dir before we go any further
-d $opt_d || die "Output directory does not exist.\n";

# process header file
my $OFFSET = 0x800;
my $pointer = 0;
my @filelist = ();
my $buffer;
my $read;

open FILE, $opt_h or die "Could not open $opt_h! $!\n";

print "Processing $opt_h ...\n";
while (1) {
	$opt_v && print  " Searching for start offset.\n";
	$opt_v && printf " - Pos   : \%10D (0x\%08X)\n", $pointer, $pointer;

	# read file offset multiplier - 0xffffffff is end of header
	$read = read FILE, $buffer, 4;
	$read < 4 && die ( sprintf "Premature end of header file at \%10D (0x\%08X) : EOH not found.\n", $pointer, $pointer );
	$pointer += 4;
	my $file_start = unpack "V*", $buffer;
	if ( $file_start == 0xffffffff ) {
		$opt_v && print " - Found : End of Header\n";
		last;
	}
	$opt_v && printf " - Found : \%10D (0x\%08X)\n", $file_start, $file_start;
	$opt_v && printf " - Real  : \%10D (0x\%08X)\n", $file_start * $OFFSET, $file_start * $OFFSET;

	$opt_v && print  " Searching for file size.\n";
	$opt_v && printf " - Pos   : \%10D (0x\%08X)\n", $pointer, $pointer;

	# read file length
	$read = read FILE, $buffer, 4;
	$read < 0 && die ( sprintf "Premature end of header file at \%10D (0x\%08X) : missing file length value.\n", $pointer, $pointer );
	$pointer += 4;
	my $file_length = unpack "V*", $buffer;
	$opt_v && printf " - Found : \%10D (0x\%08X)\n", $file_length, $file_length;

	$opt_v && print  " Searching for filename.\n";
	$opt_v && printf " - Pos   : \%10D (0x\%08X)\n", $pointer, $pointer;

	# read filename
	my $file_name;
	FILENAME: while (1) {
	  $read = read FILE, $buffer, 4;
	  $read < 4 && die ( sprintf "Premature end of header file at \%10D (0x\%08X) : missing filename.\n", $pointer, $pointer ); 
	  $pointer += 4;
	  my @chars = unpack "C*", $buffer;
	  foreach my $char ( @chars ) {
	  	last FILENAME if $char == 0;
		$file_name = $file_name . chr $char;
	  }
	}
	$opt_v && print  " - Found : " . $file_name . "\n";

	push @filelist, (
		{
			filename	=> $file_name,
			start		=> $file_start * $OFFSET,
			length		=> $file_length,
		}
	);
	$opt_v && print "\n";
}

close FILE;

print "Found " . scalar @filelist . " files.\n";

# process wad file
my $wadpos = 0;

open FILE, $opt_w or die "Could not open $opt_w! $!\n";
binmode FILE;

foreach my $files_href ( @filelist ) {
	my $start = $files_href->{'start'};
	my $length = $files_href->{'length'};
	my $filename = $files_href->{'filename'};

	# filenames use either / or \ as the directory delimiter.
	$filename =~ s#\\#/#g;

	$opt_v && printf "Current Position  : \%010D (0x\%08X)\n", $wadpos, $wadpos;
	$opt_v && printf "Expected Position : \%010D (0x\%08X)\n", $start, $start;
	
	# skip junk, if any
	if ( $wadpos < $start ) {
		my $junk_count = $start - $wadpos;
		$opt_v && printf "Skipping \%010D (\%08X) bytes of junk.\n", $junk_count, $junk_count;	
		my $skipit = seek FILE, $junk_count, 1;
		$skipit || die sprintf "Couldn't seek past junk at \%010D (0x\%08X)! $!\n", $wadpos, $wadpos;
		$wadpos += $junk_count;
		$opt_v && printf "New Position      : \%010D (0x\%08X)\n", $wadpos, $wadpos;
	}
	elsif ( $wadpos > $start ) {
		die "Offset exception when attempting to extract $filename.\n";
	}
	# split out directories and filename
	my @filename_parts = split /\//, $filename;
	my $basename = pop @filename_parts;  

	# create subdirectories, if necessary
	my $path = $opt_d ;
	foreach my $part ( @filename_parts ) {
		$opt_v && print "Checking for directory: $part\n";
		$path = File::Spec->catdir ($path,  $part);
		if ( ! $opt_t ) { -d $path || mkdir $path; }
	}

	# create the complete, OS safe, path and filename
	my $full_filename = File::Spec->catfile ($path, $basename);
	
	if ( -f $full_filename ) {
		print "Not extracting $full_filename: file exists.\n";
	}
	else {
		print "Extracting $full_filename\n";
		$opt_t || open OUT, ">:raw", "$full_filename";

		while (1) {
			if ( $OFFSET < $length ) {
				$read = read FILE, $buffer, $OFFSET;
				$read < $OFFSET && die ( sprintf "Premature end of wad file at \%10D (0x\%08X).\n", $wadpos, $wadpos); 
				$opt_t || print OUT $buffer;
				$length -= $OFFSET;
				$wadpos += $OFFSET;
			}
			else {
				$read = read FILE, $buffer, $length;
				$read < $length && die ( sprintf "Premature end of wad file at \%10D (0x\%08X).\n", $wadpos, $wadpos); 
				$opt_t || print OUT $buffer;
				$wadpos += $length;
				last;
			}
		}

		$opt_t || close OUT;
	}
}
close FILE;
print "Done!\n";

