#!/usr/bin/perl
#
#    regdiff - generate diffs of two windows 9x registries (regutils package)
#    Copyright (C) 1998 Memorial University of Newfoundland
#    
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#    
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#    
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#    


#
# Script to compare two windows 9x registries.
#
# (pod at end)
#

use strict;
use Getopt::Std;
use IO::Handle;
use IO::File;

push(@INC, '/usr/lib');
require 'regfilterLib.pl';

my $prog = $0;
$prog =~ s:.*/::;

my $regedit = '/usr/bin/regedit';
my $regsort = '/usr/bin/regsort';


my $Usage = "Usage: $prog [-r [-t name] [-N]] [-p] [-s] [-V] from-dump to-dump
    -r		Input files are raw binary registries, not exported registries
    -N		Pass -N option to regedit (no new types)
    -t name	Use name as the top level key name (eg, HKEY_USERS, etc)
    -s		Sort the dumps (useful when diffing DOS dumps)
    -p		Plain output - no new/old-value markers
    -V		Print version number and exit.
";

my %opt;
if (!&getopts('Nprst:V', \%opt)) {
    print STDERR $Usage;
    exit 1;
}
if (defined $opt{'V'}) {
    print "$prog: version 0.10\n";
    exit 0;
}
my $raw = defined $opt{'r'} ? $opt{'r'} : 0;
my $topkeyarg = defined $opt{'t'} ? " -t $opt{'t'}" : "";
my $sort = defined $opt{'s'} ? $opt{'s'} : 0;
my $plain = defined $opt{'p'} ? $opt{'p'} : 0;
my $Narg = defined $opt{'N'} ? ' -N' : '';

die "$prog: wrong number of arguments\n$Usage" unless @ARGV == 2;


my(%key, %keyIndex);
my %name;
my($from, $to);

if ($raw || $sort) {
    my $argsUsed = 0;
    my $cmdFrom = '';
    my $cmdTo = '';
    # Hmm - kind of silly as regedit output is already sorted...
    if ($raw) {
	if ($argsUsed) {
	    $cmdFrom .= " $regedit -f - $topkeyarg $Narg -e - |";
	    $cmdTo   .= " $regedit -f - $topkeyarg $Narg -e - |";
	} else {
	    $argsUsed = 1;
	    $cmdFrom .= " $regedit -f $ARGV[0] $topkeyarg $Narg -e - |";
	    $cmdTo   .= " $regedit -f $ARGV[1] $topkeyarg $Narg -e - |";
	}
    }
    if ($sort) {
	if ($argsUsed) {
	    $cmdFrom .= " $regsort |";
	    $cmdTo   .= " $regsort |";
	} else {
	    $argsUsed = 1;
	    $cmdFrom .= " $regsort $ARGV[0] |";
	    $cmdTo   .= " $regsort $ARGV[1] |";
	}
    }
    $from = new IO::File($cmdFrom);
    if (!defined $from) {
	die "$prog: command \"$cmdFrom\" failed\n";
    }
    $to = new IO::File($cmdTo);
    if (!defined $to) {
	die "$prog: command \"$cmdTo\" failed\n";
    }
} else {
    $from = new IO::File($ARGV[0], 'r');
    if (!defined $from) {
	die "$prog: couldn't open $ARGV[0] - $!\n";
    }

    $to = new IO::File($ARGV[1], 'r');
    if (!defined $to) {
	die "$prog: couldn't open $ARGV[1] - $!\n";
    }
}

$name{$from} = $ARGV[0];
$name{$to} = $ARGV[1];

sub nextkey {
    my ($file) = @_;

    $key{$file} = &regfilter::readKey($file);
    if (!defined $key{$file}) {
	die "$prog: problem reading key from $name{$file} - $regfilter::errorString\n";
    }
    if (!%{$key{$file}}) {
	#print "eof on $file ($key{$file})\n";
	return undef;
    }
    $keyIndex{$file} = 0;
    # print "$file: key $key{$file}->{'key'}\n";
    return $key{$file}->{'key'};
}

sub nextval {
    my ($file) = @_;
    my ($k) = $key{$file};

    my ($i) = $keyIndex{$file};
    if ($i >= @{$k->{'entries'}}) {
	return undef;
    }
    $keyIndex{$file} = $i + 1;
    # print "$file: value ${$k->{'entries'}}[$i]->{'name'}=${$k->{'entries'}}[$i]->{'value'}\n";
    return (${$k->{'entries'}}[$i]->{'name'}, ${$k->{'entries'}}[$i]->{'value'});
}

