#! /usr/bin/perl
#
# $Id$
# Scan downloaded Microsoft files to build the .captivemodid.xml file core.
# Copyright (C) 2005 Jan Kratochvil <project-captive@jankratochvil.net>
# 
# 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; exactly version 2 of June 1991 is required
# 
# 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


use strict;
use warnings;

use Cwd;
use Carp qw(confess cluck);
use Digest::MD5 qw(md5_hex);
use File::Remove qw(remove);
use Getopt::Long;
use Data::Dumper;
require Config::IniHash;
require File::Basename;


confess if !$ENV{"HOME"};
my $tmp=$ENV{"HOME"}."/tmp/captivemodid";
confess "tmp '$tmp' too dangerous for tmpwatch(8)" if $tmp=~m{^/tmp\b} || $tmp=~m{^/var/tmp\b};
my $out;


my $opt_quiet;
die if !GetOptions(
		"q|quiet+",\$opt_quiet,
		);


our $dirnew=$ARGV[0];
do { $dirnew="."; warn "Defaulting ARGV[0] to: $dirnew"; } if !$dirnew;
our $out=$ARGV[1];
warn "No output md5 directory, not copying." if !$out;


my %type=(
	"ntoskrnl.exe"=>"Kernel",
	"ntkrnlpa.exe"=>"Kernel PA",
	"ntkrnlmp.exe"=>"Kernel SMP",
	"ntkrpamp.exe"=>"Kernel SMP PA",
	"cdfs.sys"    =>"CD-ROM/iso-9660",
	"fastfat.sys" =>"FastFAT/vfat",
	"ntfs.sys"    =>"NTFS",
	);

my %lang=(
	"ar"=>"Arabic",
	"hk"=>"Chinese (Hong Kong SAR)",
	"cn"=>"Chinese (Simplified)",
	"tw"=>"Chinese (Traditional)",
	"cs"=>"Czech",
	"da"=>"Danish",
	"nl"=>"Dutch",
	"en"=>"English",
	"fi"=>"Finnish",
	"fr"=>"French",
	"de"=>"German",
	"el"=>"Greek",
	"he"=>"Hebrew",
	"hu"=>"Hungarian",
	"it"=>"Italian",
	"ja"=>"Japanese",
	"ko"=>"Korean",
	"no"=>"Norwegian",
	"pl"=>"Polish",
	"br"=>"Portugese (Brazil)",
	"pt"=>"Portugese (Portugal)",
	"ru"=>"Russian",
	"es"=>"Spanish",
	"sv"=>"Swedish",
	"tr"=>"Turkish",
	"ara"=>"Arabic",
	"chh"=>"Chinese (Hong Kong SAR)",
	"chs"=>"Chinese (Simplified)",
	"cht"=>"Chinese (Traditional)",
	"csy"=>"Czech",
	"dan"=>"Danish",
	"nld"=>"Dutch",
	"enu"=>"English",
	"fin"=>"Finnish",
	"fra"=>"French",
	"deu"=>"German",
	"ell"=>"Greek",
	"heb"=>"Hebrew",
	"hun"=>"Hungarian",
	"ita"=>"Italian",
	"jpn"=>"Japanese",
	"kor"=>"Korean",
	"nor"=>"Norwegian",
	"plk"=>"Polish",
	"ptb"=>"Portugese (Brazil)",
	"ptg"=>"Portugese (Portugal)",
	"rus"=>"Russian",
	"esn"=>"Spanish",
	"sve"=>"Swedish",
	"trk"=>"Turkish",
	);


sub name_valid($)
{
my($name)=@_;
	$name=~tr/-/_/;
	$name=lc $name;
	return 0 if $name=~/[^a-z]ia64[^a-z]/;
	return 1;
}

sub name_to_lang($)
{
my($name)=@_;

	$name=~tr/-/_/;
	$name=lc $name;
	my $r;
	while (my($subst,$long)=each(%lang)) {
		next if $name!~/[^a-z]$subst[^a-z]/;
# FIXME
		cluck "$name: $subst" if $r;
		$r=$long;
		}
# FIXME
	cluck $name if !$r;
	return $r;
}

