# HLstatsX Community Edition - Real-time player and clan rankings and statistics # Copyleft (L) 2008-20XX Nicholas Hastings (nshastings@gmail.com) # http://www.hlxcommunity.com # # HLstatsX Community Edition is a continuation of # ELstatsNEO - Real-time player and clan rankings and statistics # Copyleft (L) 2008-20XX Malte Bayer (steam@neo-soft.org) # http://ovrsized.neo-soft.org/ # # ELstatsNEO is an very improved & enhanced - so called Ultra-Humongus Edition of HLstatsX # HLstatsX - Real-time player and clan rankings and statistics for Half-Life 2 # http://www.hlstatsx.com/ # Copyright (C) 2005-2007 Tobias Oetzel (Tobi@hlstatsx.com) # # HLstatsX is an enhanced version of HLstats made by Simon Garner # HLstats - Real-time player and clan rankings and statistics for Half-Life # http://sourceforge.net/projects/hlstats/ # Copyright (C) 2001 Simon Garner # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # For support and installation notes visit http://www.hlxcommunity.com # HLstatsX CE release version number $g_version = ""; my %db_stmt_cache = (); %g_eventTables = ( "TeamBonuses", ["playerId", "actionId", "bonus"], "ChangeRole", ["playerId", "role"], "ChangeName", ["playerId", "oldName", "newName"], "ChangeTeam", ["playerId", "team"], "Connects", ["playerId", "ipAddress", "hostname", "hostgroup"], "Disconnects", ["playerId"], "Entries", ["playerId"], "Frags", ["killerId", "victimId", "weapon", "headshot", "killerRole", "victimRole", "pos_x","pos_y","pos_z", "pos_victim_x","pos_victim_y","pos_victim_z"], "PlayerActions", ["playerId", "actionId", "bonus", "pos_x","pos_y","pos_z"], "PlayerPlayerActions", ["playerId", "victimId", "actionId", "bonus", "pos_x","pos_y","pos_z", "pos_victim_x","pos_victim_y","pos_victim_z"], "Suicides", ["playerId", "weapon", "pos_x","pos_y","pos_z"], "Teamkills", ["killerId", "victimId", "weapon", "pos_x","pos_y","pos_z", "pos_victim_x","pos_victim_y","pos_victim_z"], "Rcon", ["type", "remoteIp", "password", "command"], "Admin", ["type", "message", "playerName"], "Statsme", ["playerId", "weapon", "shots", "hits", "headshots", "damage", "kills", "deaths"], "Statsme2", ["playerId", "weapon", "head", "chest", "stomach", "leftarm", "rightarm", "leftleg", "rightleg"], "StatsmeLatency", ["playerId", "ping"], "StatsmeTime", ["playerId", "time"], "Latency", ["playerId", "ping"], "Chat", ["playerId", "message_mode", "message"] ); ## ## Common Functions ## sub number_format { local $_ = shift; 1 while s/^(-?\d+)(\d{3})/$1,$2/; return $_; } sub date_format { my $timestamp = shift; return sprintf('%dd %02d:%02d:%02dh', $timestamp / 86400, $timestamp / 3600 % 24, $timestamp / 60 % 60, $timestamp % 60 ); } # # void error (string errormsg) # # Dies, and optionally mails error messages to $g_mailto. # sub error { my $errormsg = $_[0]; if ($g_mailto && $g_mailpath) { system("echo \"$errormsg\" | $g_mailpath -s \"HLstatsX:CE crashed `date`\" $g_mailto"); } die("$errormsg\n"); } # # string quoteSQL (string varQuote) # # Escapes all quote characters in a variable, making it suitable for use in an # SQL query. Returns the escaped version. # sub quoteSQL { my $varQuote = $_[0]; $varQuote =~ s/\\/\\\\/g; # replace \ with \\ $varQuote =~ s/'/\\'/g; # replace ' with \' return $varQuote; } # # void doConnect # # Connects to the HLstatsX database # sub doConnect { $db_conn = DBI->connect( "DBI:mysql:$db_name:$db_host", $db_user, $db_pass, { mysql_enable_utf8 => 1 } ); while(!$db_conn) { &printEvent("MYSQL", "\nCan't connect to MySQL database '$db_name' on '$db_host'\n" . "Server error: $DBI::errstr\n"); sleep(5); $db_conn = DBI->connect( "DBI:mysql:$db_name:$db_host", $db_user, $db_pass, { mysql_enable_utf8 => 1 } ); } $db_conn->do("SET NAMES 'utf8'"); &printEvent("MYSQL", "Connecting to MySQL database '$db_name' on '$db_host' as user '$db_user' ... connected ok", 1); %db_stmt_cache = (); } # # result doQuery (string query) # # Executes the SQL query 'query' and returns the result identifier. # sub doQuery { my ($query, $callref) = @_; if(!$db_conn->ping()) { &printEvent("HLSTATSX", "Lost database connection. Trying to reconnect...", 1); &doConnect(); } my $result = $db_conn->prepare($query) or die("Unable to prepare query:\n$query\n$DBI::errstr\n$callref"); $result->execute or die("Unable to execute query:\n$query\n$DBI::errstr\n$callref"); return $result; } sub execNonQuery { my ($query) = @_; if(!$db_conn->ping()) { &printEvent("HLSTATSX", "Lost database connection. Trying to reconnect...", 1); &doConnect(); } #&printEvent("DEBUG","execNonQuery:\n".$query); $db_conn->do($query); } sub execCached { my ($query_id,$query, @bind_args) = @_; if(!$db_conn->ping()) { &printEvent("HLSTATSX", "Lost database connection. Trying to reconnect...", 1); &doConnect(); } if(!$db_stmt_cache{$query_id}) { $db_stmt_cache{$query_id} = $db_conn->prepare($query) or die("Unable to prepare query ($query_id):\n$query\n$DBI::errstr"); #&printEvent("HLSTATSX", "Prepared a statement ($query_id) for the first time.", 1); } $db_stmt_cache{$query_id}->execute(@bind_args) or die ("Unable to execute query ($query_id):\n$query\n$DBI::errstr"); return $db_stmt_cache{$query_id}; } # # string resolveIp (string ip, boolean quiet) # # Do a DNS reverse-lookup on an IP address and return the hostname, or empty # string on error. # sub resolveIp { my ($ip, $quiet) = @_; my ($host) = ""; unless ($g_dns_resolveip) { return ""; } eval { $SIG{ALRM} = sub { die "DNS Timeout\n" }; alarm $g_dns_timeout; # timeout after $g_dns_timeout sec $host = gethostbyaddr(inet_aton($ip), AF_INET); alarm 0; }; if ($@) { my $error = $@; chomp($error); printEvent("DNS", "Resolving hostname (timeout $g_dns_timeout sec) for IP \"$ip\" - $error ", 1); $host = ""; # some error occurred } elsif (!defined($host)) { printEvent("DNS", "Resolving hostname (timeout $g_dns_timeout sec) for IP \"$ip\" - No Host ", 1); $host = ""; # ip did not resolve to any host } else { $host = lc($host); # lowercase printEvent("DNS", "Resolving hostname (timeout $g_dns_timeout sec) for IP \"$ip\" - $host ", 1); } chomp($host); return $host; } # # object queryHostGroups () # # Returns result identifier. # sub queryHostGroups { return &doQuery(" SELECT pattern, name, LENGTH(pattern) AS patternlength FROM hlstats_HostGroups ORDER BY patternlength DESC, pattern ASC "); } # # string getHostGroup (string hostname[, object result]) # # Return host group name if any match, or last 2 or 3 parts of hostname. # sub getHostGroup { my ($hostname, $result) = @_; my $hostgroup = ""; # User can define special named hostgroups in hlstats_HostGroups, i.e. # '.adsl.someisp.net' => 'SomeISP ADSL' $result = &queryHostGroups() unless ($result); $result->execute(); while (my($pattern, $name) = $result->fetchrow_array()) { $pattern = quotemeta($pattern); $pattern =~ s/\\\*/[^.]*/g; # allow basic shell-style globbing in pattern if ($hostname =~ /$pattern$/) { $hostgroup = $name; last; } } $result->finish; if (!$hostgroup) { # # Group by last 2 or 3 parts of hostname, i.e. 'max1.xyz.someisp.net' as # 'someisp.net', and 'max1.xyz.someisp.net.nz' as 'someisp.net.nz'. # Unfortunately some countries do not have categorical SLDs, so this # becomes more complicated. The dom_nosld array below contains a list of # known country codes that do not use categorical second level domains. # If a country uses SLDs and is not listed below, then it will be # incorrectly grouped, i.e. 'max1.xyz.someisp.yz' will become # 'xyz.someisp.yz', instead of just 'someisp.yz'. # # Please mail sgarner@hlstats.org with any additions. # my @dom_nosld = ( "ca", # Canada "ch", # Switzerland "be", # Belgium "de", # Germany "ee", # Estonia "es", # Spain "fi", # Finland "fr", # France "ie", # Ireland "nl", # Netherlands "no", # Norway "ru", # Russia "se", # Sweden ); my $dom_nosld = join("|", @dom_nosld); if ($hostname =~ /([\w-]+\.(?:$dom_nosld|\w\w\w))$/) { $hostgroup = $1; } elsif ($hostname =~ /([\w-]+\.[\w-]+\.\w\w)$/) { $hostgroup = $1; } else { $hostgroup = $hostname; } } return $hostgroup; } # # void doConf (object conf, hash directives) # # Walk through configuration directives, setting values of global variables. # sub doConf { my ($conf, %directives) = @_; while (($directive, $variable) = each(%directives)) { if ($directive eq "Servers") { %$variable = $conf->get($directive); } else { $$variable = $conf->get($directive); } } } # # void setOptionsConf (hash optionsconf) # # Walk through configuration directives, setting values of global variables. # sub setOptionsConf { my (%optionsconf) = @_; while (($thekey, $theval) = each(%optionsconf)) { if($theval) { $$thekey = $theval; } } } # # string abbreviate (string thestring[, int maxlength) # # Returns thestring abbreviated to maxlength-3 characters plus "...", unless # thestring is shorter than maxlength. # sub abbreviate { my ($thestring, $maxlength) = @_; $maxlength = 12 unless ($maxlength); if (length($thestring) > $maxlength) { $thestring = substr($thestring, 0, $maxlength - 3); return "$thestring..."; } else { return $thestring; } } # # void printEvent (int code, string description) # # Logs event information to stdout. # sub printEvent { my ($code, $description, $update_timestamp, $force_output) = @_; if ( (($g_debug > 0) && ($g_stdin == 0))|| (($g_stdin == 1) && ($force_output == 1)) ) { my ($sec,$min,$hour,$mday,$mon,$year) = localtime(time()); my $timestamp = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec); if ($update_timestamp == 0) { $timestamp = $ev_timestamp; } if (is_number($code)) { printf("%s: %21s - E%03d: %s\n", $timestamp, $s_addr, $code, $description); } else { printf("%s: %21s - %s: %s\n", $timestamp, $s_addr, $code, $description); } } } 1;