my $newMarker = $plain ? '' : "\t(new)";
print "REGEDIT4\n\n" if $plain;

my $dellen = 99999;
my $delkey;
my $fromkey = nextkey $from;
my $tokey = nextkey $to;
my ($newkey, $newlen);
while (defined $fromkey || defined $tokey) {
    my $len = length $tokey;
    if (defined $newkey && ($len <= $newlen || substr($tokey, 0, $newlen) ne $newkey)) {
	print "[$newkey]$newMarker\n\n";
	undef $newkey;
    }
    
    if (defined $fromkey && defined $tokey
	&& &regfilter::cmpKey($fromkey, $tokey) == 0)
    {
	my ($fromname, $fromdata) = nextval $from;
	my ($toname, $todata) = nextval $to;
	my $printkey = 1;
	while (defined $fromname || defined $toname) {
	    if (defined $fromname && defined $toname
		&& &regfilter::cmpEntryName($fromname, $toname) == 0)
	    {
		if ($fromdata ne $todata) {
		    if ($printkey) {
			print "[$fromkey]\n";
			$printkey = 0;
		    }
		    print "$toname=$todata";
		    print "\t($fromdata)" if !$plain;
		    print "\n";
		}
		($fromname, $fromdata) = nextval $from;
		($toname, $todata) = nextval $to;
	    } else {
		if ($printkey) {
		    print "[$fromkey]\n";
		    $printkey = 0;
		}
		if (!defined $toname || defined $fromname
			&& &regfilter::cmpEntryName($fromname, $toname) < 0)
		{
		    print "$fromname\n";
		    ($fromname, $fromdata) = nextval $from;
		} else {
		    print "$toname=$todata$newMarker\n";
		    ($toname, $todata) = nextval $to;
		}
	    }
	}
	print "\n" if !$printkey;
	$fromkey = nextkey $from;
	$tokey = nextkey $to;
    } elsif (!defined $tokey || defined $fromkey
		&& &regfilter::cmpKey($fromkey, $tokey) < 0)
    {
	$len = length $fromkey;
	if ($len <= $dellen || substr($fromkey, 0, $dellen) ne $delkey) {
	    print "[$fromkey]-\n\n";
	    $dellen = $len;
	    $delkey = $fromkey;
	}
	$fromkey = nextkey $from;
    } else {
	$newkey = $tokey;
	$newlen = $len;
	my ($toname, $todata) = nextval $to;
	my $printkey = 1;
	while (defined $toname) {
	    if ($printkey) {
		print "[$tokey]$newMarker\n";
		undef $newkey;
		$printkey = 0;
	    }
	    print "$toname=$todata\n";
	    ($toname, $todata) = nextval $to;
	}
	print "\n" if !$printkey;
	$tokey = nextkey $to;
    }
}
print "[$newkey]$newMarker\n\n" if defined $newkey;

exit(0);

__END__

=head1 NAME

regdiff - generate the differences between two windows 9x registries

=head1 SYNOPSYS

B<regdiff> [B<-r> [B<-t> name] [B<-N>]] [B<-p>] [B<-s>] file1 file2

=head1 DESCRIPTION

B<regdiff> reads two sorted windows 9x registry dumps and generates a list
of the differences between the two.
The output is suitable for B<regedit> (or, with the B<-p> option,
it is suitable for the windows 9x B<regedit>).

See the B<regedit> man page for a description of the format of a
registry dump, as well as the format of a registry patch.


=head1 OPTIONS

=over 4

=item B<-r>

Tells B<regdiff> that the registry files are raw (binary) registry files,
not dumps of registries.  B<regdiff> simply runs B<regedit> on the files
to get a dump of the files.

=item B<-t> I<name>

Option is passed on to B<regedit> (used with the B<-r> option).

=item B<-N>

Option is passed on to B<regedit> (used with the B<-r> option).

=item B<-p>

Generate plain output: old values of entries, I<etc.>, are not indicated
in parenthesis.

=item B<-s>

Sorts the registry dump files before doing the diff.  B<regdiff> expects
its input to be sorted (both the keys and the entries within the keys) -
output is incorrect if this is not the case.  The B<-s> option is useful
when examining `exports' from the windows 9x regedit program (which are
not sorted).

=item B<-V>

Prints the version number - the program then exits
immediately.

=back


=head1 SEE ALSO

L<regedit>, L<regsort>.

=head1 AUTHOR

John Rochester, Memorial University of Newfoundland (jr@cs.mun.ca).