my @checked_build=qw(
	chk
	debug
	);

sub name_is_debug($)
{
my($name)=@_;

	$name=~tr/-/_/;
	$name=lc $name;
	for my $subst (@checked_build) {
		return 1 if $name=~/[^a-z]$subst[^a-z]/;
		}
	return 0;
}

my $ver_prefix="5.1.2600.";
my @stack_out;
my %md5sum_printed_global;

sub stack0_flush()
{
	return if $opt_quiet;
	# $md5sum_printed_local[$stacki]{md5}=1;
	my @md5sum_printed_local;
	while (my $stack=shift @stack_out) {
		my $hiding=1;
		for my $stacki (0..$#$stack) {
			my $this=$stack->[$stacki];
			$hiding=0 if !$md5sum_printed_local[$stacki]{$this->{"md5"}};
			next if $hiding;
			splice @md5sum_printed_local,$stacki,@md5sum_printed_local-$stacki,{$this->{"md5"}=>1};
			my $dupe_global=$md5sum_printed_global{$this->{"md5"}}++;
			my $print="";
			$print.=<<"HERE" if  $dupe_global;
<!-- @{[ $this->{"relname"} ]} md5="@{[ $this->{"md5"} ]}" -->
HERE
			$print.=<<"HERE" if !$dupe_global;
<!-- @{[ $this->{"relname"} ]} -->
<module type="@{[ $this->{"type"} ]}" length="@{[ $this->{"length"} ]}" priority="@{[ $this->{"priority"} ]}" md5="@{[ $this->{"md5"} ]}"
		id="@{[ $this->{"dist"} ]} @{[ !$this->{"is_debug"} ? "" : "Checked Build "]}@{[ $this->{"lang"} ]} $ver_prefix@{[ $this->{"ver"} ]} @{[ $this->{"this_name"} ]}" />
HERE
			$print=~s/^/"\t" x (1+$stacki)/egm;
			print $print;
			}
		}
	print "\n";
}

our @stack;
my %md5sum_stored;
my $last_stack0_filename;

