#!/usr/bin/perl
#
#
# Version 1.2
#
#
# Scan a specified path for dlls and cross reference imports in imports.html
# Dump all dumpbin /imports and /exports output to imports.txt and exports.txt respectively
# Parse imports.txt and exports.txt and place output in imported_api.txt and exported_api.txt
#
#
# I am using ActiveState ActivePerl 5.8 on Windows XP
# 
#
# BUGS
#
# Cannot handle spaces in directory or file names
# Errors are not reported when parsing exports
#
#
# TODO
#
# Format output in a matrix
# Possibly make format selectable on command line, HTML or matrix output
# Include API imports/exports as part of the HTML map
# Automatically enter data into a database
# Greater flexibility in command line options.  Parse only imports, or skip dump and parse only for example.
# Should EXE files be dumped as well?


if (scalar @ARGV <=0){
die "Usage: $0 <directory1> [-parse]\n";
}

print("Generating a list of dlls...\n");
foreach $dir (@ARGV[0]){
 push @dir,$dir;
 while ($dirs=pop @dir){
   $whereami=`cd`;
   chomp($whereami);
   chomp($dirs);
   chdir $dirs;
   while (<*>) {
      if (-d $_) {
       push @dir,$dirs."\\".$_;
     } 
else {
	push (@unsorted, "$_  $dirs\n") if /\.dll$/;
     }
   }
   chdir $whereami;
 }

print("Sorting and converting dlls to lowercase...\n");
@lowercase = map (lc, @unsorted);
undef @unsorted;
@sorted=sort(@lowercase);
undef @lowercase;
open (STDOUT2, ">&STDOUT");
print("Dumping imports...\n");
open(STDOUT, "> imports.txt") || die "Unable to open imports.txt for writing!";
	
foreach $line (@sorted) {
	push @array, $line;
	chomp($line);
	($a,$b)=split(/\s\s+/,$line);
	print STDOUT2 ("Dumping imports for $a\n");
	system("dumpbin /imports $b/$a");
	}
}

open(STDOUT, ">&STDOUT2");
print("Dumping exports...\n");
open(STDOUT, "> exports.txt") || die "Unable to open exports.txt for writing!";

foreach $line (@sorted) {
	push @array, $line;
	chomp($line);
	($a,$b)=split(/\s\s+/,$line);
	print STDOUT2 ("Dumping exports for $a\n");
	system("dumpbin /exports $b/$a");
	}

open(DATA, "imports.txt") || die "Unable to open dump.txt for reading!";
open(IMPORTS, ">imports.html") || die "Unable to open imports.html for writing!";
print STDOUT2 ("Generating HTML...\n");

while (<DATA>) {
	sort_imports();
	}

