summaryrefslogblamecommitdiff
path: root/tools/HPMHookGen/HPMHookGen.pl
blob: 72025f5567553d1c34fdc7c24e3f8a9c40dc6dfe (plain) (tree)
1
2
3
4
5
6
7
8
9








                                                          





                                                                         




























































































































                                                                                                                              
                                                                                                                     







                                                                                         
                                                                 




































































                                                                                                                           

                                                                                       





















































                                                                                               

                                                 





































































































                                                                                                                                                        
                                                    
























                                                                          
                                                     












































































































































































                                                                                                                                   
#!/usr/bin/perl

# Copyright (c) Hercules Dev Team, licensed under GNU GPL.
# See the LICENSE file

use strict;
use warnings;
use XML::Simple;

# XML Parser hint (some are faster than others)
#local $ENV{XML_SIMPLE_PREFERRED_PARSER} = '';                 # 0m7.138s
local $ENV{XML_SIMPLE_PREFERRED_PARSER} = 'XML::Parser';      # 0m2.674s
#local $ENV{XML_SIMPLE_PREFERRED_PARSER} = 'XML::SAX::Expat';  # 0m7.026s
#local $ENV{XML_SIMPLE_PREFERRED_PARSER} = 'XML::LibXML::SAX'; # 0m4.152s

sub trim($) {
	my $s = $_[0];
	$s =~ s/^\s+//; $s =~ s/\s+$//;
	return $s;
}