sub check($$)
{
my($filename_unused,$basename_orig)=@_;

	return if !$type{lc $basename_orig};
	my $final_type=$basename_orig;
	my $final_name=$type{$final_type} or confess;
	$final_type="ntoskrnl.exe" if $final_type=~/^nt.*[.]exe$/;
	(my $basename_orig_=$basename_orig)=~s/.$/_/;
	(my $basename0=$stack[0]->{"filename"})=~s{^.*/}{} or confess;
	return if !name_valid($basename0);
	stack0_flush() if $last_stack0_filename && $last_stack0_filename ne $stack[0]->{"filename"};
	$last_stack0_filename=$stack[0]->{"filename"};
	for my $stacki (0..$#stack) {
		my $this=$stack[$stacki];
		my $filename=$this->{"filename"} or confess;
		my $relname=$this->{"relname"} or confess;
		(my $basename=$filename)=~s{^.*/}{} or confess;
		my $expanded=$stack[$#stack]->{"filename"};
		my $this_name;
		my $type;
		if ($stacki==$#stack-1 && $basename eq $basename_orig_) {
			$this->{"type"} eq "cabinet" or confess;
			$type=$this->{"type"} or confess;
			$this_name="$final_name Cabinet";
			}
		elsif ($stacki<$#stack) {
			$type=$this->{"type"} or confess;
			$this_name="Cabinet";
			}
		else {
			$type=$final_type or confess;
			confess Dumper(\@stack) if $type eq "cabinet";
			$this_name=$final_name;
			}
		my $length=(stat $filename)[7] or confess;
		my $dist=$stack[0]->{"filename"};
		$dist=~s{^.*/}{} or confess;
		my $lang=name_to_lang $basename0;
		local *F;
		open F,$expanded or confess;
		my $buf="";
		my $buf2;
		my $ver;
		while (my $got=read F,$buf2,0x1000) {
			confess $expanded if !defined $got;
			last if !$got;
			confess $expanded if $got<0;
			confess $expanded if length($buf2)!=$got;
			$buf.=$buf2;
			$ver=$buf;
			$ver=~s/\x00\x00+/!/g;
			$ver=~tr/\x00//d;
			$ver=~tr/!/\x00/;
			last if $ver=~s/^.*\x00\Q$ver_prefix\E(\d+)[ \x00].*$/$1/s;
			$ver=undef();
			$buf=substr($buf,-0x100);
			}
		confess $expanded if !$ver;
		close F or confess;
		$ver=~/^\d+$/ or confess "$expanded: $ver";
		my $pri=sprintf "510%04u00",$ver;
		$pri+=90;
		$pri-=10 if $lang ne "English";
		$pri-=20 if $basename_orig=~/^ntkrnlpa/;
		$pri-=40 if $basename_orig=~/^ntkrnlmp/;
		$pri-=60 if $basename_orig=~/^ntkrpamp/;
		$pri+=5000000 if my $is_debug=name_is_debug $basename0;
		local *F;
		open F,$filename or confess;
		my $md5obj=Digest::MD5->new();
		$md5obj->addfile(*F);
		close F or confess;
		my $md5sum=lc $md5obj->hexdigest();
		if (!$md5sum_stored{$md5sum}++ && $out && $stacki==$#stack) {
			my $dir="$out/$final_type";

			mkdir_checked($dir) if !-d $dir;
			spawn("cp -p '$filename' '$dir/$md5sum'");
			}

		my %new=(
			%$this,
			"type"=>$type,
			"length"=>$length,
			"priority"=>$pri,
			"md5"=>$md5sum,
			"dist"=>$dist,
			"lang"=>$lang,
			"ver"=>$ver,
			"this_name"=>$this_name,
			"is_debug"=>$is_debug,
			);
		# Highest priority/version of all the files in the cabinet:
		for (qw(ver priority)) {
			next if !$this->{$_};
			$new{$_}=$this->{$_} if $new{$_}<$this->{$_};
			delete $this->{$_} if $this->{$_}<$new{$_};
			}
		do { cluck Dumper($this,\%new) if $new{$_} ne $this->{$_}; } for keys(%$this);
		%$this=%new;
		}
	# It is intentional to copy only the references - to update their "ver".
	push @stack_out,[@stack];
}

sub spawn($)
{
my($command)=@_;

	my $code=system "($command)".' >&2';
	confess "$code: $command" if $code;
}

sub rm_rf($)
{
my($dir)=@_;

	return if !-e $dir;
	# Do not: Can't remove directory xyzzy: Directory not empty
	#         -r--r--r-- xyzzy
	#         remove \1,$dir or confess "$dir: $!";
	spawn "rm -rf '$dir'";
}

sub mkdir_checked
{
my(@dirs)=@_;

	for (@dirs) {
		mkdir $_ or confess "$_: $!";
		}
}


our $depth=-1;

sub prep()
{
	confess if $dirnew;
	$dirnew="$tmp/$depth";
	chdir "/" or confess;
	rm_rf $dirnew;
	mkdir_checked $dirnew;
	chdir $dirnew or confess "chdir: $dirnew";
	return $dirnew;
}

sub relname($)
{
my($filename)=@_;

	return $filename if $filename=~s{^\Q$tmp\E/\d+/}{};
	(my $basename=$filename)=~s{^.*/}{} or confess;
	return $basename;
}

my %unknown;
sub process(;$$);
sub process(;$$)
{
my($ref,$reftype)=@_;

	my $dirname=$dirnew or confess;
	$dirnew=undef();
	local $depth=$depth+1;
	my @stack_orig=@stack;
	local @stack;

	# Handle so-called 'mspatch'.
	# Look for Product/Technology: Platform SDK
	# Item like: Microsoft Windows SDK
	# 	http://www.microsoft.com/downloads/details.aspx?FamilyID=2297bdc9-b5ae-4b8a-b601-eef54a52867a&DisplayLang=en
	# 	http://download.microsoft.com/download/0/7/3/073d42b8-e2ba-4293-ad97-28365dc2d655/6.0.5270.0.9.WindowsSDK_Vista_idw.DVD.Rel_Update.img
	# 	Setup/WinSDK-SDK_MSI_SMP-common.0.cab 
	# 	APATCH 5.1.2600.0 Patch Application Utility
	# Do not use: Windows Installer SDK (x86): APATCH 1.94 Patch Application Utility
	# as it will break on some files (incompatible with some new features).
	# You need proper Wine setup incl. its binary filetype registration.
	if (-e "$dirname/_sfx_manifest_") {
		Config::IniHash::ReadINI("_sfx_manifest_",
				# The only way how to read it in the original order:
				"forValue"=>sub {
					my($dest_raw,$src_raw,$sectionname,$inihashref)=@_;
					return if $sectionname ne "Deltas";
					$dest_raw=~tr{\\}{/};
					$src_raw=~tr{\\}{/};
					my $dest=($dest_raw=~/^\s*"([^"]+)"\s*$/)[0] or confess;
					my($patch,$src)=($src_raw=~/^\s*"([^"]+)"\s*,\s*"([^"]+)"\s*$/) or confess;
					my $destdir=File::Basename::dirname($dest);
					cluck "$dirname/$patch" if !-r "$dirname/$patch";
					cluck "$dirname/$src" if !-r "$dirname/$src";
					# Not &mkdir: Take care of already existing directories and ... "-p".
					spawn "mkdir -p '$dirname/$destdir'";
					rm_rf "$dirname/$dest";
					spawn "true;(apatch '$dirname/$patch' '$dirname/$src' '$dirname/$dest' &>/dev/null)";
					return;
					},
				);
		}

	local *FIND;
	open FIND,"find '$dirname' -type f|sort|" or confess $dirname;
	local $_;
	while (<FIND>) {
		next if $_ eq "";
		print STDERR "." if !$depth;
		chomp;
		my $filename=$_;
		$filename=~m{^/} or confess $filename;
		(my $basename=$filename)=~s{^.*/}{} or confess;
		-r $filename or do { cluck Dumper(\@stack,$filename); system("ls -l '$filename'"); };
		$$ref=$reftype if $ref;
		my $this={
			"filename"=>$filename,
			"relname"=>relname($filename),
			};
		@stack=(@stack_orig,$this);

		if ($basename=~/[.]_p$/) {
			do { cluck "Missing $_" if !-e $_; } for "_sfx_manifest_";
			next;
			}

		check $filename,$basename;

		local *MIME;
		open MIME,"file -b -i '$filename'|" or confess;
		my $mime=do { local $/; <MIME> or confess; };
		close MIME or confess;
		chomp $mime;
		next if $mime=~m{^text/};
		next if $mime=~m{^image/};
		next if $mime=~m{^audio/};
		next if $mime eq 'application/x-empty';
		next if $mime eq 'application/msaccess';

		local *ID;
		open ID,"file -b '$filename'|" or confess;
		my $id=do { local $/; <ID> or confess; };
		close ID or confess;
		chomp $id;
		next if $id eq 'Microsoft Office Document';
		next if $id=~/^PDF document, version 1[.]\d+$/;
		next if $id eq 'Rich Text Format data, version 1,';
		next if $id=~/^x86 boot sector, /;
		next if $id eq 'data';
		next if $id eq 'MZ executable for MS-DOS';
		next if $id eq 'XML document text';
		next if $id eq 'MS Windows HtmlHelp Data';
		next if $id eq 'MPEG sequence';
		next if $id=~/^MSVC program database ver /;
		next if $id eq 'Lotus 1-2-3';
		next if $id eq 'MS Windows Help Data';
		next if $id=~/^Macromedia Flash data, /;
		next if $id eq 'Microsoft ASF';	# .wmv
		next if $id eq 'GLF_BINARY_LSB_FIRST';	# .hlp
		next if $id=~/^Sendmail frozen configuration /;
		next if $id eq 'Assembler source';
		next if $id=~/^DBase 3 data file/;
		next if $id=~/^Macintosh HFS Extended version /;
		next if $id=~/^MPEG ADTS, /;	# .txt?,.hlp?
		next if $id eq 'MS-DOS executable (COM)';
		next if $id eq 'NE executable for MS Windows 3.x (driver)';
		next if $id eq 'TrueType font data';
		next if $id eq 'Windows NT registry file';
		next if $id eq 'lif file';	# .sig?
		next if $id=~/^Infocom game data /;	# .chm?
		next if $id=~/^Sun disk label /;	# .dl_?
		next if $id=~/^compiled Java class data, /;
		next if $id=~/^Minix filesystem/;	# .dl_?
		next if $id eq 'DBase 3 index file';	# .nls?
		next if $id=~/^Macintosh MFS data /;	# .cp_?
		next if $id eq 'ACB archive data';	# .cat?
		next if $id eq 'COM executable for MS-DOS';	# .edb?
		next if $id eq 'SysEx File - Gulbransen';	# .edb?
		next if $id eq 'LE executable for MS Windows (VxD)';	# .386?

		# Do not:
		#next if $id=~/^PE executable for MS Windows .* 32-bit$/;	# TODO

		if ($id eq 'PE executable for MS Windows (GUI) Intel 80386 32-bit, InnoSetup self-extracting archive') {
			# FIXME: http://innounp.sourceforge.net/
			next;
			}
		if (0
				|| $id=~/^Microsoft Cabinet archive data, \d+ byte(?:s)?, \d+ file(?:s)?$/
				|| $id eq 'PE executable for MS Windows (GUI) Intel 80386 32-bit, MS CAB-Installer self-extracting archive'
				# Catch all unidentified flying cabinets:
				|| $id=~/^PE executable for MS Windows .* 32-bit$/
				) {
			# Acceleration: Spawning cabextract(1) is slow, try to find "MSCF" first.
			local *CAB;
			open CAB,$filename or confess $filename;
			my $buf="";
			my $buf2;
			my $mscf;	# magic
			while (my $got=read CAB,$buf2,0x1000) {
				confess $filename if !defined $got;
				last if !$got;
				confess $filename if $got<0;
				confess $filename if length($buf2)!=$got;
				$buf.=$buf2;
				last if $mscf=($buf=~/MSCF/s);
				$buf=substr($buf,-4);
				}
			close CAB or confess;
			next if !$mscf;
			prep();
			spawn q{cabextract -q '}.$filename.q{' 2>&1|grep -v ': \(}.join(q{\|},
					q{WARNING; possible [0-9]* extra bytes at end of file.},
					q{no valid cabinets found},
					q{checksum error},
					q{error in CAB data format},
					).q{\)$';true};
			process(\$this->{"type"},"cabinet");
			next;
			}
		if (0
				|| $id eq 'PE executable for MS Windows (GUI) Intel 80386 32-bit, ZIP self-extracting archive (WinZip)'
				|| $id=~/^Zip archive data, /
				) {
			prep();
			spawn "unzip -q '$filename'";
			process();
			next;
			}
		# non-"32-bit" - amd64 in fact but not detected by file(1).
		# Really.
		next if $id=~/^PE executable for MS Windows/;
		if ($id=~m{^gzip compressed data, was "\S+", from Win/32}) {
			prep();
			spawn "gzip -d <'$filename' >'$basename'";
			process();
			next;
			}
		if ($id eq 'tar archive') {
			prep();
			spawn "tar xf '$filename'";
			process();
			next;
			}
		warn "$filename: $mime - $id" if !$unknown{$mime,$id}++;
		}
	close FIND or confess;
	$dirnew=undef();
}