sub sort_imports {
	chomp($_);
	s/^\s*(.*?)\s*$/$1/;
	($a,$b,$c,$d,$e)=split(/\s+/,$_);

		if ($d =~ /\.dll$/) {
		($path,$name)=split(/\//,$d);
		($name,$b)=split(/\./,$name);
		print IMPORTS "<br><li> <A NAME=\"$name\"> $name imports: <br>";
	}

	if ($a =~ /\.dll$/) {
		push (@imports, "$a");
	}
	elsif ($a =~ /Summary|DUMPBIN/) {
		@alpha = sort (map (lc, @imports));
		foreach $line (@alpha) {
			($line,$b)=split(/\./,$line);
			print IMPORTS "<A HREF=\"#$line\">$line</A><br>";
		}

		undef @imports;

	}

	else { 
		next;
		}


	}
close(DATA);
close(IMPORTS);
if ($ARGV [1] eq '-parse') {

open(IMPORTS, "> imported_api.txt");
print STDOUT2 ("\nParsing import list.  This may take several minutes.\n\n");
open(DATA, "imports.txt") || die "Unable to open imports.txt!";
LINE: while(<DATA>) {
	chomp($_);
	s/^\s*(.*?)\s*$/$1/;
	($a,$b,$c,$d,$e)=split(/\s+/,$_);
	$fullname = $d if ($d =~ /dll|DLL/);
	$importdll = lc($a) if ($a =~ /dll|DLL/);

#
# I _think_ this will ignore everything that is not needed...
# This includes errors, which are reported, but details are ignored.
#

	next LINE if ($importdll =~ /sys|SYS/);
	next LINE if /Address of/|/Section contains|.data1$|.shared$|.MSIMESH$|.rdata$|INIT$|PAGEKD$/;
	next LINE if ($a =~ /ExceptionCode/ | $a =~ /ExceptionFlags/ | $a =~ /ExceptionAddress/);
	next LINE if ($a =~ /NumberParameters/ | $a =~ /ExceptionInformation/ | $a =~ /CONTEXT/);
	next LINE if ($a =~ /Eax/ | $a =~ /Ebx/ | $a =~ /Ecx/ | $a =~ /Edx/ | $a =~ /Eip/ | $a =~ /SegCs/);
	next LINE if ($a =~ /SegSs/ | $a =~ /SegFs/ | $a =~ /Dr0/ | $a =~ /Dr1/ | $a =~ /Dr2/);
	next LINE if /Microsoft|File Type|Table|Dump of|date stamp|following imports|first forwarder|.dll|bound|.data$|.reloc$|.rsrc$|.text$|Bound|.bss$|.orpc$|.instanc$|.CRT$/;
	next LINE if ($b =~ /Characteristics/);
	next LINE if (length $a == 0 | length $b == 0);
	next LINE if ($a =~ /Summary/);

	($path,$name)=split(/\//,$fullname);

	if (/rdina/ & length $c ==0) {
		print IMPORTS ("$name\t$importdll\t$a $b\n");
	}

	elsif ($c =~ /^[0-9]+$/) {
		print IMPORTS ("$name\t$importdll\t$b $c\n");
	}


	elsif ($a =~ /[\da-fA-F]/ & length $c == 0) {
		print IMPORTS ("$name\t$importdll\t$b\n");
	}

	elsif (length $c == 0) {
		print IMPORTS ("$name\t$importdll\t$a $b\n");
	}

	elsif ($a =~ /DUMPBIN/) {
		print STDOUT2 ("\nError during dump of $name!  Import data may be incomplete!\n\n");
	}

	else {
		print IMPORTS ("$name\t$importdll\t$c\n");
	}
}

close(DATA);
close(IMPORTS);

print STDOUT2 ("\nParsing export list.  This may take several minutes.\n\n");
open(DATA, "exports.txt");
open(EXPORTS, ">exported_api.txt");
LINE: while(<DATA>) {
	chomp($_);
	s/^\s*(.*?)\s*$/$1/;
	($a,$b,$c,$d,$e)=split(/\s+/,$_);
	$fullname = $d if ($d =~ /.dll$/ & $a =~ /Dump/);
	next LINE if (/Microsoft|File Type|Table|Dump of|date stamp|number of|first forwarder|bound import|.reloc$/ & ($a != /DUMPBIN/));
	next LINE if (/.orpc$|.bss$|.data1$|.instanc$|.CRT|.text$|.rsrc$|.data$/ & ($a != /DUMPBIN/));
	next LINE if (length $a == 0 | $a == "Summary");
	next LINE if /ordinal base/;


	($path,$name)=split(/\//,$fullname);

	if ($c =~ /NONAME/) {
		print EXPORTS ("$name\tordinal $a\n");
		}

	elsif ($a =~ /DUMPBIN/) {
		print STDOUT2 ("\nError during dump of $name!  Export data may be incomplete!\n\n");
	}

	elsif ($d =~ /forwarded/) {
		print EXPORTS ("$name\t$c\n");
		}
	elsif (length $d != 0 & $A != /DUMPBIN/) {
		print EXPORTS ("$name\t$d\n");
	}

	else {
		next LINE;
	}

}
}