sub parse($$) {
	my ($p, $d) = @_;
	$p =~ s/^.*?\)\((.*)\).*$/$1/; # Clean up extra parentheses )(around the arglist)

	# Retrieve return type
	unless ($d =~ /^(.+)\(\*\s*[a-zA-Z0-9_]+_interface::([^\)]+)\s*\)\(.*\)$/) {
		print "Error: unable to parse '$d'\n";
		return {};
	}
	my $rt = trim($1);   #return type
	my $name = trim($2); #function name

	my @args;
	my ($anonvars, $variadic) = (0, 0);
	my ($lastvar, $notes) = ('', '');

	$p = ' ' unless $p; # ensure there's at least one character (we don't want a do{} block)
	while ($p) { # Scan the string for variables
		my $current = '';
		my ($paren, $needspace) = (0, 0);

		while ($p) { # Parse tokens
			$p =~ s|^(?:\s*(?:/\*.*?\*/)?)*||; # strip heading whitespace and c-style comments
			last unless $p;

			if ($p =~ s/^([a-zA-Z0-9_]+)//) { # Word (variable, type)
				$current .= ' ' if $needspace;
				$current .= $1;
				$needspace = 1;
				next;
			}
			if ($p =~ s/^(,)//) { # Comma
				last unless $paren; # Argument terminator unless inside parentheses
				$current .= $1;
				$needspace = 1;
				next;
			}
			if ($p =~ s/^(\*)//) { # Pointer
				$current .= ' ' if $needspace;
				$current .= $1;
				$needspace = 0;
				next;
			}
			if ($p =~ s/^([\[\].])//) { # Array subscript
				$current .= $1;
				$needspace = 0;
				next;
			}
			if ($p =~ s/^(\()//) { # Open parenthesis
				$current .= ' ' if $needspace;
				$current .= $1;
				$needspace = 0;
				$paren++;
				next;
			}
			if ($p =~ s/^(\))//) { # Closed parenthesis
				$current .= $1;
				$needspace = 1;
				if (!$paren) {
					$notes .= "\n/* Error: unexpected ')' */";
					print "Error: unexpected ')' at '$p'\n";
				} else {
					$paren--;
				}
				next;
			}
			$p =~ s/^(.)//; # Any other symbol
			$notes .= "\n/* Error: Unexpected character '$1' */";
			print "Error: Unexpected character '$1' at '$p'\n";
			$current .= $1;
			$needspace = 0;
		}

		$current =~ s/^\s+//; $current =~ s/\s+$//g; # trim

		next if (!$current or $current =~ /^void$/); # Skip if empty

		my ($array, $type1, $var, $type2, $indir) = ('', '', '', '', '');
		if ($current =~ qr/^
			([\w\s\[\]*]+\()          # Capture words, spaces, array subscripts, up to the first '(' (return type)
			\s*                       # Skip spaces
			(\*)                      # Capture the '*' from the function name
			\s*                       # Skip spaces
			([\w]*)                   # Capture words (function name)
			\s*                       # Skip spaces
			(\)\s*\([\w\s\[\]*,]*\))  # Capture first ')' followed by a '( )' block containing arguments
			\s*                       # Skip spaces
			$/x
		) { # Match a function pointer
			$type1 = trim($1);
			$indir = $2;
			$var = trim($3 // '');
			$type2 = trim($4);
		} elsif ($current eq '...') { # Match a '...' variadic argument
			$type1 = '...';
			$indir = '';
			$var = '';
			$type2 = '';
		} else { # Match a "regular" variable
			$type1 = '';
			while(1) {
				if ($current =~ /^(const)\s+(.*)$/) { # const modifier
					$type1 .= "$1 ";
					$current = $2 // '';
					next;
				}
				if ($current =~ /^((?:un)?signed)\s+((?:char|int|long|short)[*\s]+.*)$/
				 or $current =~ /^(long|short)\s+((?:int|long)[*\s]+.*)$/
				) { # signed/unsigned/long/short modifiers
					$current = $2;
					$type1 .= "$1 ";
					next;
				}
				if ($current =~ /^(struct|enum)\s+(.*)$/) { # enum and struct names
					$current = $2 // '';
					$type1 .= "$1 ";
				}
				last; # No other modifiers
			}
			if ($current =~ /^\s*(\w+)((?:const|[*\s])*)(\w*)\s*((?:\[\])?)$/) { # Variable type and name
				$type1 .= trim($1);
				$indir = trim($2 // '');
				$var = trim($3 // '');
				$array = trim($4 // '');
				$type2 = '';
			} else { # Unsupported
				$notes .= "\n/* Error: Unhandled var type '$current' */";
				print "Error: Unhandled var type '$current'\n";
				push(@args, { var => $current });
				next;
			}
		}
		unless ($var) {
			$anonvars++;
			$var = "p$anonvars";
		}
		my ($callvar, $pre_code, $post_code, $dereference, $addressof) = ($var, '', '', '', '');
		my $indirectionlvl = () = $indir =~ /\*/;
		if ($type1 eq 'va_list') { # Special handling for argument-list variables
			$callvar = "${var}___copy";
			$pre_code = "va_list ${callvar}; va_copy(${callvar}, ${var});";
			$post_code = "va_end(${callvar});";
		} elsif ($type1 eq '...') { # Special handling for variadic arguments
			unless ($lastvar) {
				$notes .= "\n/* Error: Variadic function with no fixed arguments */";
				print "Error: Variadic function with no fixed arguments\n";
				next;
			}
			$pre_code = "va_list ${callvar}; va_start(${callvar}, ${lastvar});";
			$post_code = "va_end(${callvar});";
			$var = '';
			$variadic = 1;
		} elsif (!$indirectionlvl) { # Increase indirection level when necessary
			$dereference = '*';
			$addressof = '&';
		}
		$indirectionlvl++ if ($array); # Arrays are pointer, no matter how cute you write them

		push(@args, {
			var     => $var,
			callvar => $callvar,
			type    => $type1.$array.$type2,
			orig    => $type1 eq '...' ? '...' : trim("$type1 $indir$var$array $type2"),
			indir   => $indirectionlvl,
			hookf   => $type1 eq '...' ? "va_list ${var}" : trim("$type1 $dereference$indir$var$array $type2"),
			hookc   => trim("$addressof$callvar"),
			origc   => trim($callvar),
			pre     => $pre_code,
			post    => $post_code,
		});
		$lastvar = $var;
	}

	my $rtmemset = 0;
	my $rtinit = '';
	foreach ($rt) { # Decide initialization for the return value
		my $x = $_;
		if ($x =~ /^const\s+(.+)$/) { # Strip const modifier
			$x = $1;
		}
		if ($x =~ /\*/g) { # Pointer
			$rtinit = ' = NULL';
		} elsif ($x eq 'void') { # void
			$rtinit = '';
		} elsif ($x eq 'bool') { # bool
			$rtinit = ' = false';
		} elsif ($x =~ /^(?:enum\s+)?damage_lv$/) { # Known enum damage_lv
			$rtinit = ' = ATK_NONE';
		} elsif ($x =~ /^(?:enum\s+)?sc_type$/) { # Known enum sc_type
			$rtinit = ' = SC_NONE';
		} elsif ($x =~/^(?:enum\s+)?c_op$/) { # Known enum c_op
			$rtinit = ' = C_NOP';
		} elsif ($x =~ /^enum\s+BATTLEGROUNDS_QUEUE_ACK$/) { # Known enum BATTLEGROUNDS_QUEUE_ACK
			$rtinit = ' = BGQA_SUCCESS';
		} elsif ($x =~ /^enum\s+bl_type$/) { # Known enum bl_type
			$rtinit = ' = BL_NUL';
		} elsif ($x =~ /^enum\s+homun_type$/) { # Known enum homun_type
			$rtinit = ' = HT_INVALID';
		} elsif ($x =~ /^enum\s+bg_queue_types$/) { # Known enum bg_queue_types
			$rtinit = ' = BGQT_INVALID';
		} elsif ($x =~ /^struct\s+.*$/ or $x eq 'DBData') { # Structs
			$rtinit = '';
			$rtmemset = 1;
		} elsif ($x =~ /^(?:(?:un)?signed\s+)?(?:char|int|long|short)$/
		      or $x =~ /^(?:long|short)\s+(?:int|long)$/
		      or $x =~ /^u?int(?:8|16|32|64)$/
		      or $x eq 'defType'
		) { # Numeric variables
			$rtinit = ' = 0';
		} else { # Anything else
			$notes .= "\n/* Unknown return type '$rt'. Initializing to '0'. */";
			print "Unknown return type '$rt'. Initializing to '0'.\n";
			$rtinit = ' = 0';
		}
	}

	return {
		name     => $name,
		vname    => $variadic ? "v$name" : $name,
		type     => $rt,
		typeinit => $rtinit,
		memset   => $rtmemset,
		variadic => $variadic,
		args     => \@args,
		notes    => $notes,
	};
}

my %key2original;
my @files = grep { -f } glob 'doxyoutput/xml/*interface*.xml';
my %ifs;
my @keys;
foreach my $file (@files) { # Loop through the xml files

	my $xml = new XML::Simple;
	my $data = $xml->XMLin($file);

	my $loc = $data->{compounddef}->{location};
	next unless $loc->{file} =~ /src\/map\//; # We only handle mapserver for the time being

	my $key = $data->{compounddef}->{compoundname};
	my $original = $key;

	# Some known interfaces with different names
	if ($key =~ /battleground/) {
		$key = "bg";
	} elsif ($key =~ /guild_storage/) {
		$key = "gstorage";
	} elsif ($key =~ /homunculus/) {
		$key = "homun";
	} elsif ($key =~ /irc_bot/) {
		$key = "ircbot";
	} elsif ($key =~ /log_interface/) {
		$key = "logs";
	} elsif ($key =~ /pc_groups_interface/) {
		$key = "pcg";
	} else {
		$key =~ s/_interface//;
	}

	foreach my $v ($data->{compounddef}->{sectiondef}) { # Loop through the sections
		my $memberdef = $v->{memberdef};
		foreach my $fk (sort { # Sort the members in declaration order according to what the xml says
					my $astart = $memberdef->{$a}->{location}->{bodystart} || $memberdef->{$a}->{location}->{line};
					my $bstart = $memberdef->{$b}->{location}->{bodystart} || $memberdef->{$b}->{location}->{line};
					$astart <=> $bstart
				} keys %$memberdef) { # Loop through the members
			my $f = $memberdef->{$fk};

			my $t = $f->{argsstring};
			next unless ref $t ne 'HASH' and $t =~ /^[^\[]/; # If it's not a string, or if it starts with an array subscript, we can skip it

			my $if = parse($t, $f->{definition});
			next unless scalar keys %$if; # If it returns an empty hash reference, an error must've occurred

			# Skip variadic functions, we only allow hooks on their arglist equivalents.
			# i.e. you can't hook on map->foreachinmap, but you hook on map->vforeachinmap
			# (foreachinmap is guaranteed to do nothing other than call vforeachinmap)
			next if ($if->{variadic});

			# Some preprocessing
			$if->{hname} = "HP_${key}_$if->{name}";
			$if->{hvname} = "HP_${key}_$if->{vname}";

			$if->{handlerdef} = "$if->{type} $if->{hname}(";
			$if->{predef} = "$if->{type} (*preHookFunc) (";
			$if->{postdef} = "$if->{type} (*postHookFunc) (";
			if ($if->{type} eq 'void') {
				$if->{precall} = '';
				$if->{postcall} = '';
				$if->{origcall} = '';
			} else {
				$if->{precall} = "retVal___ = ";
				$if->{postcall} = "retVal___ = ";
				$if->{origcall} = "retVal___ = ";
			}
			$if->{precall} .= "preHookFunc(";
			$if->{postcall} .= "postHookFunc(";
			$if->{origcall} .= "HPMHooks.source.$key.$if->{vname}(";
			$if->{before} = []; $if->{after} = [];

			my ($i, $j) = (0, 0);

			if ($if->{type} ne 'void') {
				$j++;
				$if->{postdef} .= "$if->{type} retVal___";
				$if->{postcall} .= "retVal___";
			}

			foreach my $arg (@{ $if->{args} }) {
				push(@{ $if->{before} }, $arg->{pre}) if ($arg->{pre});
				push(@{ $if->{after} }, $arg->{post}) if ($arg->{post});
				if ($i) {
					$if->{handlerdef} .=  ', ';
					$if->{predef} .= ', ';
					$if->{precall} .= ', ';
					$if->{origcall} .= ', ';
				}
				if ($j) {
					$if->{postdef} .= ', ';
					$if->{postcall} .= ', ';
				}
				$if->{handlerdef} .= $arg->{orig};
				$if->{predef} .= $arg->{hookf};
				$if->{precall} .= $arg->{hookc};
				$if->{postdef} .= $arg->{hookf};
				$if->{postcall} .= $arg->{hookc};
				$if->{origcall} .= $arg->{origc};
				$i++; $j++;
			}

			if (!$i) {
				$if->{predef} .= 'void';
				$if->{handlerdef} .= 'void';
			}
			if (!$j) {
				$if->{postdef} .= 'void';
			}

			$if->{handlerdef} .= ')';
			$if->{predef} .= ");";
			$if->{precall} .= ");";
			$if->{postdef} .= ");";
			$if->{postcall} .= ");";
			$if->{origcall} .= ");";

			$key2original{$key} = $original;
			$ifs{$key} = [] unless $ifs{$key};
			push(@{ $ifs{$key} }, $if);
		}
	}
	push(@keys, $key) if $key2original{$key};
}

# Some interfaces use different names
my %exportsymbols = map {
	$_ => &{ sub ($) {
		return 'battlegrounds' if $_ =~ /^bg$/;
		return 'pc_groups' if $_ =~ /^pcg$/;
		return $_;
	}}($_);
} @keys;

my ($maxlen, $idx) = (0, 0);
my $fname;
$fname = "../../src/plugins/HPMHooking/HPMHooking.HookingPoints.inc";
open(FH, ">", $fname)
	or die "cannot open > $fname: $!";

print FH <<"EOF";
// Copyright (c) Hercules Dev Team, licensed under GNU GPL.
// See the LICENSE file
//
// NOTE: This file was auto-generated and should never be manually edited,
//       as it will get overwritten.

struct HookingPointData HookingPoints[] = {
EOF

foreach my $key (@keys) {
	print FH "/* ".$key." */\n";
	foreach my $if (@{ $ifs{$key} }) {

		print FH <<"EOF";
	{ HP_POP($key\->$if->{name}, $if->{hname}) },
EOF

		$idx += 2;
		$maxlen = length($key."->".$if->{name}) if( length($key."->".$if->{name}) > $maxlen )
	}
}
print FH <<"EOF";
};

int HookingPointsLenMax = $maxlen;
EOF
close FH;

$fname = "../../src/plugins/HPMHooking/HPMHooking.sources.inc";
open(FH, ">", $fname)
	or die "cannot open > $fname: $!";

print FH <<"EOF";
// Copyright (c) Hercules Dev Team, licensed under GNU GPL.
// See the LICENSE file
//
// NOTE: This file was auto-generated and should never be manually edited,
//       as it will get overwritten.

EOF
foreach my $key (@keys) {

	print FH <<"EOF";
memcpy(&HPMHooks.source.$key, $key, sizeof(struct $key2original{$key}));
EOF
}
close FH;

$fname = "../../src/plugins/HPMHooking/HPMHooking.GetSymbol.inc";
open(FH, ">", $fname)
	or die "cannot open > $fname: $!";

print FH <<"EOF";
// Copyright (c) Hercules Dev Team, licensed under GNU GPL.
// See the LICENSE file
//
// NOTE: This file was auto-generated and should never be manually edited,
//       as it will get overwritten.

EOF
foreach my $key (@keys) {

	print FH <<"EOF";
if( !($key = GET_SYMBOL("$exportsymbols{$key}") ) ) return false;
EOF
}
close FH;

$fname = "../../src/plugins/HPMHooking/HPMHooking.HPMHooksCore.inc";
open(FH, ">", $fname)
	or die "cannot open > $fname: $!";

print FH <<"EOF";
// Copyright (c) Hercules Dev Team, licensed under GNU GPL.
// See the LICENSE file
//
// NOTE: This file was auto-generated and should never be manually edited,
//       as it will get overwritten.

struct {
EOF

foreach my $key (@keys) {
	foreach my $if (@{ $ifs{$key} }) {

		print FH <<"EOF";
	struct HPMHookPoint *$if->{hname}_pre;
	struct HPMHookPoint *$if->{hname}_post;
EOF
	}
}
print FH <<"EOF";
} list;

struct {
EOF

foreach my $key (@keys) {
	foreach my $if (@{ $ifs{$key} }) {

		print FH <<"EOF";
	int $if->{hname}_pre;
	int $if->{hname}_post;
EOF
	}
}
print FH <<"EOF";
} count;

struct {
EOF

foreach my $key (@keys) {

	print FH <<"EOF";
	struct $key2original{$key} $key;
EOF
}

print FH <<"EOF";
} source;
EOF
close FH;

$fname = "../../src/plugins/HPMHooking/HPMHooking.Hooks.inc";
open(FH, ">", $fname)
	or die "cannot open > $fname: $!";

print FH <<"EOF";
// Copyright (c) Hercules Dev Team, licensed under GNU GPL.
// See the LICENSE file
//
// NOTE: This file was auto-generated and should never be manually edited,
//       as it will get overwritten.

EOF
foreach my $key (@keys) {

	print FH <<"EOF";
/* $key */
EOF

	foreach my $if (@{ $ifs{$key} }) {
		my ($initialization, $beforeblock3, $beforeblock2, $afterblock3, $afterblock2, $retval) = ('', '', '', '', '', '');

		unless ($if->{type} eq 'void') {
			$initialization  = "\n\t$if->{type} retVal___$if->{typeinit};";
			$initialization .= "\n\tmemset(&retVal___, '\\0', sizeof($if->{type}));" if $if->{memset};
		}

		$beforeblock3 .= "\n\t\t\t$_" foreach (@{ $if->{before} });
		$afterblock3 .= "\n\t\t\t$_" foreach (@{ $if->{after} });
		$beforeblock2 .= "\n\t\t$_" foreach (@{ $if->{before} });
		$afterblock2 .= "\n\t\t$_" foreach (@{ $if->{after} });
		$retval = ' retVal___' unless $if->{type} eq 'void';

		print FH <<"EOF";
$if->{handlerdef} {$if->{notes}
	int hIndex = 0;${initialization}
	if( HPMHooks.count.$if->{hname}_pre ) {
		$if->{predef}
		for(hIndex = 0; hIndex < HPMHooks.count.$if->{hname}_pre; hIndex++ ) {$beforeblock3
			preHookFunc = HPMHooks.list.$if->{hname}_pre[hIndex].func;
			$if->{precall}$afterblock3
		}
		if( *HPMforce_return ) {
			*HPMforce_return = false;
			return$retval;
		}
	}
	{$beforeblock2
		$if->{origcall}$afterblock2
	}
	if( HPMHooks.count.$if->{hname}_post ) {
		$if->{postdef}
		for(hIndex = 0; hIndex < HPMHooks.count.$if->{hname}_post; hIndex++ ) {$beforeblock3
			postHookFunc = HPMHooks.list.$if->{hname}_post[hIndex].func;
			$if->{postcall}$afterblock3
		}
	}
	return$retval;
}
EOF
	}
}

close FH;