$dirnew=getcwd()."/".$dirnew if $dirnew!~m{^/};
$out   =getcwd()."/".$out    if $out && $out!~m{^/};
rm_rf $tmp;
mkdir_checked $tmp;
if ($out) {
	rm_rf $out;
	mkdir_checked $out;
	}
$|=1;
print <<"HERE";
<?xml version="1.0" encoding="UTF-8"?>
<!-- @{[ '$' ]}Id@{[ '$' ]}
HERE
print <<'HERE';
 - Database of ids for Captive-compatible W32 binary modules
 - Copyright (C) 2003-2005 Jan Kratochvil <project-captive@jankratochvil.net>
 - 
 - 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; exactly version 2 of June 1991 is required
 - 
 - 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 -->
<!--
 - XP No Service Pack                           5.1.2600.0
 - Q317277_WXP_SP1_x86 xpclnt_qfe.010827-1803   5.1.2600.31
 - Q811493_WXP_SP2 2600.xpclnt_qfe.021108-2107  5.1.2600.108
 - XP Service Pack 1/1a xpsp1.020828-1920       5.1.2600.1106
 - Q811493_WXP_SP2 2600.xpsp2.030422-1633       5.1.2600.1151
 -
 - 510110690:
 -        90->{ntoskrnl +80}+{English +10}
 -    1106->5.1.2600.{1106}
 -   0->{0=Free,5=Checked}
 - 51->{5}.{1}
 - ntoskrnl
 -          ntoskrnl +80 (Uniprocessor 4GB or less)
 -          ntkrnlpa +60 (Uniprocessor >4GB extended addressing support)
 -          ntkrnlmp +40 (Multiprocessor 4GB or less)
 -          ntkrpamp +20 (Multiprocessor >4GB extended addressing support)
 - English           +10
 -->
<modid>


	<!-- Non-downloadable (or just no longer downloadable) fixed section: { -->


	<!-- MS-Windows XP (No Service Pack) Checked Build English 5.1.2600.0 -->
		<!-- ntoskrnl.ex_ -->
		<module type="cabinet" length="1229871" priority="515000090" md5="ffd9de8245c9a2239c5bdccd25c301bc"
				id="(No Service Pack) Checked Build English 5.1.2600.0 Kernel Cabinet" />
		<!-- ntkrnlmp.ex_ -->
		<module type="cabinet" length="1230619" priority="515000050" md5="1e12693d52c12e4f9be95e278d8abcd3"
				id="(No Service Pack) Checked Build English 5.1.2600.0 Kernel SMP Cabinet" />
		<!-- fastfat.sy_ -->
		<module type="cabinet" length="92500" priority="515000090" md5="a1afe5e343bc0d922c21328c3d250f41"
				id="(No Service Pack) Checked Build English 5.1.2600.0 FastFAT/vfat Cabinet" />
		<!-- cdfs.sy_ -->
		<module type="cabinet" length="43821" priority="515000090" md5="44f9d65d8045a18c14dfde732324f361"
				id="(No Service Pack) Checked Build English 5.1.2600.0 CD-ROM/iso-9660 Cabinet" />
		<!-- ntoskrnl.exe -->
		<module type="ntoskrnl.exe" length="3396096" priority="515000090" md5="aafc4863e2aba5287d84a3da05105a92"
				id="(No Service Pack) Checked Build English 5.1.2600.0 Kernel" />
		<!-- ntkrnlmp.exe -->
		<module type="ntoskrnl.exe" length="3396608" priority="515000050" md5="d35fc3d2e33306b1ad660ab7b723a924"
				id="(No Service Pack) Checked Build English 5.1.2600.0 Kernel SMP" />
		<!-- ntfs.sys -->
		<module type="ntfs.sys" length="809344" priority="515000090" md5="962efb6be87a642eb5d0d4b381d3786b"
				id="(No Service Pack) Checked Build English 5.1.2600.0 NTFS" />
		<!-- fastfat.sys -->
		<module type="fastfat.sys" length="227328" priority="515000090" md5="595a1602383e6c18e233aaa97fc3ba2e"
				id="(No Service Pack) Checked Build English 5.1.2600.0 FastFAT/vfat" />
		<!-- cdfs.sys -->
		<module type="cdfs.sys" length="114688" priority="515000090" md5="5a75583c08d709d5ed926e7dfed5c09c"
				id="(No Service Pack) Checked Build English 5.1.2600.0 CD-ROM/iso-9660" />

	<!-- MS-Windows XP (No Service Pack) 5.1.2600.0 -->
		<!-- fastfat.sy_ -->
		<module type="cabinet" length="76742" priority="510000090" md5="2c0b027760858b4b6ff823deb61b1785"
				id="(No Service Pack) 5.1.2600.0 FastFAT/vfat Cabinet" />
		<!-- cdfs.sy_ -->
		<module type="cabinet" length="34873" priority="510000090" md5="b6d7f84df4431f79173574b873a9edf0"
				id="(No Service Pack) 5.1.2600.0 CD-ROM/iso-9660 Cabinet" />
		<!-- ntfs.sys -->
		<module type="ntfs.sys" length="533504" priority="510000090" md5="70fae0dcfdfaa0838d6778fca028ce01"
				id="(No Service Pack) 5.1.2600.0 NTFS" />
		<!-- fastfat.sys -->
		<module type="fastfat.sys" length="144768" priority="510000090" md5="998bbf32a142910b5e539df4225df892"
				id="(No Service Pack) 5.1.2600.0 FastFAT/vfat" />
		<!-- cdfs.sys -->
		<module type="cdfs.sys" length="62208" priority="510000090" md5="bab95bbefd0676eab2dc02cf88c99fc5"
				id="(No Service Pack) 5.1.2600.0 CD-ROM/iso-9660" />

	<!-- MS-Windows XP (No Service Pack) English 5.1.2600.0 -->
		<!-- ntoskrnl.ex_ -->
		<module type="cabinet" length="969331" priority="510000090" md5="d76e823d8b9004ce658accae0235cbd7"
				id="(No Service Pack) English 5.1.2600.0 Kernel Cabinet" />
		<!-- ntkrnlmp.ex_ -->
		<module type="cabinet" length="938895" priority="510000050" md5="a12abfbc47263fd060f81dad2d53d655"
				id="(No Service Pack) English 5.1.2600.0 Kernel SMP Cabinet" />
		<!-- ntoskrnl.exe -->
		<module type="ntoskrnl.exe" length="1982208" priority="510000090" md5="a29222d5281056e497408fcc9062f749"
				id="(No Service Pack) English 5.1.2600.0 Kernel" />
		<!-- ntkrnlmp.exe -->
		<module type="ntoskrnl.exe" length="1897984" priority="510000050" md5="5e9003146793d4a8d2b46c7414965daf"
				id="(No Service Pack) English 5.1.2600.0 Kernel SMP" />

	<!-- MS-Windows XP (No Service Pack) German 5.1.2600.0 -->
		<!-- ntoskrnl.exe -->
		<module type="ntoskrnl.exe" length="1984512" priority="510000080" md5="3ba950b403060180606235bbb955a315"
				id="(No Service Pack) German 5.1.2600.0 Kernel" />


	<module type="ext2fsd.sys" length="171325" priority="100" md5="39af1cc9b68c662d29b05547342546c7"
			id="ext2 Filesystem v0.10a by http://sys.xiloo.com Checked Build English" />
	<module type="ext2fsd.sys" length="59514" priority="50" md5="8803c8f4c535d6a58606bcd0ff3f77e9"
			id="ext2 Filesystem v0.10a by http://sys.xiloo.com English" />


	<!-- xpsp1_en_x86_chk.exe -->
	<module type="cabinet" length="160852568" priority="515110600" md5="ed5c40cab78513084f9a11cdd3a25361"
			id="Service Pack 1 Checked Build English Cabinet" />
	<!-- xpsp1_en_x86.exe -->
	<module type="cabinet" length="140440152" priority="510110600" md5="ecf625f6563dfcaeeecc4b1481899d4b"
			id="Service Pack 1 English Cabinet" />


	<!-- Non-downloadable (or just no longer downloadable) fixed section: } -->


HERE
process;
stack0_flush() if $last_stack0_filename;
rm_rf $tmp;
print STDERR "\n";
print <<'HERE';

</modid>
HERE
