Initial Commit

This commit is contained in:
Steffen Schröder 2013-11-07 20:13:13 +01:00
commit 1cf4021d4f
34 changed files with 7357 additions and 0 deletions

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure authentication for players -->
<authentication-config>
<!-- SuperAdmins with full access -->
<superadmin>
<!-- <login>login1</login> <login>login2</login> -->
<login>steeffeen</login>
</superadmin>
<!-- Admins with almost full access -->
<admin>
<!-- <login>login1</login> <login>login2</login> -->
<login>gorby</login>
<login>canyondrive</login>
</admin>
<!-- Operators with only moderating access -->
<operator>
<!-- <login>login1</login> <login>login2</login> -->
<login>eyebo</login>
<login>jojo95183</login>
<login>xanashea</login>
<login>ardid</login>
<login>gugli</login>
<login>phil13hebert</login>
<login>xcaliber</login>
<login>eole</login>
<login>fix</login>
<login>kremsy</login>
<login>papychampy</login>
<login>titishu</login>
<login>wurstigewurst</login>
</operator>
</authentication-config>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure how iControl sends chat message -->
<chat-config>
<!-- Set which codes should be used to compose chat messages -->
<messages>
<information>$fff</information>
<success>$0f0</success>
<error>$f00</error>
</messages>
</chat-config>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure the chatlog plugin -->
<chatlog-config>
<!-- Enable plugin -->
<enabled>true</enabled>
<!-- Filename for the chatlog -->
<filename>chat.log</filename>
<!-- Log server messages -->
<log_server_messages>true</log_server_messages>
</chatlog-config>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Enable/Disable chat commands -->
<!-- Authentication levels: superadmin, admin, operator, all, none -->
<!-- 'Operator' means that you have to be at least Operator to perform the command -->
<!-- 'None' means that nobody is able to use the command (disabled) -->
<commands-config>
<!-- SuperAdmin commands -->
<superadmin>
<shutdown />
<shutdownserver />
<networkstats />
<systeminfo />
<pay />
</superadmin>
<!-- Admin commands -->
<admin>
<getplanets />
<setservername />
<getservername />
</admin>
<!-- Operator commands -->
<operator>
<kick />
<nextmap />
<addmap />
<removemap />
<startwarmup />
<stopwarmup />
</operator>
<!-- Everybody commands -->
<all>
<help />
<donate />
</all>
<!-- Nobody commands -->
<none>
</none>
</commands-config>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Core configuration for iControl -->
<core-config>
<!-- Connection timeout -->
<timeout>20</timeout>
<!-- Define the file name for automatically saving the match settings after each map (relative from /Maps/MatchSettings/) -->
<!-- (leave empty for not saving) -->
<autosave_matchsettings>tracklist.txt</autosave_matchsettings>
<!-- Directory for downloaded maps (relative from /Maps/) -->
<maps_dir>mx</maps_dir>
</core-config>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure the mysql database used by iControl -->
<database-config>
<!-- MySQL Server -->
<host>localhost</host>
<port>3306</port>
<!-- MySQL User -->
<user>steff</user>
<pass>kjhgvhbjnfih2394ugnjk</pass>
<!-- Database Name -->
<database>steff_united</database>
</database-config>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure the karma plugin -->
<karma-plugin-config>
<!-- Enable the plugin -->
<enabled>true</enabled>
<!-- Title -->
<title>Map Voting</title>
<!-- Position -->
<pos_x>92</pos_x>
<pos_y>83</pos_y>
</karma-plugin-config>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure the obstacle plugin -->
<obstacle-config>
<!-- Enable/Disable the whole plugin -->
<enabled>true</enabled>
<!-- Set right level needed for jumps -->
<jumps_rightlevel>operator</jumps_rightlevel>
</obstacle-config>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure which plugins should be loaded -->
<plugins-config>
<!-- <plugin>FILENAME1</plugin> <plugin>FILENAME2</plugin> -->
<plugin>chatlog.plugin.php</plugin>
<plugin>karma.plugin.php</plugin>
<plugin>records.plugin.php</plugin>
<plugin>united.plugin.php</plugin>
<!-- <plugin>obstacle.plugin.php</plugin> -->
</plugins-config>

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure the records plugin -->
<records-config>
<!-- Enable/Disable the whole plugin -->
<enabled>true</enabled>
<!-- Configure local records -->
<local_records>
<enabled>true</enabled>
<!-- Define how many records per map are saved -->
<count>200</count>
<!-- Define the widget -->
<widget>
<enabled>true</enabled>
<pos_x>-139</pos_x>
<pos_y>65</pos_y>
<title>Local Records</title>
<width>40</width>
<lines>25</lines>
<line_height>4</line_height>
</widget>
</local_records>
<!-- Configure dedimania records -->
<dedimania_records>
<enabled>true</enabled>
<!-- Define your dedimania account (http://dedimania.net/tm2stats/?do=register) -->
<account>
<login>steff_test</login>
<code>468a0a185c</code>
</account>
<account>
<login>nsa_dev</login>
<code>3c20cbc737</code>
</account>
<account>
<login>united_canyon</login>
<code>703d080ddd</code>
</account>
<account>
<login>united_stadium</login>
<code>067b0d0017</code>
</account>
<account>
<login>united_valley</login>
<code>37ed993fe1</code>
</account>
<!-- Define the widget -->
<widget>
<enabled>true</enabled>
<pos_x>139</pos_x>
<pos_y>60</pos_y>
<title>Dedimania</title>
<width>40</width>
<lines>20</lines>
<line_height>4</line_height>
</widget>
</dedimania_records>
</records-config>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure your maniaplanet server -->
<server-config>
<!-- Enable to use only one static server (default behavior) -->
<enable>true</enable>
<!-- Server connection details (Make sure that the firewall and the server config are properly configured!) -->
<host>144.76.158.111</host>
<host>localhost</host>
<port>21003</port>
<!-- XmlRpc login details -->
<login>SuperAdmin</login>
<pass>dtcfvgubhnjomkjnbhv</pass>
</server-config>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure the stats manager and enable/disable specific stats -->
<stats-config>
<stats>
<!-- Server stats -->
<server>
<!-- Track how many players connected on the server -->
<track_server_connects>true</track_server_connects>
<!-- Track how many players have been on the server at max -->
<track_server_max_players>true</track_server_max_players>
<!-- Track how many maps have been played during the day -->
<track_server_played_maps>true</track_server_played_maps>
<!-- Track how many times players finished during the day -->
<track_server_finishes>true</track_server_finishes>
</server>
<!-- Player stats -->
<player>
<!-- Track player connect counts -->
<track_player_connects>true</track_player_connects>
<!-- Track player play time -->
<track_player_playtime>true</track_player_playtime>
<!-- Track how many chat message players have sent -->
<track_player_chats>true</track_player_chats>
<!-- Track player finishes -->
<track_player_finishes>true</track_player_finishes>
</player>
</stats>
</stats-config>

View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configure the United plugin -->
<!-- Note: You should disable the config server.iControl.xml while using this plugin -->
<united-config>
<!-- Enable the whole plugin -->
<enabled>false</enabled>
<!-- Configure the server order (Random or as defined in the xml) -->
<random_order>false</random_order>
<!-- Define the mode that should be running on all the server -->
<!-- Rounds (1), TimeAttack (2), Team (3), Laps (4), Cup (5), Stunts (6) -->
<mode>2</mode>
<!-- Whether game server should be hidden from the browser -->
<hide_game_server>false</hide_game_server>
<!-- Set lobby status for lobby server -->
<set_lobby_status>true</set_lobby_status>
<!-- Define the game server -->
<gameserver>
<!-- Define the join/spec password for game server -->
<password>a</password>
<!-- Template -->
<!-- <server> -->
<!-- <host>ip</host> -->
<!-- <port>5000</port> -->
<!-- <login>SuperAdmin</login> -->
<!-- <pass>SuperAdmin</pass> -->
<!-- </server> -->
<!-- Canyon -->
<server>
<host>144.76.158.111</host>
<port>21013</port>
<login>SuperAdmin</login>
<pass>dtcfvgubhnjomkjnbhv</pass>
</server>
<!-- Stadium -->
<server>
<host>144.76.158.111</host>
<port>21033</port>
<login>SuperAdmin</login>
<pass>dtcfvgubhnjomkjnbhv</pass>
</server>
<!-- Valley -->
<server>
<host>144.76.158.111</host>
<port>21053</port>
<login>SuperAdmin</login>
<pass>dtcfvgubhnjomkjnbhv</pass>
</server>
</gameserver>
<!-- Define lobbies -->
<lobbies>
<!-- Define the join/spec password for lobbies -->
<password>a</password>
<!-- Template -->
<!-- <server> -->
<!-- <host>ip</host> -->
<!-- <port>5000</port> -->
<!-- <login>SuperAdmin</login> -->
<!-- <pass>SuperAdmin</pass> -->
<!-- </server> -->
<!-- Canyon -->
<server>
<host>144.76.158.111</host>
<port>21023</port>
<login>SuperAdmin</login>
<pass>dtcfvgubhnjomkjnbhv</pass>
</server>
<!-- Stadium -->
<server>
<host>144.76.158.111</host>
<port>21043</port>
<login>SuperAdmin</login>
<pass>dtcfvgubhnjomkjnbhv</pass>
</server>
<!-- Valley -->
<server>
<host>144.76.158.111</host>
<port>21063</port>
<login>SuperAdmin</login>
<pass>dtcfvgubhnjomkjnbhv</pass>
</server>
</lobbies>
<!-- Configure widgets -->
<widgets>
<enabled>true</enabled>
<!-- Favorite widget -->
<addfavorite>
<enabled>true</enabled>
<pos_x>76</pos_x>
<pos_y>85.5</pos_y>
<width>10</width>
<height>10</height>
<!-- Enable add_all to let players add all game and lobby server to favorite with 1 click -->
<add_all>true</add_all>
</addfavorite>
</widgets>
</united-config>

View File

@ -0,0 +1,892 @@
<?php
/* vim: set noexpandtab tabstop=2 softtabstop=2 shiftwidth=2: */
/*
IXR - The Incutio XML-RPC Library - (c) Incutio Ltd 2002
Version 1.61 - Simon Willison, 11th July 2003 (htmlentities -> htmlspecialchars)
Site: http://scripts.incutio.com/xmlrpc/
Manual: http://scripts.incutio.com/xmlrpc/manual.php
Errors: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
Made available under the Artistic License: http://www.opensource.org/licenses/artistic-license.php
Modified to support protocol 'GbxRemote 2' ('GbxRemote 1')
This version is for BigEndian machines. For LittleEndian (e.g. Intel PC)
machines use the original GbxRemote.inc.php instead.
Release 2007-09-22 - Slig:
Modified to support >256KB received data (and now >2MB data produce a specific error message)
Modified readCB() to wait the initial timeout only before first read packet
Modified readCB() to return true if there is data to get with getCBResponses()
Modified to support amd64 (for $recvhandle)
Modified IXR_ClientMulticall_Gbx::addCall() to fit Aseco 0.6.1
Added IXR_Client_Gbx::bytes_sent & bytes_received counters
Fix for a changed feature since php5.1.1 about reference parameter assignment (was used in stream_select)
Workaround for stream_select return value bug with amd64
Release 2008-01-20 - Slig / Xymph / Assembler Maniac:
Workaround for fread delay bug in some cases
Added IXR_Client_Gbx::resetError() method (by Xymph)
Some comments and strings code cleanup (by Xymph)
Fix stream_set_timeout($this->socket,...) (thx to CavalierDeVache)
Added a default timeout value to IXR_Client_Gbx::readCB($timeout)
Changed calls with timeout on a stream to use microseconds instead of seconds (by AM)
Removed IXR_Client_Gbx::bytes_sent & bytes_received counters - not used (by AM)
Release 2008-02-05 - Slig:
Changed some socket read/write timeouts back to seconds to avoid 'transport error'
Changed max data received from 2MB to 4MB
Release 2008-05-20 - Xymph:
Prevented unpack() warnings in IXR_Client_Gbx::query() when the connection dies
Changed IXR_Client_Gbx::resetError() to assign 'false' for correct isError()
Tweaked some 'transport error' messages
Release 2009-04-08 - Gou1:
Added method IXR_Client_Gbx::queryIgnoreResult()
Added methods IXR_Client_Gbx::sendRequest() & IXR_Client_Gbx::getResult()
IXR_Client_Gbx::queryIgnoreResult checks if the request is larger than 512KB to avoid errors
If larger than 512KB and method is system.multicall, try to divide the request into
two separate requests with two separate IXR_Client_Gbx::queryIgnoreResult() calls
Release 2009-06-03 - Xymph:
Suppress possible repetitive CRT warning at stream_select
Release 2011-04-09 - Xymph / La beuze:
Added optional timeout mechanism to IXR_Client_Gbx::InitWithIp()
Release 2011-05-22 - Xymph:
Added non-error (true) return status to IXR_Client_Gbx::queryIgnoreResult()
Updated status codes and messages for transport/endian errors
Prevented possible PHP warning in IXR_Client_Gbx::getErrorCode() and getErrorMessage()
*/
if (!defined('LF')) {
define('LF', "\n");
}
class IXR_Value {
public $data;
public $type;
function IXR_Value ($data, $type = false) {
$this->data = $data;
if (!$type) {
$type = $this->calculateType();
}
$this->type = $type;
if ($type == 'struct') {
// Turn all the values in the array into new IXR_Value objects
foreach ($this->data as $key => $value) {
$this->data[$key] = new IXR_Value($value);
}
}
if ($type == 'array') {
for ($i = 0, $j = count($this->data); $i < $j; $i++) {
$this->data[$i] = new IXR_Value($this->data[$i]);
}
}
}
function calculateType() {
if ($this->data === true || $this->data === false) {
return 'boolean';
}
if (is_integer($this->data)) {
return 'int';
}
if (is_double($this->data)) {
return 'double';
}
// Deal with IXR object types base64 and date
if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
return 'date';
}
if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
return 'base64';
}
// If it is a normal PHP object convert it into a struct
if (is_object($this->data)) {
$this->data = get_object_vars($this->data);
return 'struct';
}
if (!is_array($this->data)) {
return 'string';
}
// We have an array - is it an array or a struct?
if ($this->isStruct($this->data)) {
return 'struct';
} else {
return 'array';
}
}
function getXml() {
// Return XML for this value
switch ($this->type) {
case 'boolean':
return '<boolean>' . ($this->data ? '1' : '0') . '</boolean>';
break;
case 'int':
return '<int>' . $this->data . '</int>';
break;
case 'double':
return '<double>' . $this->data . '</double>';
break;
case 'string':
return '<string>' . htmlspecialchars($this->data) . '</string>';
break;
case 'array':
$return = '<array><data>' . LF;
foreach ($this->data as $item) {
$return .= ' <value>' . $item->getXml() . '</value>' . LF;
}
$return .= '</data></array>';
return $return;
break;
case 'struct':
$return = '<struct>' . LF;
foreach ($this->data as $name => $value) {
$return .= ' <member><name>' . $name . '</name><value>';
$return .= $value->getXml() . '</value></member>' . LF;
}
$return .= '</struct>';
return $return;
break;
case 'date':
case 'base64':
return $this->data->getXml();
break;
}
return false;
}
function isStruct($array) {
// Nasty function to check if an array is a struct or not
$expected = 0;
foreach ($array as $key => $value) {
if ((string)$key != (string)$expected) {
return true;
}
$expected++;
}
return false;
}
}
class IXR_Message {
public $message;
public $messageType; // methodCall / methodResponse / fault
public $faultCode;
public $faultString;
public $methodName;
public $params;
// Current variable stacks
protected $_arraystructs = array(); // Stack to keep track of the current array/struct
protected $_arraystructstypes = array(); // Stack to keep track of whether things are structs or array
protected $_currentStructName = array(); // A stack as well
protected $_param;
protected $_value;
protected $_currentTag;
protected $_currentTagContents;
// The XML parser
protected $_parser;
function IXR_Message ($message) {
$this->message = $message;
}
function parse() {
// first remove the XML declaration
$this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);
if (trim($this->message) == '') {
return false;
}
$this->_parser = xml_parser_create();
// Set XML parser to take the case of tags into account
xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
// Set XML parser callback functions
xml_set_object($this->_parser, $this);
xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
xml_set_character_data_handler($this->_parser, 'cdata');
if (!xml_parse($this->_parser, $this->message)) {
/* die(sprintf('GbxRemote XML error: %s at line %d',
xml_error_string(xml_get_error_code($this->_parser)),
xml_get_current_line_number($this->_parser))); */
return false;
}
xml_parser_free($this->_parser);
// Grab the error messages, if any
if ($this->messageType == 'fault') {
$this->faultCode = $this->params[0]['faultCode'];
$this->faultString = $this->params[0]['faultString'];
}
return true;
}
function tag_open($parser, $tag, $attr) {
$this->currentTag = $tag;
switch ($tag) {
case 'methodCall':
case 'methodResponse':
case 'fault':
$this->messageType = $tag;
break;
// Deal with stacks of arrays and structs
case 'data': // data is to all intents and purposes more interesting than array
$this->_arraystructstypes[] = 'array';
$this->_arraystructs[] = array();
break;
case 'struct':
$this->_arraystructstypes[] = 'struct';
$this->_arraystructs[] = array();
break;
}
}
function cdata($parser, $cdata) {
$this->_currentTagContents .= $cdata;
}
function tag_close($parser, $tag) {
$valueFlag = false;
switch ($tag) {
case 'int':
case 'i4':
$value = (int)trim($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'double':
$value = (double)trim($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'string':
$value = (string)trim($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'dateTime.iso8601':
$value = new IXR_Date(trim($this->_currentTagContents));
// $value = $iso->getTimestamp();
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'value':
// If no type is indicated, the type is string
if (trim($this->_currentTagContents) != '') {
$value = (string)$this->_currentTagContents;
$this->_currentTagContents = '';
$valueFlag = true;
}
break;
case 'boolean':
$value = (boolean)trim($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'base64':
$value = base64_decode($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
// Deal with stacks of arrays and structs
case 'data':
case 'struct':
$value = array_pop($this->_arraystructs);
array_pop($this->_arraystructstypes);
$valueFlag = true;
break;
case 'member':
array_pop($this->_currentStructName);
break;
case 'name':
$this->_currentStructName[] = trim($this->_currentTagContents);
$this->_currentTagContents = '';
break;
case 'methodName':
$this->methodName = trim($this->_currentTagContents);
$this->_currentTagContents = '';
break;
}
if ($valueFlag) {
/*
if (!is_array($value) && !is_object($value)) {
$value = trim($value);
}
*/
if (count($this->_arraystructs) > 0) {
// Add value to struct or array
if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
// Add to struct
$this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
} else {
// Add to array
$this->_arraystructs[count($this->_arraystructs)-1][] = $value;
}
} else {
// Just add as a paramater
$this->params[] = $value;
}
}
}
}
class IXR_Request {
public $method;
public $args;
public $xml;
function IXR_Request($method, $args) {
$this->method = $method;
$this->args = $args;
$this->xml = '<?xml version="1.0" encoding="utf-8" ?><methodCall><methodName>' . $this->method . '</methodName><params>';
foreach ($this->args as $arg) {
$this->xml .= '<param><value>';
$v = new IXR_Value($arg);
$this->xml .= $v->getXml();
$this->xml .= '</value></param>' . LF;
}
$this->xml .= '</params></methodCall>';
}
function getLength() {
return strlen($this->xml);
}
function getXml() {
return $this->xml;
}
}
class IXR_Error {
public $code;
public $message;
function IXR_Error($code, $message) {
$this->code = $code;
$this->message = $message;
}
function getXml() {
$xml = <<<EOD
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>{$this->code}</int></value>
</member>
<member>
<name>faultString</name>
<value><string>{$this->message}</string></value>
</member>
</struct>
</value>
</fault>
</methodResponse>
EOD;
return $xml;
}
}
class IXR_Date {
public $year;
public $month;
public $day;
public $hour;
public $minute;
public $second;
function IXR_Date($time) {
// $time can be a PHP timestamp or an ISO one
if (is_numeric($time)) {
$this->parseTimestamp($time);
} else {
$this->parseIso($time);
}
}
function parseTimestamp($timestamp) {
$this->year = date('Y', $timestamp);
$this->month = date('Y', $timestamp);
$this->day = date('Y', $timestamp);
$this->hour = date('H', $timestamp);
$this->minute = date('i', $timestamp);
$this->second = date('s', $timestamp);
}
function parseIso($iso) {
$this->year = substr($iso, 0, 4);
$this->month = substr($iso, 4, 2);
$this->day = substr($iso, 6, 2);
$this->hour = substr($iso, 9, 2);
$this->minute = substr($iso, 12, 2);
$this->second = substr($iso, 15, 2);
}
function getIso() {
return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second;
}
function getXml() {
return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
}
function getTimestamp() {
return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
}
}
class IXR_Base64 {
public $data;
function IXR_Base64($data) {
$this->data = $data;
}
function getXml() {
return '<base64>'.base64_encode($this->data).'</base64>';
}
}
//////////////////////////////////////////////////////////
// Nadeo modifications //
// (many thanks to slig for adding callback support) //
//////////////////////////////////////////////////////////
class IXR_Client_Gbx {
public $socket;
public $message = false;
public $cb_message = array();
public $reqhandle;
public $protocol = 0;
// Storage place for an error message
public $error = false;
function bigEndianTest() {
list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
if ($endiantest == 1) {
echo "Machine reports itself as LittleEndian, float handling will work correctly with unpack\r\n";
echo "Use original GbxRemote.inc.php instead of GbxRemote.bem.php\r\n";
die('App Terminated');
return false;
}
return true;
} // bigEndianTest
function IXR_Client_Gbx() {
$this->socket = false;
$this->reqhandle = 0x80000000;
}
function InitWithIp($ip, $port, $timeout = null) {
if (!$this->bigEndianTest()) {
$this->error = new IXR_Error(-31999, 'endian error - script doesn\'t match machine type');
return false;
}
// open connection, with timeout if specified
if (!isset($timeout)) {
$this->socket = @fsockopen($ip, $port, $errno, $errstr);
} else {
$init_time = microtime(true);
$init_timeout = 5; // retry every 5s
while (true) {
$this->socket = @fsockopen($ip, $port, $errno, $errstr, $init_timeout);
if ($this->socket || (microtime(true) - $init_time >= $timeout))
break;
}
}
if (!$this->socket) {
$this->error = new IXR_Error(-32300, "transport error - could not open socket (error: $errno, $errstr)");
return false;
}
// handshake
$array_result = big_endian_unpack('Vsize', fread($this->socket, 4));
$size = $array_result['size'];
if ($size > 64) {
$this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol header');
return false;
}
$handshake = fread($this->socket, $size);
if ($handshake == 'GBXRemote 1') {
$this->protocol = 1;
} else if ($handshake == 'GBXRemote 2') {
$this->protocol = 2;
} else {
$this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol version');
return false;
}
return true;
}
function Init($port) {
return $this->InitWithIp('localhost', $port);
}
function Terminate() {
if ($this->socket) {
fclose($this->socket);
$this->socket = false;
}
}
protected function sendRequest(IXR_Request $request) {
$xml = $request->getXml();
@stream_set_timeout($this->socket, 20); // timeout 20s (to write the request)
// send request
$this->reqhandle++;
if ($this->protocol == 1) {
$bytes = pack('Va*', strlen($xml), $xml);
} else {
$bytes = pack('VVa*', strlen($xml), $this->reqhandle, $xml);
}
$bytes_to_write = strlen($bytes);
while ($bytes_to_write > 0) {
$r = @fwrite($this->socket, $bytes);
if ($r === false || $r == 0) {
// connection interrupted
return false; // or die?
}
$bytes_to_write -= $r;
if ($bytes_to_write == 0)
break;
$bytes = substr($bytes, $r);
}
return true;
}
protected function getResult() {
$contents = '';
$contents_length = 0;
do {
$size = 0;
$recvhandle = 0;
@stream_set_timeout($this->socket, 20); // timeout 20s (to read the reply header)
// Get result
if ($this->protocol == 1) {
$contents = fread($this->socket, 4);
if (strlen($contents) == 0) {
$this->error = new IXR_Error(-32300, 'transport error - cannot read size');
return false;
}
$array_result = big_endian_unpack('Vsize', $contents);
$size = $array_result['size'];
$recvhandle = $this->reqhandle;
} else {
$contents = fread($this->socket, 8);
if (strlen($contents) == 0) {
$this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
return false;
}
$array_result = big_endian_unpack('Vsize/Vhandle', $contents);
$size = $array_result['size'];
$recvhandle = $array_result['handle'];
// -- amd64 support --
$bits = sprintf('%b', $recvhandle);
if (strlen($bits) == 64) {
$recvhandle = bindec(substr($bits, 32));
}
}
if ($recvhandle == 0 || $size == 0) {
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
return false;
}
if ($size > 4096*1024) {
$this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
return false;
}
$contents = '';
$contents_length = 0;
@stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (for successive reads until end)
while ($contents_length < $size) {
$contents .= fread($this->socket, $size-$contents_length);
$contents_length = strlen($contents);
}
if (($recvhandle & 0x80000000) == 0) {
// this is a callback, not our answer! handle= $recvhandle, xml-rpc= $contents
// just add it to the message list for the user to read
$new_cb_message = new IXR_Message($contents);
if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
}
}
} while ((int)$recvhandle != (int)$this->reqhandle);
$this->message = new IXR_Message($contents);
if (!$this->message->parse()) {
// XML error
$this->error = new IXR_Error(-32700, 'parse error. not well formed');
return false;
}
// Is the message a fault?
if ($this->message->messageType == 'fault') {
$this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
return false;
}
// Message must be OK
return true;
}
function query() {
$args = func_get_args();
$method = array_shift($args);
if (!$this->socket || $this->protocol == 0) {
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
return false;
}
$request = new IXR_Request($method, $args);
// Check if request is larger than 512 Kbytes
if (($size = $request->getLength()) > 512*1024-8) {
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
return false;
}
// Send request
$ok = $this->sendRequest($request);
if (!$ok) {
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
return false;
}
// Get result
return $this->getResult();
}
// Non-blocking query method: doesn't read the response
function queryIgnoreResult() {
$args = func_get_args();
$method = array_shift($args);
if (!$this->socket || $this->protocol == 0) {
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
return false;
}
$request = new IXR_Request($method, $args);
// Check if the request is greater than 512 Kbytes to avoid errors
// If the method is system.multicall, make two calls (possibly recursively)
if (($size = $request->getLength()) > 512*1024-8) {
if ($method = 'system.multicall' && isset($args[0])) {
$count = count($args[0]);
// If count is 1, query cannot be reduced
if ($count < 2) {
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
return false;
}
$length = floor($count/2);
$args1 = array_slice($args[0], 0, $length);
$args2 = array_slice($args[0], $length, ($count-$length));
$res1 = $this->queryIgnoreResult('system.multicall', $args1);
$res2 = $this->queryIgnoreResult('system.multicall', $args2);
return ($res1 && $res2);
}
// If the method is not a multicall, just stop
else {
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
return false;
}
}
// Send request
$ok = $this->sendRequest($request);
if (!$ok) {
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
return false;
}
return true;
}
function readCB($timeout = 2000) { // timeout 2 ms
if (!$this->socket || $this->protocol == 0) {
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
return false;
}
if ($this->protocol == 1)
return false;
$something_received = count($this->cb_message)>0;
$contents = '';
$contents_length = 0;
@stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (to read available data)
// (assignment in arguments is forbidden since php 5.1.1)
$read = array($this->socket);
$write = NULL;
$except = NULL;
$nb = @stream_select($read, $write, $except, 0, $timeout);
// workaround for stream_select bug with amd64
if ($nb !== false)
$nb = count($read);
while ($nb !== false && $nb > 0) {
$timeout = 0; // we don't want to wait for the full time again, just flush the available data
$size = 0;
$recvhandle = 0;
// Get result
$contents = fread($this->socket, 8);
if (strlen($contents) == 0) {
$this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
return false;
}
$array_result = big_endian_unpack('Vsize/Vhandle', $contents);
$size = $array_result['size'];
$recvhandle = $array_result['handle'];
if ($recvhandle == 0 || $size == 0) {
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
return false;
}
if ($size > 4096*1024) {
$this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
return false;
}
$contents = '';
$contents_length = 0;
while ($contents_length < $size) {
$contents .= fread($this->socket, $size-$contents_length);
$contents_length = strlen($contents);
}
if (($recvhandle & 0x80000000) == 0) {
// this is a callback. handle= $recvhandle, xml-rpc= $contents
//echo 'CALLBACK('.$contents_length.')[ '.$contents.' ]' . LF;
$new_cb_message = new IXR_Message($contents);
if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
}
$something_received = true;
}
// (assignment in arguments is forbidden since php 5.1.1)
$read = array($this->socket);
$write = NULL;
$except = NULL;
$nb = @stream_select($read, $write, $except, 0, $timeout);
// workaround for stream_select bug with amd64
if ($nb !== false)
$nb = count($read);
}
return $something_received;
}
function getResponse() {
// methodResponses can only have one param - return that
return $this->message->params[0];
}
function getCBResponses() {
// (look at the end of basic.php for an example)
$messages = $this->cb_message;
$this->cb_message = array();
return $messages;
}
function isError() {
return is_object($this->error);
}
function resetError() {
$this->error = false;
}
function getErrorCode() {
if ($this->isError())
return $this->error->code;
else
return 0;
}
function getErrorMessage() {
if ($this->isError())
return $this->error->message;
else
return '';
}
}
class IXR_ClientMulticall_Gbx extends IXR_Client_Gbx {
public $calls = array();
function addCall($methodName, $args) {
$struct = array('methodName' => $methodName, 'params' => $args);
$this->calls[] = $struct;
return (count($this->calls) - 1);
}
function multiquery($ignoreResult = false) {
// Prepare multicall, then call the parent::query() (or queryIgnoreResult) method
if ($ignoreResult) {
$result = parent::queryIgnoreResult('system.multicall', $this->calls);
} else {
$result = parent::query('system.multicall', $this->calls);
}
$this->calls = array(); // reset for next calls
return $result;
}
}
/**
* The following code is a workaround for php's unpack function which
* does not have the capability of unpacking double precision floats
* that were packed in the opposite byte order of the current machine.
*/
function big_endian_unpack($format, $data) {
$ar = unpack($format, $data);
$vals = array_values($ar);
$f = explode('/', $format);
$i = 0;
foreach ($f as $f_k => $f_v) {
$repeater = intval(substr($f_v, 1));
if ($repeater == 0)
$repeater = 1;
if ($f_v{1} == '*') {
$repeater = count($ar) - $i;
}
if ($f_v{0} != 'd') {
$i += $repeater;
continue;
}
$j = $i + $repeater;
for ($a = $i; $a < $j; ++$a) {
$p = pack('d', $vals[$i]);
$p = strrev($p);
list($vals[$i]) = array_values(unpack('d1d', $p));
++$i;
}
}
$a = 0;
foreach ($ar as $ar_k => $ar_v) {
$ar[$ar_k] = $vals[$a];
++$a;
}
return $ar;
}
?>

View File

@ -0,0 +1,855 @@
<?php
/* vim: set noexpandtab tabstop=2 softtabstop=2 shiftwidth=2: */
/*
IXR - The Incutio XML-RPC Library - (c) Incutio Ltd 2002
Version 1.61 - Simon Willison, 11th July 2003 (htmlentities -> htmlspecialchars)
Site: http://scripts.incutio.com/xmlrpc/
Manual: http://scripts.incutio.com/xmlrpc/manual.php
Errors: http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
Made available under the Artistic License: http://www.opensource.org/licenses/artistic-license.php
Modified to support protocol 'GbxRemote 2' ('GbxRemote 1')
This version is for LittleEndian (e.g. Intel PC) machines. For BegEndian
machines use GbxRemote.bem.php as GbxRemote.inc.php instead.
Release 2007-09-22 - Slig:
Modified to support >256KB received data (and now >2MB data produce a specific error message)
Modified readCB() to wait the initial timeout only before first read packet
Modified readCB() to return true if there is data to get with getCBResponses()
Modified to support amd64 (for $recvhandle)
Modified IXR_ClientMulticall_Gbx::addCall() to fit Aseco 0.6.1
Added IXR_Client_Gbx::bytes_sent & bytes_received counters
Fix for a changed feature since php5.1.1 about reference parameter assignment (was used in stream_select)
Workaround for stream_select return value bug with amd64
Release 2008-01-20 - Slig / Xymph / Assembler Maniac:
Workaround for fread delay bug in some cases
Added IXR_Client_Gbx::resetError() method (by Xymph)
Some comments and strings code cleanup (by Xymph)
Fix stream_set_timeout($this->socket,...) (thx to CavalierDeVache)
Added a default timeout value to IXR_Client_Gbx::readCB($timeout)
Changed calls with timeout on a stream to use microseconds instead of seconds (by AM)
Removed IXR_Client_Gbx::bytes_sent & bytes_received counters - not used (by AM)
Release 2008-02-05 - Slig:
Changed some socket read/write timeouts back to seconds to avoid 'transport error'
Changed max data received from 2MB to 4MB
Release 2008-05-20 - Xymph:
Prevented unpack() warnings in IXR_Client_Gbx::query() when the connection dies
Changed IXR_Client_Gbx::resetError() to assign 'false' for correct isError()
Tweaked some 'transport error' messages
Release 2009-04-08 - Gou1:
Added method IXR_Client_Gbx::queryIgnoreResult()
Added methods IXR_Client_Gbx::sendRequest() & IXR_Client_Gbx::getResult()
IXR_Client_Gbx::queryIgnoreResult checks if the request is larger than 512KB to avoid errors
If larger than 512KB and method is system.multicall, try to divide the request into
two separate requests with two separate IXR_Client_Gbx::queryIgnoreResult() calls
Release 2009-06-03 - Xymph:
Suppress possible repetitive CRT warning at stream_select
Release 2011-04-09 - Xymph / La beuze:
Added optional timeout mechanism to IXR_Client_Gbx::InitWithIp()
Release 2011-05-22 - Xymph:
Added non-error (true) return status to IXR_Client_Gbx::queryIgnoreResult()
Updated status codes and messages for transport/endian errors
Prevented possible PHP warning in IXR_Client_Gbx::getErrorCode() and getErrorMessage()
*/
if (!defined('LF')) {
define('LF', "\n");
}
class IXR_Value {
public $data;
public $type;
function IXR_Value ($data, $type = false) {
$this->data = $data;
if (!$type) {
$type = $this->calculateType();
}
$this->type = $type;
if ($type == 'struct') {
// Turn all the values in the array into new IXR_Value objects
foreach ($this->data as $key => $value) {
$this->data[$key] = new IXR_Value($value);
}
}
if ($type == 'array') {
for ($i = 0, $j = count($this->data); $i < $j; $i++) {
$this->data[$i] = new IXR_Value($this->data[$i]);
}
}
}
function calculateType() {
if ($this->data === true || $this->data === false) {
return 'boolean';
}
if (is_integer($this->data)) {
return 'int';
}
if (is_double($this->data)) {
return 'double';
}
// Deal with IXR object types base64 and date
if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {
return 'date';
}
if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {
return 'base64';
}
// If it is a normal PHP object convert it into a struct
if (is_object($this->data)) {
$this->data = get_object_vars($this->data);
return 'struct';
}
if (!is_array($this->data)) {
return 'string';
}
// We have an array - is it an array or a struct?
if ($this->isStruct($this->data)) {
return 'struct';
} else {
return 'array';
}
}
function getXml() {
// Return XML for this value
switch ($this->type) {
case 'boolean':
return '<boolean>' . ($this->data ? '1' : '0') . '</boolean>';
break;
case 'int':
return '<int>' . $this->data . '</int>';
break;
case 'double':
return '<double>' . $this->data . '</double>';
break;
case 'string':
return '<string>' . htmlspecialchars($this->data) . '</string>';
break;
case 'array':
$return = '<array><data>' . LF;
foreach ($this->data as $item) {
$return .= ' <value>' . $item->getXml() . '</value>' . LF;
}
$return .= '</data></array>';
return $return;
break;
case 'struct':
$return = '<struct>' . LF;
foreach ($this->data as $name => $value) {
$return .= ' <member><name>' . $name . '</name><value>';
$return .= $value->getXml() . '</value></member>' . LF;
}
$return .= '</struct>';
return $return;
break;
case 'date':
case 'base64':
return $this->data->getXml();
break;
}
return false;
}
function isStruct($array) {
// Nasty function to check if an array is a struct or not
$expected = 0;
foreach ($array as $key => $value) {
if ((string)$key != (string)$expected) {
return true;
}
$expected++;
}
return false;
}
}
class IXR_Message {
public $message;
public $messageType; // methodCall / methodResponse / fault
public $faultCode;
public $faultString;
public $methodName;
public $params;
// Current variable stacks
protected $_arraystructs = array(); // Stack to keep track of the current array/struct
protected $_arraystructstypes = array(); // Stack to keep track of whether things are structs or array
protected $_currentStructName = array(); // A stack as well
protected $_param;
protected $_value;
protected $_currentTag;
protected $_currentTagContents;
// The XML parser
protected $_parser;
function IXR_Message ($message) {
$this->message = $message;
}
function parse() {
// first remove the XML declaration
$this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);
if (trim($this->message) == '') {
return false;
}
$this->_parser = xml_parser_create();
// Set XML parser to take the case of tags into account
xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);
// Set XML parser callback functions
xml_set_object($this->_parser, $this);
xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');
xml_set_character_data_handler($this->_parser, 'cdata');
if (!xml_parse($this->_parser, $this->message)) {
/* die(sprintf('GbxRemote XML error: %s at line %d',
xml_error_string(xml_get_error_code($this->_parser)),
xml_get_current_line_number($this->_parser))); */
return false;
}
xml_parser_free($this->_parser);
// Grab the error messages, if any
if ($this->messageType == 'fault') {
$this->faultCode = $this->params[0]['faultCode'];
$this->faultString = $this->params[0]['faultString'];
}
return true;
}
function tag_open($parser, $tag, $attr) {
$this->currentTag = $tag;
switch ($tag) {
case 'methodCall':
case 'methodResponse':
case 'fault':
$this->messageType = $tag;
break;
// Deal with stacks of arrays and structs
case 'data': // data is to all intents and purposes more interesting than array
$this->_arraystructstypes[] = 'array';
$this->_arraystructs[] = array();
break;
case 'struct':
$this->_arraystructstypes[] = 'struct';
$this->_arraystructs[] = array();
break;
}
}
function cdata($parser, $cdata) {
$this->_currentTagContents .= $cdata;
}
function tag_close($parser, $tag) {
$valueFlag = false;
switch ($tag) {
case 'int':
case 'i4':
$value = (int)trim($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'double':
$value = (double)trim($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'string':
$value = (string)trim($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'dateTime.iso8601':
$value = new IXR_Date(trim($this->_currentTagContents));
// $value = $iso->getTimestamp();
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'value':
// If no type is indicated, the type is string
if (trim($this->_currentTagContents) != '') {
$value = (string)$this->_currentTagContents;
$this->_currentTagContents = '';
$valueFlag = true;
}
break;
case 'boolean':
$value = (boolean)trim($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
case 'base64':
$value = base64_decode($this->_currentTagContents);
$this->_currentTagContents = '';
$valueFlag = true;
break;
// Deal with stacks of arrays and structs
case 'data':
case 'struct':
$value = array_pop($this->_arraystructs);
array_pop($this->_arraystructstypes);
$valueFlag = true;
break;
case 'member':
array_pop($this->_currentStructName);
break;
case 'name':
$this->_currentStructName[] = trim($this->_currentTagContents);
$this->_currentTagContents = '';
break;
case 'methodName':
$this->methodName = trim($this->_currentTagContents);
$this->_currentTagContents = '';
break;
}
if ($valueFlag) {
/*
if (!is_array($value) && !is_object($value)) {
$value = trim($value);
}
*/
if (count($this->_arraystructs) > 0) {
// Add value to struct or array
if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {
// Add to struct
$this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;
} else {
// Add to array
$this->_arraystructs[count($this->_arraystructs)-1][] = $value;
}
} else {
// Just add as a paramater
$this->params[] = $value;
}
}
}
}
class IXR_Request {
public $method;
public $args;
public $xml;
function IXR_Request($method, $args) {
$this->method = $method;
$this->args = $args;
$this->xml = '<?xml version="1.0" encoding="utf-8" ?><methodCall><methodName>' . $this->method . '</methodName><params>';
foreach ($this->args as $arg) {
$this->xml .= '<param><value>';
$v = new IXR_Value($arg);
$this->xml .= $v->getXml();
$this->xml .= '</value></param>' . LF;
}
$this->xml .= '</params></methodCall>';
}
function getLength() {
return strlen($this->xml);
}
function getXml() {
return $this->xml;
}
}
class IXR_Error {
public $code;
public $message;
function IXR_Error($code, $message) {
$this->code = $code;
$this->message = $message;
}
function getXml() {
$xml = <<<EOD
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>{$this->code}</int></value>
</member>
<member>
<name>faultString</name>
<value><string>{$this->message}</string></value>
</member>
</struct>
</value>
</fault>
</methodResponse>
EOD;
return $xml;
}
}
class IXR_Date {
public $year;
public $month;
public $day;
public $hour;
public $minute;
public $second;
function IXR_Date($time) {
// $time can be a PHP timestamp or an ISO one
if (is_numeric($time)) {
$this->parseTimestamp($time);
} else {
$this->parseIso($time);
}
}
function parseTimestamp($timestamp) {
$this->year = date('Y', $timestamp);
$this->month = date('Y', $timestamp);
$this->day = date('Y', $timestamp);
$this->hour = date('H', $timestamp);
$this->minute = date('i', $timestamp);
$this->second = date('s', $timestamp);
}
function parseIso($iso) {
$this->year = substr($iso, 0, 4);
$this->month = substr($iso, 4, 2);
$this->day = substr($iso, 6, 2);
$this->hour = substr($iso, 9, 2);
$this->minute = substr($iso, 12, 2);
$this->second = substr($iso, 15, 2);
}
function getIso() {
return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second;
}
function getXml() {
return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';
}
function getTimestamp() {
return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);
}
}
class IXR_Base64 {
public $data;
function IXR_Base64($data) {
$this->data = $data;
}
function getXml() {
return '<base64>'.base64_encode($this->data).'</base64>';
}
}
//////////////////////////////////////////////////////////
// Nadeo modifications //
// (many thanks to slig for adding callback support) //
//////////////////////////////////////////////////////////
class IXR_Client_Gbx {
public $socket;
public $message = false;
public $cb_message = array();
public $reqhandle;
public $protocol = 0;
// Storage place for an error message
public $error = false;
function bigEndianTest() {
list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
if ($endiantest != 1) {
echo "Machine reports itself as BigEndian, float handling must be altered\r\n";
echo "Overwrite GbxRemote.inc.php with GbxRemote.bem.php\r\n";
die('App Terminated');
return false;
}
return true;
} // bigEndianTest
function IXR_Client_Gbx() {
$this->socket = false;
$this->reqhandle = 0x80000000;
}
function InitWithIp($ip, $port, $timeout = null) {
if (!$this->bigEndianTest()) {
$this->error = new IXR_Error(-31999, 'endian error - script doesn\'t match machine type');
return false;
}
// open connection, with timeout if specified
if (!isset($timeout)) {
$this->socket = @fsockopen($ip, $port, $errno, $errstr);
} else {
$init_time = microtime(true);
$init_timeout = 5; // retry every 5s
while (true) {
$this->socket = @fsockopen($ip, $port, $errno, $errstr, $init_timeout);
if ($this->socket || (microtime(true) - $init_time >= $timeout))
break;
}
}
if (!$this->socket) {
$this->error = new IXR_Error(-32300, "transport error - could not open socket (error: $errno, $errstr)");
return false;
}
// handshake
$array_result = unpack('Vsize', fread($this->socket, 4));
$size = $array_result['size'];
if ($size > 64) {
$this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol header');
return false;
}
$handshake = fread($this->socket, $size);
if ($handshake == 'GBXRemote 1') {
$this->protocol = 1;
} else if ($handshake == 'GBXRemote 2') {
$this->protocol = 2;
} else {
$this->error = new IXR_Error(-32300, 'transport error - wrong lowlevel protocol version');
return false;
}
return true;
}
function Init($port) {
return $this->InitWithIp('localhost', $port);
}
function Terminate() {
if ($this->socket) {
fclose($this->socket);
$this->socket = false;
}
}
protected function sendRequest(IXR_Request $request) {
$xml = $request->getXml();
@stream_set_timeout($this->socket, 20); // timeout 20s (to write the request)
// send request
$this->reqhandle++;
if ($this->protocol == 1) {
$bytes = pack('Va*', strlen($xml), $xml);
} else {
$bytes = pack('VVa*', strlen($xml), $this->reqhandle, $xml);
}
$bytes_to_write = strlen($bytes);
while ($bytes_to_write > 0) {
$r = @fwrite($this->socket, $bytes);
if ($r === false || $r == 0) {
// connection interrupted
return false; // or die?
}
$bytes_to_write -= $r;
if ($bytes_to_write == 0)
break;
$bytes = substr($bytes, $r);
}
return true;
}
protected function getResult() {
$contents = '';
$contents_length = 0;
do {
$size = 0;
$recvhandle = 0;
@stream_set_timeout($this->socket, 20); // timeout 20s (to read the reply header)
// Get result
if ($this->protocol == 1) {
$contents = fread($this->socket, 4);
if (strlen($contents) == 0) {
$this->error = new IXR_Error(-32300, 'transport error - cannot read size');
return false;
}
$array_result = unpack('Vsize', $contents);
$size = $array_result['size'];
$recvhandle = $this->reqhandle;
} else {
$contents = fread($this->socket, 8);
if (strlen($contents) == 0) {
$this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
return false;
}
$array_result = unpack('Vsize/Vhandle', $contents);
$size = $array_result['size'];
$recvhandle = $array_result['handle'];
// -- amd64 support --
$bits = sprintf('%b', $recvhandle);
if (strlen($bits) == 64) {
$recvhandle = bindec(substr($bits, 32));
}
}
if ($recvhandle == 0 || $size == 0) {
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
return false;
}
if ($size > 4096*1024) {
$this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
return false;
}
$contents = '';
$contents_length = 0;
@stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (for successive reads until end)
while ($contents_length < $size) {
$contents .= fread($this->socket, $size-$contents_length);
$contents_length = strlen($contents);
}
if (($recvhandle & 0x80000000) == 0) {
// this is a callback, not our answer! handle= $recvhandle, xml-rpc= $contents
// just add it to the message list for the user to read
$new_cb_message = new IXR_Message($contents);
if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
}
}
} while ((int)$recvhandle != (int)$this->reqhandle);
$this->message = new IXR_Message($contents);
if (!$this->message->parse()) {
// XML error
$this->error = new IXR_Error(-32700, 'parse error. not well formed');
return false;
}
// Is the message a fault?
if ($this->message->messageType == 'fault') {
$this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);
return false;
}
// Message must be OK
return true;
}
function query() {
$args = func_get_args();
$method = array_shift($args);
if (!$this->socket || $this->protocol == 0) {
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
return false;
}
$request = new IXR_Request($method, $args);
// Check if request is larger than 512 Kbytes
if (($size = $request->getLength()) > 512*1024-8) {
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
return false;
}
// Send request
$ok = $this->sendRequest($request);
if (!$ok) {
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
return false;
}
// Get result
return $this->getResult();
}
// Non-blocking query method: doesn't read the response
function queryIgnoreResult() {
$args = func_get_args();
$method = array_shift($args);
if (!$this->socket || $this->protocol == 0) {
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
return false;
}
$request = new IXR_Request($method, $args);
// Check if the request is greater than 512 Kbytes to avoid errors
// If the method is system.multicall, make two calls (possibly recursively)
if (($size = $request->getLength()) > 512*1024-8) {
if ($method = 'system.multicall' && isset($args[0])) {
$count = count($args[0]);
// If count is 1, query cannot be reduced
if ($count < 2) {
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
return false;
}
$length = floor($count/2);
$args1 = array_slice($args[0], 0, $length);
$args2 = array_slice($args[0], $length, ($count-$length));
$res1 = $this->queryIgnoreResult('system.multicall', $args1);
$res2 = $this->queryIgnoreResult('system.multicall', $args2);
return ($res1 && $res2);
}
// If the method is not a multicall, just stop
else {
$this->error = new IXR_Error(-32300, "transport error - request too large ($size)");
return false;
}
}
// Send request
$ok = $this->sendRequest($request);
if (!$ok) {
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
return false;
}
return true;
}
function readCB($timeout = 2000) { // timeout 2 ms
if (!$this->socket || $this->protocol == 0) {
$this->error = new IXR_Error(-32300, 'transport error - client not initialized');
return false;
}
if ($this->protocol == 1)
return false;
$something_received = count($this->cb_message)>0;
$contents = '';
$contents_length = 0;
@stream_set_timeout($this->socket, 0, 10000); // timeout 10 ms (to read available data)
// (assignment in arguments is forbidden since php 5.1.1)
$read = array($this->socket);
$write = NULL;
$except = NULL;
$nb = @stream_select($read, $write, $except, 0, $timeout);
// workaround for stream_select bug with amd64
if ($nb !== false)
$nb = count($read);
while ($nb !== false && $nb > 0) {
$timeout = 0; // we don't want to wait for the full time again, just flush the available data
$size = 0;
$recvhandle = 0;
// Get result
$contents = fread($this->socket, 8);
if (strlen($contents) == 0) {
$this->error = new IXR_Error(-32300, 'transport error - cannot read size/handle');
return false;
}
$array_result = unpack('Vsize/Vhandle', $contents);
$size = $array_result['size'];
$recvhandle = $array_result['handle'];
if ($recvhandle == 0 || $size == 0) {
$this->error = new IXR_Error(-32300, 'transport error - connection interrupted!');
return false;
}
if ($size > 4096*1024) {
$this->error = new IXR_Error(-32300, "transport error - response too large ($size)");
return false;
}
$contents = '';
$contents_length = 0;
while ($contents_length < $size) {
$contents .= fread($this->socket, $size-$contents_length);
$contents_length = strlen($contents);
}
if (($recvhandle & 0x80000000) == 0) {
// this is a callback. handle= $recvhandle, xml-rpc= $contents
//echo 'CALLBACK('.$contents_length.')[ '.$contents.' ]' . LF;
$new_cb_message = new IXR_Message($contents);
if ($new_cb_message->parse() && $new_cb_message->messageType != 'fault') {
array_push($this->cb_message, array($new_cb_message->methodName, $new_cb_message->params));
}
$something_received = true;
}
// (assignment in arguments is forbidden since php 5.1.1)
$read = array($this->socket);
$write = NULL;
$except = NULL;
$nb = @stream_select($read, $write, $except, 0, $timeout);
// workaround for stream_select bug with amd64
if ($nb !== false)
$nb = count($read);
}
return $something_received;
}
function getResponse() {
// methodResponses can only have one param - return that
return $this->message->params[0];
}
function getCBResponses() {
// (look at the end of basic.php for an example)
$messages = $this->cb_message;
$this->cb_message = array();
return $messages;
}
function isError() {
return is_object($this->error);
}
function resetError() {
$this->error = false;
}
function getErrorCode() {
if ($this->isError())
return $this->error->code;
else
return 0;
}
function getErrorMessage() {
if ($this->isError())
return $this->error->message;
else
return '';
}
}
class IXR_ClientMulticall_Gbx extends IXR_Client_Gbx {
public $calls = array();
function addCall($methodName, $args) {
$struct = array('methodName' => $methodName, 'params' => $args);
$this->calls[] = $struct;
return (count($this->calls) - 1);
}
function multiquery($ignoreResult = false) {
// Prepare multicall, then call the parent::query() (or queryIgnoreResult) method
if ($ignoreResult) {
$result = parent::queryIgnoreResult('system.multicall', $this->calls);
} else {
$result = parent::query('system.multicall', $this->calls);
}
$this->calls = array(); // reset for next calls
return $result;
}
}
?>

View File

@ -0,0 +1,103 @@
<?php
namespace iControl;
/**
* Class handling authentication levels
*
* @author steeffeen
*/
class Authentication {
/**
* Constants
*/
public $RIGHTS_LEVELS = array(-1 => 'none', 0 => 'superadmin', 1 => 'admin', 2 => 'operator', 3 => 'all');
/**
* Private properties
*/
private $iControl = null;
private $config = null;
/**
* Construct authentication manager
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Load config
$this->config = Tools::loadConfig('authentication.iControl.xml');
}
/**
* Check if the player has enough rights
*
* @param string $login
* @param string $defaultRight
* @param string $neededRight
* @return bool
*/
public function checkRight($login, $neededRight) {
$right = $this->getRights($login);
return $this->compareRights($right, $neededRight);
}
/**
* Compare if the rights are enough
*
* @param string $hasRight
* @param string $neededRight
* @return bool
*/
public function compareRights($hasRight, $neededRight) {
if (!in_array($hasRight, $this->RIGHTS_LEVELS) || !in_array($neededRight, $this->RIGHTS_LEVELS)) {
return false;
}
$hasLevel = array_search($hasRight, $this->RIGHTS_LEVELS);
$neededLevel = array_search($neededRight, $this->RIGHTS_LEVELS);
if ($hasLevel > $neededLevel) {
return false;
}
else {
return true;
}
}
/**
* Get rights of the given login
*
* @param string $login
* @param string $defaultRights
* @return string
*/
public function getRights($login, $defaultRight = 'all') {
$groups = $this->config->xpath('//login[text()="' . $login . '"]/..');
if (empty($groups)) return $defaultRight;
$right = $defaultRight;
$rightLevel = array_search($right, $this->RIGHTS_LEVELS);
foreach ($groups as $group) {
$level = array_search($group->getName(), $this->RIGHTS_LEVELS);
if ($level === false) continue;
if ($level < $rightLevel || $rightLevel === false) {
$right = $group->getName();
$rightLevel = $level;
}
}
return $right;
}
/**
* Sends an error message to the login
*
* @param string $login
*/
public function sendNotAllowed($login) {
if (!$this->iControl->chat->sendError('You do not have the required rights to perform this command!', $login)) {
trigger_error("Couldn't send forbidden message to login '" . $login . "'. " . $this->iControl->getClientErrorText());
}
}
}
?>

View File

@ -0,0 +1,191 @@
<?php
namespace iControl;
/**
* Class for handling server callbacks
*
* @author steeffeen
*/
class Callbacks {
/**
* Constants
*/
// iControl callbacks
const CB_IC_1_SECOND = 'iControl.1Second';
const CB_IC_5_SECOND = 'iControl.5Second';
const CB_IC_1_MINUTE = 'iControl.1Minute';
const CB_IC_3_MINUTE = 'iControl.3Minute';
const CB_IC_ONINIT = 'iControl.OnInit';
const CB_IC_CLIENTUPDATED = 'iControl.ClientUpdated';
const CB_IC_BEGINMAP = 'iControl.BeginMap';
const CB_IC_ENDMAP = 'iControl.EndMap';
// ManiaPlanet callbacks
const CB_MP_SERVERSTART = 'ManiaPlanet.ServerStart';
const CB_MP_SERVERSTOP = 'ManiaPlanet.ServerStop';
const CB_MP_BEGINMAP = 'ManiaPlanet.BeginMap';
const CB_MP_BEGINMATCH = 'ManiaPlanet.BeginMatch';
const CB_MP_ENDMATCH = 'ManiaPlanet.EndMatch';
const CB_MP_ENDMAP = 'ManiaPlanet.EndMap';
const CB_MP_MAPLISTMODIFIED = 'ManiaPlanet.MapListModified';
const CB_MP_ECHO = 'ManiaPlanet.Echo';
const CB_MP_BILLUPDATED = 'ManiaPlanet.BillUpdated';
const CB_MP_PLAYERCHAT = 'ManiaPlanet.PlayerChat';
const CB_MP_PLAYERCONNECT = 'ManiaPlanet.PlayerConnect';
const CB_MP_PLAYERDISCONNECT = 'ManiaPlanet.PlayerDisconnect';
const CB_MP_PLAYERMANIALINKPAGEANSWER = 'ManiaPlanet.PlayerManialinkPageAnswer';
const CB_MP_PLAYERINFOCHANGED = 'ManiaPlanet.PlayerInfoChanged';
const CB_MP_PLAYERALLIESCHANGED = 'ManiaPlanet.PlayerAlliesChanged';
const CB_MP_VOTEUPDATED = 'ManiaPlanet.VoteUpdated';
const CB_MP_STATUSCHANGED = 'ManiaPlanet.StatusChanged';
const CB_MP_MODESCRIPTCALLBACK = 'ManiaPlanet.ModeScriptCallback';
const CB_MP_MODESCRIPTCALLBACKARRAY = 'ManiaPlanet.ModeScriptCallbackArray';
const CB_MP_TUNNELDATARECEIVED = 'ManiaPlanet.TunnelDataReceived';
// TrackMania callbacks
const CB_TM_PLAYERCHECKPOINT = 'TrackMania.PlayerCheckpoint';
const CB_TM_PLAYERFINISH = 'TrackMania.PlayerFinish';
const CB_TM_PLAYERINCOHERENCE = 'TrackMania.PlayerIncoherence';
/**
* Private properties
*/
private $iControl = null;
private $callbackHandlers = array();
private $last1Second = -1;
private $last5Second = -1;
private $last1Minute = -1;
private $last3Minute = -1;
/**
* Construct callbacks handler
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Init values
$this->last1Second = time();
$this->last5Second = time();
$this->last1Minute = time();
$this->last3Minute = time();
}
/**
* Perform OnInit callback
*/
public function onInit() {
// On init callback
$this->triggerCallback(self::CB_IC_ONINIT, array(self::CB_IC_ONINIT));
// Simulate begin map
$map = $this->iControl->server->getMap();
if ($map) {
$this->triggerCallback(self::CB_IC_BEGINMAP, array(self::CB_IC_BEGINMAP, array($map)));
}
}
/**
* Handles the given array of callbacks
*/
public function handleCallbacks() {
// Perform iControl callbacks
if ($this->last1Second <= time() - 1) {
$this->last1Second = time();
// 1 second
$this->triggerCallback(self::CB_IC_1_SECOND, array(self::CB_IC_1_SECOND));
if ($this->last5Second <= time() - 5) {
$this->last5Second = time();
// 5 second
$this->triggerCallback(self::CB_IC_5_SECOND, array(self::CB_IC_5_SECOND));
if ($this->last1Minute <= time() - 60) {
$this->last1Minute = time();
// 1 minute
$this->triggerCallback(self::CB_IC_1_MINUTE, array(self::CB_IC_1_MINUTE));
if ($this->last3Minute <= time() - 180) {
$this->last3Minute = time();
// 3 minute
$this->triggerCallback(self::CB_IC_3_MINUTE, array(self::CB_IC_3_MINUTE));
}
}
}
}
// Get server callbacks
if (!$this->iControl->client) return;
$this->iControl->client->resetError();
$this->iControl->client->readCB();
$callbacks = $this->iControl->client->getCBResponses();
if (!is_array($callbacks) || $this->iControl->client->isError()) {
trigger_error("Error reading server callbacks. " . $this->iControl->getClientErrorText());
return;
}
// Handle callbacks
foreach ($callbacks as $index => $callback) {
$callbackName = $callback[0];
switch ($callbackName) {
case self::CB_MP_BEGINMAP:
{
// Map begin
$this->triggerCallback($callbackName, $callback);
$this->triggerCallback(self::CB_IC_BEGINMAP, $callback);
break;
}
case self::CB_MP_ENDMAP:
{
// Map end
$this->triggerCallback($callbackName, $callback);
$this->triggerCallback(self::CB_IC_ENDMAP, $callback);
break;
}
default:
{
$this->triggerCallback($callbackName, $callback);
break;
}
}
}
}
/**
* Trigger a specific callback
*
* @param string $callbackName
* @param mixed $data
*/
public function triggerCallback($callbackName, $data) {
if (!array_key_exists($callbackName, $this->callbackHandlers) || !is_array($this->callbackHandlers[$callbackName])) return;
foreach ($this->callbackHandlers[$callbackName] as $handler) {
call_user_func(array($handler[0], $handler[1]), $data);
}
}
/**
* Add a new callback handler
*/
public function registerCallbackHandler($callback, $handler, $method) {
if (!is_object($handler) || !method_exists($handler, $method)) {
trigger_error("Given handler can't handle callback '" . $callback . "' (no method '" . $method . "')!");
return;
}
if (!array_key_exists($callback, $this->callbackHandlers) || !is_array($this->callbackHandlers[$callback])) {
// Init callback handler array
$this->callbackHandlers[$callback] = array();
}
// Register callback handler
array_push($this->callbackHandlers[$callback], array($handler, $method));
}
}
?>

View File

@ -0,0 +1,85 @@
<?php
namespace iControl;
/**
* Class for chat methods
*
* @author steeffeen
*/
class Chat {
/**
* Private properties
*/
private $iControl = null;
private $config = null;
private $prefix = 'iControl>';
/**
* Construct iControl chat
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Load config
$this->config = Tools::loadConfig('chat.iControl.xml');
}
/**
* Send a chat message to the given login
*
* @param string $login
* @param string $message
* @param bool $prefix
*/
public function sendChat($message, $login = null, $prefix = false) {
if (!$this->iControl->client) return false;
if ($login === null) {
return $this->iControl->client->query('ChatSendServerMessage', ($prefix ? $this->prefix : '') . $message);
}
else {
return $this->iControl->client->query('ChatSendServerMessageToLogin', ($prefix ? $this->prefix : '') . $message, $login);
}
}
/**
* Send an information message to the given login
*
* @param string $login
* @param string $message
* @param bool $prefix
*/
public function sendInformation($message, $login = null, $prefix = false) {
$format = (string) $this->config->messages->information;
return $this->sendChat($format . $message, $login);
}
/**
* Send a success message to the given login
*
* @param string $message
* @param string $login
* @param bool $prefix
*/
public function sendSuccess($message, $login = null, $prefix = false) {
$format = (string) $this->config->messages->success;
return $this->sendChat($format . $message, $login);
}
/**
* Send an error message to the given login
*
* @param string $login
* @param string $message
* @param bool $prefix
*/
public function sendError($message, $login = null, $prefix = false) {
$format = (string) $this->config->messages->error;
return $this->sendChat($format . $message, $login);
}
}
?>

View File

@ -0,0 +1,684 @@
<?php
namespace iControl;
/**
* Class for handling chat commands
*
* @author steeffeen
*/
class Commands {
/**
* Private properties
*/
private $iControl = null;
private $config = null;
private $commandHandlers = array();
private $openBills = array();
private $serverShutdownTime = -1;
private $serverShutdownEmpty = false;
/**
* Construct commands handler
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Load config
$this->config = Tools::loadConfig('commands.iControl.xml');
// Register for callbacks
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_5_SECOND, $this, 'each5Seconds');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_BILLUPDATED, $this, 'handleBillUpdated');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCHAT, $this, 'handleChatCallback');
// Register basic commands
$commands = array('help', 'version', 'shutdown', 'shutdownserver', 'networkstats', 'systeminfo', 'getservername',
'setservername', 'getplanets', 'donate', 'pay', 'kick', 'nextmap', 'restartmap', 'addmap', 'removemap', 'startwarmup',
'stopwarmup');
foreach ($commands as $command) {
$this->registerCommandHandler($command, $this, 'command_' . $command);
}
}
/**
* Register a command handler
*
* @param string $commandName
* @param object $handler
* @param string $method
*/
public function registerCommandHandler($commandName, $handler, $method) {
$command = strtolower($commandName);
if (!is_object($handler) || !method_exists($handler, $method)) {
trigger_error("Given handler can't handle command '" . $command . "' (no method '" . $method . "')!");
return;
}
if (!array_key_exists($command, $this->commandHandlers) || !is_array($this->commandHandlers[$command])) {
// Init handlers array
$this->commandHandlers[$command] = array();
}
// Register command handler
array_push($this->commandHandlers[$command], array($handler, $method));
}
/**
* Handle chat callback
*/
public function handleChatCallback($callback) {
$chat = $callback[1];
// Check for command
if (!$chat[3]) return;
// Check for valid player
if ($chat[0] <= 0 || strlen($chat[1]) <= 0) return;
// Handle command
$command = explode(" ", substr($chat[2], 1));
$command = strtolower($command[0]);
if (!array_key_exists($command, $this->commandHandlers) || !is_array($this->commandHandlers[$command])) {
// No command handler registered
return;
}
// Inform command handlers
foreach ($this->commandHandlers[$command] as $handler) {
call_user_func(array($handler[0], $handler[1]), $callback);
}
}
/**
* Handle bill updated callback
*/
public function handleBillUpdated($callback) {
$bill = $callback[1];
if (!array_key_exists($bill[0], $this->openBills)) return;
$login = $this->openBills[$bill[0]];
switch ($bill[1]) {
case 4:
{
// Payed
$message = 'Success! Thanks.';
$this->iControl->chat->sendSuccess($message, $login);
unset($this->openBills[$bill[0]]);
break;
}
case 5:
{
// Refused
$message = 'Transaction cancelled.';
$this->iControl->chat->sendError($message, $login);
unset($this->openBills[$bill[0]]);
break;
}
case 6:
{
// Error
$this->iControl->chat->sendError($bill[2], $login);
unset($this->openBills[$bill[0]]);
break;
}
}
}
/**
* Retrieve the needed rights level to perform the given command
*
* @param string $commandName
* @param string $defaultLevel
* @return string
*/
private function getRightsLevel($commandName, $defaultLevel) {
$command_rights = $this->config->xpath('//' . strtolower($commandName) . '/..');
if (empty($command_rights)) return $defaultLevel;
$rights = $this->iControl->authentication->RIGHTS_LEVELS;
$highest_level = null;
foreach ($command_rights as $right) {
$levelName = $right->getName();
$levelInt = array_search($levelName, $rights);
if ($levelInt !== false && ($highest_level === null || $highest_level < $levelInt)) {
$highest_level = $levelInt;
}
}
if ($highest_level === null || !array_key_exists($highest_level, $rights)) return $defaultLevel;
return $rights[$highest_level];
}
/**
* Send iControl version
*/
private function command_version($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('version', 'all'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
if (!$this->iControl->chat->sendInformation('This server is using iControl v' . iControl::VERSION . '!', $login)) {
trigger_error("Couldn't send version to '" . $login . "'. " . $this->iControl->getClientErrorText());
}
}
/**
* Send help list
*/
private function command_help($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('help', 'all'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
// TODO: improve help command
// TODO: enable help for specific commands
$list = 'Available commands: ';
$commands = array_keys($this->commandHandlers);
$count = count($commands);
for ($index = 0; $index < $count; $index++) {
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel($commands[$index], 'superadmin'))) {
unset($commands[$index]);
}
}
$count = count($commands);
$index = 0;
foreach ($commands as $command) {
$list .= $command;
if ($index < $count - 1) {
$list .= ', ';
}
$index++;
}
if (!$this->iControl->chat->sendInformation($list, $login)) {
trigger_error("Couldn't send help list to '" . $login . "'. " . $this->iControl->getClientErrorText());
}
}
/**
* Handle getplanets command
*/
private function command_getplanets($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('getplanets', 'admin'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
if (!$this->iControl->client->query('GetServerPlanets')) {
trigger_error("Couldn't retrieve server planets. " . $this->iControl->getClientErrorText());
}
else {
$planets = $this->iControl->client->getResponse();
if (!$this->iControl->chat->sendInformation('This Server has ' . $planets . ' Planets!', $login)) {
trigger_error("Couldn't send server planets to '" . $login . "'. " . $this->iControl->getClientErrorText());
}
}
}
/**
* Handle donate command
*/
private function command_donate($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('donate', 'all'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
$params = explode(' ', $chat[1][2]);
if (count($params) < 2) {
// TODO: send usage information
return;
}
$amount = (int) $params[1];
if (!$amount || $amount <= 0) {
// TODO: send usage information
return;
}
if (count($params) >= 3) {
$receiver = $params[2];
$receiverPlayer = $this->iControl->database->getPlayer($receiver);
$receiverName = ($receiverPlayer ? $receiverPlayer['NickName'] : $receiver);
}
else {
$receiver = '';
$receiverName = $this->iControl->server->getName();
}
$message = 'Donate ' . $amount . ' Planets to $<' . $receiverName . '$>?';
if (!$this->iControl->client->query('SendBill', $login, $amount, $message, $receiver)) {
trigger_error(
"Couldn't create donation of " . $amount . " planets from '" . $login . "' for '" . $receiver . "'. " .
$this->iControl->getClientErrorText());
$this->iControl->chat->sendError("Creating donation failed.", $login);
}
else {
$bill = $this->iControl->client->getResponse();
$this->openBills[$bill] = $login;
}
}
/**
* Handle pay command
*/
private function command_pay($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('pay', 'superadmin'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
$params = explode(' ', $chat[1][2]);
if (count($params) < 2) {
// TODO: send usage information
return;
}
$amount = (int) $params[1];
if (!$amount || $amount <= 0) {
// TODO: send usage information
return;
}
if (count($params) >= 3) {
$receiver = $params[2];
}
else {
$receiver = $login;
}
$message = 'Payout from $<' . $this->iControl->server->getName() . '$>.';
if (!$this->iControl->client->query('Pay', $receiver, $amount, $message)) {
trigger_error(
"Couldn't create payout of" . $amount . " planets by '" . $login . "' for '" . $receiver . "'. " .
$this->iControl->getClientErrorText());
$this->iControl->chat->sendError("Creating payout failed.", $login);
}
else {
$bill = $this->iControl->client->getResponse();
$this->openBills[$bill] = $login;
}
}
/**
* Handle networkstats command
*/
private function command_networkstats($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('networkstats', 'superadmin'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
$networkStats = $this->iControl->server->getNetworkStats();
$message = 'NetworkStats: ' . 'uptime=' . $networkStats['Uptime'] . ', ' . 'nbConn=' . $networkStats['NbrConnection'] . ', ' .
'recvRate=' . $networkStats['RecvNetRate'] . ', ' . 'sendRate=' . $networkStats['SendNetRate'] . ', ' . 'recvTotal=' .
$networkStats['SendNetRate'] . ', ' . 'sentTotal=' . $networkStats['SendNetRate'];
if (!$this->iControl->chat->sendInformation($message, $login)) {
trigger_error("Couldn't send network stats to '" . $login . "'. " . $this->iControl->getClientErrorText());
}
}
/**
* Handle systeminfo command
*/
private function command_systeminfo($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('systeminfo', 'superadmin'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
$systemInfo = $this->iControl->server->getSystemInfo();
$message = 'SystemInfo: ' . 'ip=' . $systemInfo['PublishedIp'] . ', ' . 'port=' . $systemInfo['Port'] . ', ' . 'p2pPort=' .
$systemInfo['P2PPort'] . ', ' . 'title=' . $systemInfo['TitleId'] . ', ' . 'login=' . $systemInfo['ServerLogin'] . ', ';
if (!$this->iControl->chat->sendInformation($message, $login)) {
trigger_error("Couldn't send system info to '" . $login . "'. " . $this->iControl->getClientErrorText());
}
}
/**
* Handle shutdown command
*/
private function command_shutdown($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('shutdown', 'superadmin'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
$this->iControl->quit("iControl shutdown requested by '" . $login . "'");
}
/**
* Handle startwarmup command
*/
private function command_startwarmup($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('startwarmup', 'operator'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
if (!$this->iControl->client->query("SetWarmUp", true)) {
trigger_error("Couldn't start warmup. " . $this->iControl->getClientErrorText());
$player = $this->iControl->database->getPlayer($login);
$this->iControl->chat->sendInformation('$<' . ($player ? $player['NickName'] : $login) . '$> started WarmUp!');
}
}
/**
* Handle stopwarmup command
*/
private function command_stopwarmup($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('stopwarmup', 'operator'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
if (!$this->iControl->client->query("SetWarmUp", false)) {
trigger_error("Couldn't stop warmup. " . $this->iControl->getClientErrorText());
}
else {
$player = $this->iControl->database->getPlayer($login);
$this->iControl->chat->sendInformation('$<' . ($player ? $player['NickName'] : $login) . '$> stopped WarmUp!');
}
}
/**
* Handle server shutdown command
*/
private function command_shutdownserver($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('shutdownserver', 'superadmin'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
// Check for delayed shutdown
$params = explode(' ', $chat[1][2]);
if (count($params) >= 2) {
$param = $params[1];
if ($param == 'empty') {
$this->serverShutdownEmpty = !$this->serverShutdownEmpty;
if ($this->serverShutdownEmpty) {
$this->iControl->chat->sendInformation("The server will shutdown as soon as it's empty!", $login);
}
else {
$this->iControl->chat->sendInformation("Empty-shutdown cancelled!", $login);
}
}
else {
$delay = (int) $param;
if ($delay <= 0) {
// Cancel shutdown
$this->serverShutdownTime = -1;
$this->iControl->chat->sendInformation("Delayed shutdown cancelled!", $login);
}
else {
// Trigger delayed shutdown
$this->serverShutdownTime = time() + $delay * 60.;
$this->iControl->chat->sendInformation("The server will shut down in " . $delay . " minutes!", $login);
}
}
}
else {
$this->shutdownServer($login);
}
}
/**
* Handle kick command
*/
private function command_kick($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('kick', 'operator'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
$params = explode(' ', $chat[1][2], 3);
if (count($params) < 2) {
// TODO: show usage
return;
}
$target = $params[1];
$players = $this->iControl->server->getPlayers();
foreach ($players as $player) {
if ($player['Login'] != $target) continue;
// Kick player
if (isset($params[2])) {
$message = $params[2];
}
else {
$message = "";
}
if (!$this->iControl->client->query('Kick', $target, $message)) {
trigger_error("Couldn't kick player '" . $target . "'! " . $this->iControl->getClientErrorText());
}
return;
}
$this->iControl->chat->sendError("Invalid player login.", $login);
}
/**
* Handle removemap command
*/
private function command_removemap($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('kick', 'operator'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
// TODO: allow params
// Get map name
$map = $this->iControl->server->getMap();
if (!$map) {
$this->iControl->chat->sendError("Couldn't remove map.", $login);
}
else {
$mapName = $map['FileName'];
// Remove map
if (!$this->iControl->client->query('RemoveMap', $mapName)) {
trigger_error("Couldn't remove current map. " . $this->iControl->getClientErrorText());
}
else {
$this->iControl->chat->sendSuccess('Map removed.', $login);
}
}
}
/**
* Handle addmap command
*/
private function command_addmap($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('addmap', 'operator'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
$params = explode(' ', $chat[1][2], 2);
if (count($params) < 2) {
// TODO: show usage
return;
}
// Check if iControl can even write to the maps dir
if (!$this->iControl->client->query('GetMapsDirectory')) {
trigger_error("Couldn't get map directory. " . $this->iControl->getClientErrorText());
$this->iControl->chat->sendError("iControl couldn't retrieve the maps directory.", $login);
return;
}
else {
$mapDir = $this->iControl->client->getResponse();
if (!is_dir($mapDir)) {
trigger_error("iControl doesn't have have access to the maps directory in '" . $mapDir . "'.");
$this->iControl->chat->sendError("iControl doesn't have access to the maps directory.", $login);
return;
}
$dlDir = (string) $this->iControl->config->maps_dir;
// Create mx directory if necessary
if (!is_dir($mapDir . $dlDir) && !mkdir($mapDir . $dlDir)) {
trigger_error("iControl doesn't have to rights to save maps in'" . $mapDir . $dlDir, "'.");
$this->iControl->chat->sendError("iControl doesn't have to rights to save maps.", $login);
return;
}
$mapDir .= $dlDir . '/';
// Download the map
if (is_numeric($params[1])) {
$serverInfo = $this->iControl->server->getSystemInfo();
$title = strtolower(substr($serverInfo['TitleId'], 0, 2));
// Check if map exists
$url = 'http://' . $title . '.mania-exchange.com/api/tracks/get_track_info/id/' . $params[1] . '?format=json';
$mapInfo = Tools::loadFile($url);
if (!$mapInfo || strlen($mapInfo) <= 0) {
// Invalid id
$this->iControl->chat->sendError('Invalid MX-Id!', $login);
return;
}
$mapInfo = json_decode($mapInfo, true);
$url = 'http://' . $title . '.mania-exchange.com/tracks/download/' . $params[1];
$file = Tools::loadFile($url);
if (!$file) {
// Download error
$this->iControl->chat->sendError('Download failed!', $login);
return;
}
// Save map
$fileName = $mapDir . $mapInfo['TrackID'] . '_' . $mapInfo['Name'] . '.Map.Gbx';
if (!file_put_contents($fileName, $file)) {
// Save error
$this->iControl->chat->sendError('Saving map failed!', $login);
return;
}
// Check for valid map
if (!$this->iControl->client->query('CheckMapForCurrentServerParams', $fileName)) {
trigger_error("Couldn't check if map is valid. " . $this->iControl->getClientErrorText());
}
else {
$response = $this->iControl->client->getResponse();
if (!$response) {
// Inalid map type
$this->iControl->chat->sendError("Invalid map type.", $login);
return;
}
}
// Add map to map list
if (!$this->iControl->client->query('InsertMap', $fileName)) {
$this->iControl->chat->sendError("Couldn't add map to match settings!", $login);
return;
}
$this->iControl->chat->sendSuccess('Map $<' . $mapInfo['Name'] . '$> successfully added!');
}
else {
// TODO: check if map exists locally
// TODO: load map from direct url
}
}
}
/**
* Handle nextmap command
*/
private function command_nextmap($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('nextmap', 'operator'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
if (!$this->iControl->client->query('NextMap')) {
trigger_error("Couldn't skip map. " . $this->iControl->getClientErrorText());
}
}
/**
* Handle retartmap command
*/
private function command_restartmap($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('restartmap', 'operator'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
if (!$this->iControl->client->query('RestartMap')) {
trigger_error("Couldn't restart map. " . $this->iControl->getClientErrorText());
}
}
/**
* Handle getservername command
*/
private function command_getservername($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('getservername', 'operator'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
$serverName = $this->iControl->server->getName();
$this->iControl->chat->sendInformation("Server Name: " . $serverName, $login);
}
/**
* Handle setservername command
*/
private function command_setservername($chat) {
$login = $chat[1][1];
if (!$this->iControl->authentication->checkRight($login, $this->getRightsLevel('setservername', 'admin'))) {
// Not allowed!
$this->iControl->authentication->sendNotAllowed($login);
return;
}
$params = explode(' ', $chat[1][2], 2);
if (count($params) < 2) {
// TODO: show usage
return;
}
$serverName = $params[1];
if (!$this->iControl->client->query('SetServerName', $serverName)) {
trigger_error("Couldn't set server name. " . $this->iControl->getClientErrorText());
$this->iControl->chat->sendError("Error!");
}
else {
$serverName = $this->iControl->server->getName();
$this->iControl->chat->sendInformation("New Name: " . $serverName);
}
}
/**
* Check stuff each 5 seconds
*/
public function each5Seconds() {
// Empty shutdown
if ($this->serverShutdownEmpty) {
$players = $this->iControl->server->getPlayers();
if (count($players) <= 0) {
$this->shutdownServer('empty');
}
}
// Delayed shutdown
if ($this->serverShutdownTime > 0) {
if (time() >= $this->serverShutdownTime) {
$this->shutdownServer('delayed');
}
}
}
/**
* Perform server shutdown
*/
private function shutdownServer($login = '#') {
$this->iControl->client->resetError();
if (!$this->iControl->client->query('StopServer') || $this->iControl->client->isError()) {
trigger_error("Server shutdown command from '" . $login . "' failed. " . $this->iControl->getClientErrorText());
return;
}
$this->iControl->quit("Server shutdown requested by '" . $login . "'");
}
}
?>

View File

@ -0,0 +1,348 @@
<?php
namespace iControl;
/**
* Needed includes
*/
require_once __DIR__ . '/authentication.iControl.php';
require_once __DIR__ . '/callbacks.iControl.php';
require_once __DIR__ . '/chat.iControl.php';
require_once __DIR__ . '/commands.iControl.php';
require_once __DIR__ . '/database.iControl.php';
require_once __DIR__ . '/server.iControl.php';
require_once __DIR__ . '/stats.iControl.php';
require_once __DIR__ . '/tools.iControl.php';
list($endiantest) = array_values(unpack('L1L', pack('V', 1)));
if ($endiantest == 1) {
require_once __DIR__ . '/PhpRemote/GbxRemote.inc.php';
}
else {
require_once __DIR__ . '/PhpRemote/GbxRemote.bem.php';
}
/**
* iControl Server Controller for ManiaPlanet Server
*
* @author steeffeen
*/
class iControl {
/**
* Constants
*/
const VERSION = '0.1';
const API_VERSION = '2013-04-16';
const DATE = 'd-m-y h:i:sa T';
/**
* Public properties
*/
public $authentication = null;
public $callbacks = null;
public $client = null;
public $chat = null;
public $config = null;
public $commands = null;
public $database = null;
public $debug = false;
public $server = null;
public $startTime = -1;
public $stats = null;
/**
* Private properties
*/
private $plugins = array();
private $shutdownRequested = false;
/**
* Construct iControl
*/
public function __construct() {
// Load core
$this->config = Tools::loadConfig('core.iControl.xml');
$this->startTime = time();
// Load chat tool
$this->chat = new Chat($this);
// Load callbacks handler
$this->callbacks = new Callbacks($this);
// Load database
$this->database = new Database($this);
// Load server
$this->server = new Server($this);
// Load authentication
$this->authentication = new Authentication($this);
// Load commands handler
$this->commands = new Commands($this);
// Load stats manager
$this->stats = new Stats($this);
// Register for core callbacks
$this->callbacks->registerCallbackHandler(Callbacks::CB_MP_ENDMAP, $this, 'handleEndMap');
}
/**
* Return message composed of client error message and error code
*
* @param object $client
* @return string
*/
public function getClientErrorText($client = null) {
if (is_object($client)) {
return $client->getErrorMessage() . ' (' . $client->getErrorCode() . ')';
}
return $this->client->getErrorMessage() . ' (' . $this->client->getErrorCode() . ')';
}
/**
* Quit iControl and log the given message
*/
public function quit($message = false) {
if ($this->shutdownRequested) return;
if ($this->client) {
// Announce quit
$this->chat->sendInformation('iControl shutting down.');
// Hide manialinks
$this->client->query('SendHideManialinkPage');
}
// Log quit reason
if ($message) {
error_log($message);
}
// Shutdown
if ($this->client) $this->client->Terminate();
error_log("Quitting iControl!");
exit();
}
/**
* Run iControl
*/
public function run($debug = false) {
error_log('Starting iControl v' . self::VERSION . '!');
$this->debug = (bool) $debug;
// Load plugins
$this->loadPlugins();
// Connect to server
$this->connect();
// Loading finished
error_log("Loading completed!");
// Announce iControl
if (!$this->chat->sendInformation('iControl v' . self::VERSION . ' successfully started!')) {
trigger_error("Couldn't announce iControl. " . $this->iControl->getClientErrorText());
}
// OnInit
$this->callbacks->onInit();
// Main loop
while (!$this->shutdownRequested) {
$loopStart = microtime(true);
// Disable script timeout
set_time_limit(30);
// Handle server callbacks
$this->callbacks->handleCallbacks();
// Loop plugins
foreach ($this->plugins as $plugin) {
if (!method_exists($plugin, 'loop')) {
continue;
}
$plugin->loop();
}
// Yield for next tick
$loopEnd = microtime(true);
$sleepTime = 300000 - $loopEnd + $loopStart;
if ($sleepTime > 0) {
usleep($sleepTime);
}
}
// Shutdown
$this->client->Terminate();
}
/**
* Connect to ManiaPlanet server
*/
private function connect() {
$enable = $this->server->config->xpath('enable');
$enable = Tools::toBool($enable[0]);
if (!$enable) return;
// Load remote client
$this->client = new \IXR_ClientMulticall_Gbx();
$host = $this->server->config->xpath('host');
if (!$host) trigger_error("Invalid server configuration (host).", E_USER_ERROR);
$host = (string) $host[0];
$port = $this->server->config->xpath('port');
if (!$host) trigger_error("Invalid server configuration (port).", E_USER_ERROR);
$port = (string) $port[0];
$timeout = $this->config->xpath('timeout');
if (!$timeout) trigger_error("Invalid core configuration (timeout).", E_USER_ERROR);
$timeout = (int) $timeout[0];
error_log("Connecting to server at " . $host . ":" . $port . "...");
// Connect
if (!$this->client->InitWithIp($host, $port, $timeout)) {
trigger_error(
"Couldn't connect to server! " . $this->client->getErrorMessage() . "(" . $this->client->getErrorCode() . ")",
E_USER_ERROR);
}
$login = $this->server->config->xpath('login');
if (!$login) trigger_error("Invalid server configuration (login).", E_USER_ERROR);
$login = (string) $login[0];
$pass = $this->server->config->xpath('pass');
if (!$pass) trigger_error("Invalid server configuration (password).", E_USER_ERROR);
$pass = (string) $pass[0];
// Authenticate
if (!$this->client->query('Authenticate', $login, $pass)) {
trigger_error(
"Couldn't authenticate on server with user '" . $login . "'! " . $this->client->getErrorMessage() . "(" .
$this->client->getErrorCode() . ")", E_USER_ERROR);
}
// Enable callback system
if (!$this->client->query('EnableCallbacks', true)) {
trigger_error("Couldn't enable callbacks! " . $this->client->getErrorMessage() . "(" . $this->client->getErrorCode() . ")",
E_USER_ERROR);
}
// Wait for server to be ready
if (!$this->server->waitForStatus($this->client, 4)) {
trigger_error("Server couldn't get ready!", E_USER_ERROR);
}
// Set api version
if (!$this->client->query('SetApiVersion', self::API_VERSION)) {
trigger_error(
"Couldn't set API version '" . self::API_VERSION . "'! This might cause problems. " .
$this->iControl->getClientErrorText());
}
// Connect finished
error_log("Server connection succesfully established!");
// Enable service announces
if (!$this->client->query("DisableServiceAnnounces", false)) {
trigger_error("Couldn't enable service announces. " . $this->iControl->getClientErrorText());
}
// Enable script callbacks if needed
if ($this->server->getGameMode() === 0) {
if (!$this->client->query('GetModeScriptSettings')) {
trigger_error("Couldn't get mode script settings. " . $this->iControl->getClientErrorText());
}
else {
$scriptSettings = $this->client->getResponse();
if (array_key_exists('S_UseScriptCallbacks', $scriptSettings)) {
$scriptSettings['S_UseScriptCallbacks'] = true;
if (!$this->client->query('SetModeScriptSettings', $scriptSettings)) {
trigger_error(
"Couldn't set mode script settings to enable script callbacks. " . $this->iControl->getClientErrorText());
}
else {
error_log("Script callbacks successfully enabled.");
}
}
}
}
}
/**
* Load iControl plugins
*/
private function loadPlugins() {
$pluginsConfig = Tools::loadConfig('plugins.iControl.xml');
if (!$pluginsConfig || !isset($pluginsConfig->plugin)) {
trigger_error('Invalid plugins config.');
return;
}
// Load plugin classes
$classes = get_declared_classes();
foreach ($pluginsConfig->xpath('plugin') as $plugin) {
$fileName = ICONTROL . '/plugins/' . $plugin;
if (!file_exists($fileName)) {
trigger_error("Couldn't load plugin '" . $plugin . "'! File doesn't exist. (/plugins/" . $plugin . ")");
}
else {
require_once $fileName;
error_log("Loading plugin: " . $plugin);
}
}
$plugins = array_diff(get_declared_classes(), $classes);
// Create plugins
foreach ($plugins as $plugin) {
$nameIndex = stripos($plugin, 'plugin');
if ($nameIndex === false) continue;
array_push($this->plugins, new $plugin($this));
}
}
/**
* Handle EndMap callback
*/
public function handleEndMap($callback) {
// Autosave match settings
$autosaveMatchsettings = $this->config->xpath('autosave_matchsettings');
if ($autosaveMatchsettings) {
$autosaveMatchsettings = (string) $autosaveMatchsettings[0];
if ($autosaveMatchsettings) {
if (!$this->client->query('SaveMatchSettings', 'MatchSettings/' . $autosaveMatchsettings)) {
trigger_error("Couldn't autosave match settings. " . $this->iControl->getClientErrorText());
}
}
}
}
/**
* Check config settings
*/
public function checkConfig($config, $settings, $name = 'Config XML') {
if (!is_array($settings)) $settings = array($settings);
foreach ($settings as $setting) {
$settingTags = $config->xpath('//' . $setting);
if (empty($settingTags)) {
trigger_error("Missing property '" . $setting . "' in config '" . $name . "'!", E_USER_ERROR);
}
}
}
}
?>

View File

@ -0,0 +1,401 @@
<?php
namespace iControl;
/**
* Class for database connection
*
* @author steeffeen
*/
class Database {
/**
* Constants
*/
const TABLE_PLAYERS = 'ic_players';
const TABLE_MAPS = 'ic_maps';
/**
* Public properties
*/
public $mysqli = null;
/**
* Private properties
*/
private $iControl = null;
private $config = null;
private $multiQueries = '';
/**
* Construct database connection
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Load config
$this->config = Tools::loadConfig('database.iControl.xml');
$this->iControl->checkConfig($this->config, array("host", "user"), 'database.iControl.xml');
// Get mysql server information
$host = $this->config->xpath('host');
if (!$host) trigger_error("Invalid database configuration (host).", E_USER_ERROR);
$host = (string) $host[0];
$port = $this->config->xpath('port');
if (!$port) trigger_error("Invalid database configuration (port).", E_USER_ERROR);
$port = (int) $port[0];
$user = $this->config->xpath('user');
if (!$user) trigger_error("Invalid database configuration (user).", E_USER_ERROR);
$user = (string) $user[0];
$pass = $this->config->xpath('pass');
if (!$pass) trigger_error("Invalid database configuration (pass).", E_USER_ERROR);
$pass = (string) $pass[0];
// Open database connection
$this->mysqli = new \mysqli($host, $user, $pass, null, $port);
if ($this->mysqli->connect_error) {
// Connection error
throw new \Exception(
"Error on connecting to mysql server. " . $this->mysqli->connect_error . " (" . $this->mysqli->connect_errno . ")");
}
// Set charset
$this->mysqli->set_charset("utf8");
// Create/Connect database
$this->initDatabase();
// Init tables
$this->initTables();
// Register for callbacks
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_5_SECOND, $this, 'handle5Second');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_BEGINMAP, $this, 'handleBeginMap');
}
/**
* Destruct database connection
*/
public function __destruct() {
$this->mysqli->close();
}
/**
* Connect to the defined database (create it if needed)
*/
private function initDatabase() {
$dbname = $this->config->xpath('database');
if (!$dbname) trigger_error("Invalid database configuration (database).", E_USER_ERROR);
$dbname = (string) $dbname[0];
// Try to connect
$result = $this->mysqli->select_db($dbname);
if (!$result) {
// Create database
$query = "CREATE DATABASE `" . $this->escape($dbname) . "`;";
$result = $this->mysqli->query($query);
if (!$result) {
trigger_error(
"Couldn't create database '" . $dbname . "'. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')',
E_USER_ERROR);
}
else {
// Connect to database
$result = $this->mysqli->select_db($dbname);
if (!$result) {
trigger_error(
"Couldn't select database '" . $dbname . "'. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')',
E_USER_ERROR);
}
}
}
}
/**
* Create the needed tables
*/
private function initTables() {
$query = "";
// Players table
$query .= "CREATE TABLE IF NOT EXISTS `" . self::TABLE_PLAYERS . "` (
`index` int(11) NOT NULL AUTO_INCREMENT,
`Login` varchar(100) NOT NULL,
`NickName` varchar(250) NOT NULL,
`PlayerId` int(11) NOT NULL,
`LadderRanking` int(11) NOT NULL,
`Flags` varchar(50) NOT NULL,
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`index`),
UNIQUE KEY `Login` (`Login`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Store player metadata' AUTO_INCREMENT=1;";
// Maps table
$query .= "CREATE TABLE IF NOT EXISTS `ic_maps` (
`index` int(11) NOT NULL AUTO_INCREMENT,
`UId` varchar(100) NOT NULL,
`Name` varchar(100) NOT NULL,
`FileName` varchar(200) NOT NULL,
`Author` varchar(150) NOT NULL,
`Environnement` varchar(50) NOT NULL,
`Mood` varchar(50) NOT NULL,
`BronzeTime` int(11) NOT NULL DEFAULT '-1',
`SilverTime` int(11) NOT NULL DEFAULT '-1',
`GoldTime` int(11) NOT NULL DEFAULT '-1',
`AuthorTime` int(11) NOT NULL DEFAULT '-1',
`CopperPrice` int(11) NOT NULL DEFAULT '-1',
`LapRace` tinyint(1) NOT NULL,
`NbLaps` int(11) NOT NULL DEFAULT '-1',
`NbCheckpoints` int(11) NOT NULL DEFAULT '-1',
`MapType` varchar(100) NOT NULL,
`MapStyle` varchar(100) NOT NULL,
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`index`),
UNIQUE KEY `UId` (`UId`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Store map metadata' AUTO_INCREMENT=1;";
// Perform queries
if (!$this->multiQuery($query)) {
trigger_error("Creating basic tables failed. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')', E_USER_ERROR);
}
// Optimize all existing tables
$query = "SHOW TABLES;";
$result = $this->query($query);
if (!$result || !is_object($result)) {
trigger_error("Couldn't select tables. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
}
else {
$query = "OPTIMIZE TABLE ";
$count = $result->num_rows;
$index = 0;
while ($row = $result->fetch_row()) {
$query .= "`" . $row[0] . "`";
if ($index < $count - 1) $query .= ", ";
$index++;
}
$query .= ";";
if (!$this->query($query)) {
trigger_error("Couldn't optimize tables. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
}
}
}
/**
* Wrapper for performing a simple query
*
* @param string $query
* @return mixed query result
*/
public function query($query) {
if (!is_string($query)) return false;
if (strlen($query) <= 0) return true;
return $this->mysqli->query($query);
}
/**
* Perform multi query
*
* @param
* string multi_query
* @return bool whether no error occured during executing the multi query
*/
public function multiQuery($query) {
if (!is_string($query)) return false;
if (strlen($query) <= 0) return true;
$noError = true;
$this->mysqli->multi_query($query);
if ($this->mysqli->error) {
trigger_error("Executing multi query failed. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
$noError = false;
}
while ($this->mysqli->more_results() && $this->mysqli->next_result()) {
if ($this->mysqli->error) {
trigger_error("Executing multi query failed. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
$noError = false;
}
}
return $noError;
}
/**
* Handle 5Second callback
*/
public function handle5Second($callback = null) {
// Save current players in database
$players = $this->iControl->server->getPlayers();
if ($players) {
$query = "";
foreach ($players as $player) {
if (!Tools::isPlayer($player)) continue;
$query .= $this->composeInsertPlayer($player);
}
$this->multiQuery($query);
}
}
/**
* Handle BeginMap callback
*/
public function handleBeginMap($callback) {
$map = $callback[1][0];
$query = $this->composeInsertMap($map);
$result = $this->query($query);
if ($this->mysqli->error) {
trigger_error("Couldn't save map. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
}
}
/**
* Get the player index for the given login
*
* @param string $login
* @return int null
*/
public function getPlayerIndex($login) {
$query = "SELECT `index` FROM `" . self::TABLE_PLAYERS . "` WHERE `Login` = '" . $this->escape($login) . "';";
$result = $this->query($query);
$result = $result->fetch_assoc();
if ($result) {
return $result['index'];
}
return null;
}
/**
* Get the map index for the given UId
*
* @param string $uid
* @return int null
*/
public function getMapIndex($uid) {
$query = "SELECT `index` FROM `" . self::TABLE_MAPS . "` WHERE `UId` = '" . $this->escape($uid) . "';";
$result = $this->query($query);
$result = $result->fetch_assoc();
if ($result) {
return $result['index'];
}
return null;
}
/**
* Compose a query string for inserting the given player
*
* @param array $player
*/
private function composeInsertPlayer($player) {
if (!Tools::isPlayer($player)) return "";
return "INSERT INTO `" . self::TABLE_PLAYERS . "` (
`Login`,
`NickName`,
`PlayerId`,
`LadderRanking`,
`Flags`
) VALUES (
'" . $this->escape($player['Login']) . "',
'" . $this->escape($player['NickName']) . "',
" . $player['PlayerId'] . ",
" . $player['LadderRanking'] . ",
'" . $this->escape($player['Flags']) . "'
) ON DUPLICATE KEY UPDATE
`NickName` = VALUES(`NickName`),
`PlayerId` = VALUES(`PlayerId`),
`LadderRanking` = VALUES(`LadderRanking`),
`Flags` = VALUES(`Flags`);";
}
/**
* Compose a query string for inserting the given map
*
* @param array $map
*/
private function composeInsertMap($map) {
if (!$map) return "";
return "INSERT INTO `" . self::TABLE_MAPS . "` (
`UId`,
`Name`,
`FileName`,
`Author`,
`Environnement`,
`Mood`,
`BronzeTime`,
`SilverTime`,
`GoldTime`,
`AuthorTime`,
`CopperPrice`,
`LapRace`,
`NbLaps`,
`NbCheckpoints`,
`MapType`,
`MapStyle`
) VALUES (
'" . $this->escape($map['UId']) . "',
'" . $this->escape($map['Name']) . "',
'" . $this->escape($map['FileName']) . "',
'" . $this->escape($map['Author']) . "',
'" . $this->escape($map['Environnement']) . "',
'" . $this->escape($map['Mood']) . "',
" . $map['BronzeTime'] . ",
" . $map['SilverTime'] . ",
" . $map['GoldTime'] . ",
" . $map['AuthorTime'] . ",
" . $map['CopperPrice'] . ",
" . Tools::boolToInt($map['LapRace']) . ",
" . $map['NbLaps'] . ",
" . $map['NbCheckpoints'] . ",
'" . $this->escape($map['MapType']) . "',
'" . $this->escape($map['MapStyle']) . "'
) ON DUPLICATE KEY UPDATE
`Name` = VALUES(`Name`),
`FileName` = VALUES(`FileName`),
`Author` = VALUES(`Author`),
`Environnement` = VALUES(`Environnement`),
`Mood` = VALUES(`Mood`),
`BronzeTime` = VALUES(`BronzeTime`),
`SilverTime` = VALUES(`SilverTime`),
`GoldTime` = VALUES(`GoldTime`),
`AuthorTime` = VALUES(`AuthorTime`),
`CopperPrice` = VALUES(`CopperPrice`),
`LapRace` = VALUES(`LapRace`),
`NbLaps` = VALUES(`NbLaps`),
`NbCheckpoints` = VALUES(`NbCheckpoints`),
`MapType` = VALUES(`MapType`),
`MapStyle` = VALUES(`MapStyle`);";
}
/**
* Retrieve all information about the player with the given login
*/
public function getPlayer($login) {
if (!$login) return null;
$query = "SELECT * FROM `" . self::TABLE_PLAYERS . "` WHERE `Login` = '" . $this->escape($login) . "';";
$result = $this->mysqli->query($query);
if ($this->mysqli->error || !$result) {
trigger_error(
"Couldn't select player with login '" . $login . "'. " . $this->mysqli->error . ' (' . $this->mysqli->errno . ')');
return null;
}
else {
while ($player = $result->fetch_assoc()) {
return $player;
}
return null;
}
}
/**
* Escapes the given string for a mysql query
*
* @param string $string
* @return string
*/
public function escape($string) {
return $this->mysqli->escape_string($string);
}
}
?>

View File

@ -0,0 +1,381 @@
<?php
namespace iControl;
/**
* Class providing information and commands for the connected maniaplanet server
*
* @author steeffeen
*/
class Server {
/**
* Constants
*/
const VALIDATIONREPLAYDIR = 'ValidationReplays/';
const GHOSTREPLAYDIR = 'GhostReplays/';
/**
* Public properties
*/
public $config = null;
/**
* Private properties
*/
private $iControl = null;
/**
* Construct server
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Load config
$this->config = Tools::loadConfig('server.iControl.xml');
$this->iControl->checkConfig($this->config, array('host', 'port', 'login', 'pass'), 'server');
// Register for callbacks
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_1_SECOND, $this, 'eachSecond');
}
/**
* Perform actions every second
*/
public function eachSecond() {
// Delete cached information
$this->players = null;
}
/**
* Fetch game directory of the server
*
* @return string
*/
public function getDataDirectory() {
if (!$this->iControl->client->query('GameDataDirectory')) {
trigger_error("Couldn't get data directory. " . $this->iControl->getClientErrorText());
return null;
}
return $this->iControl->client->getResponse();
}
/**
* Checks if iControl has access to the given directory (server data directory if no param)
*
* @param string $directory
* @return bool
*/
public function checkAccess($directory = null) {
if (!$directory) {
$directory = $this->getDataDirectory();
}
return is_dir($directory) && is_writable($directory);
}
/**
* Fetch server login
*/
public function getLogin($client = null) {
$systemInfo = $this->getSystemInfo(false, $client);
if (!$systemInfo) return null;
return $systemInfo['ServerLogin'];
}
/**
* Get detailed server info
*/
public function getInfo($detailed = false) {
if ($detailed) {
$login = $this->getLogin();
if (!$this->iControl->client->query('GetDetailedPlayerInfo', $login)) {
trigger_error("Couldn't fetch detailed server player info. " . $this->iControl->getClientErrorText());
return null;
}
}
else {
if (!$this->iControl->client->query('GetMainServerPlayerInfo')) {
trigger_error("Couldn't fetch server player info. " . $this->iControl->getClientErrorText());
return null;
}
}
return $this->iControl->client->getResponse();
}
/**
* Get server options
*/
public function getOptions() {
if (!$this->iControl->client->query('GetServerOptions')) {
trigger_error("Couldn't fetch server options. " . $this->iControl->getClientErrorText());
return null;
}
return $this->iControl->client->getResponse();
}
/**
* Fetch server name
*/
public function getName() {
if (!$this->iControl->client->query('GetServerName')) {
trigger_error("Couldn't fetch server name. " . $this->iControl->getClientErrorText());
return null;
}
return $this->iControl->client->getResponse();
}
/**
* Fetch server version
*/
public function getVersion($forceRefresh = false) {
if (isset($this->iControl->client->version) && !$forceRefresh) return $this->iControl->client->version;
if (!$this->iControl->client->query('GetVersion')) {
trigger_error("Couldn't fetch server version. " . $this->iControl->getClientErrorText());
return null;
}
else {
$this->iControl->client->version = $this->iControl->client->getResponse();
return $this->iControl->client->version;
}
}
/**
* Fetch server system info
*/
public function getSystemInfo($forceRefresh = false, &$client = null) {
if (!$this->iControl->client && !$client) return null;
if (!$client) $client = $this->iControl->client;
if (isset($client->systemInfo) && !$forceRefresh) return $client->systemInfo;
if (!$client->query('GetSystemInfo')) {
trigger_error("Couldn't fetch server system info. " . $this->iControl->getClientErrorText($client));
return null;
}
else {
$client->systemInfo = $client->getResponse();
return $client->systemInfo;
}
}
/**
* Fetch network status
*/
public function getNetworkStats($forceRefresh = false) {
if (isset($this->iControl->client->networkStats) && !$forceRefresh) return $this->iControl->client->networkStats;
if (!$this->iControl->client->query('GetNetworkStats')) {
trigger_error("Couldn't fetch network stats. " . $this->iControl->getClientErrorText());
return null;
}
else {
$this->iControl->client->networkStats = $this->iControl->client->getResponse();
return $this->iControl->client->networkStats;
}
}
/**
* Fetch current game mode
*
* @param bool $stringValue
* @param int $parseValue
* @return int | string
*/
public function getGameMode($stringValue = false, $parseValue = null) {
if (is_int($parseValue)) {
$gameMode = $parseValue;
}
else {
if (!$this->iControl->client->query('GetGameMode')) {
trigger_error("Couldn't fetch current game mode. " . $this->iControl->getClientErrorText());
return null;
}
$gameMode = $this->iControl->client->getResponse();
}
if ($stringValue) {
switch ($gameMode) {
case 0:
{
return 'Script';
}
case 1:
{
return 'Rounds';
}
case 2:
{
return 'TimeAttack';
}
case 3:
{
return 'Team';
}
case 4:
{
return 'Laps';
}
case 5:
{
return 'Cup';
}
case 6:
{
return 'Stunts';
}
default:
{
return 'Unknown';
}
}
}
return $gameMode;
}
/**
* Fetch player info
*
* @param string $login
* @return struct
*/
public function getPlayer($login, $detailed = false) {
if (!$login) return null;
$command = ($detailed ? 'GetDetailedPlayerInfo' : 'GetPlayerInfo');
if (!$this->iControl->client->query($command, $login)) {
trigger_error("Couldn't player info for '" . $login . "'. " . $this->iControl->getClientErrorText());
return null;
}
return $this->iControl->client->getResponse();
}
/**
* Fetch all players
*/
public function getPlayers(&$client = null, &$purePlayers = null, &$pureSpectators = null) {
if (!$this->iControl->client && !$client) return null;
if (!$client) $client = $this->iControl->client;
$fetchLength = 30;
$offset = 0;
$players = array();
if (!is_array($purePlayers)) $purePlayers = array();
if (!is_array($pureSpectators)) $pureSpectators = array();
$tries = 0;
while ($tries < 10) {
if (!$client->query('GetPlayerList', $fetchLength, $offset)) {
trigger_error("Couldn't get player list. " . $this->iControl->getClientErrorText($client));
$tries++;
}
else {
$chunk = $client->getResponse();
$count = count($chunk);
$serverLogin = $this->getLogin($client);
for ($index = 0; $index < $count; $index++) {
$login = $chunk[$index]['Login'];
if ($login === $serverLogin) {
// Ignore server
unset($chunk[$index]);
}
else {
if ($chunk[$index]['SpectatorStatus'] > 0) {
// Pure spectator
array_push($pureSpectators, $chunk[$index]);
}
else {
// Pure player
array_push($purePlayers, $chunk[$index]);
}
}
}
$players = array_merge($players, $chunk);
$offset += $count;
if ($count < $fetchLength) break;
}
}
return $players;
}
/**
* Retrieve validation replay for given login
*
* @param string $login
* @return string
*/
public function getValidationReplay($login) {
if (!$login) return null;
if (!$this->iControl->client->query('GetValidationReplay', $login)) {
trigger_error("Couldn't get validation replay of '" . $login . "'. " . $this->iControl->getClientErrorText());
return null;
}
return $this->iControl->client->getResponse();
}
public function getGhostReplay($login) {
$dataDir = $this->getDataDirectory();
if (!$this->checkAccess($dataDir)) {
return null;
}
// Build file name
$map = $this->getMap();
$gameMode = $this->getGameMode();
$time = time();
$fileName = 'Ghost.' . $login . '.' . $gameMode . '.' . $time . '.' . $map['UId'] . '.Replay.Gbx';
// Save ghost replay
if (!$this->iControl->client->query('SaveBestGhostsReplay', $login, self::GHOSTREPLAYDIR . $fileName)) {
trigger_error("Couldn't save ghost replay. " . $this->iControl->getClientErrorText());
return null;
}
// Load replay file
$ghostReplay = file_get_contents($dataDir . 'Replays/' . self::GHOSTREPLAYDIR . $fileName);
if (!$ghostReplay) {
trigger_error("Couldn't retrieve saved ghost replay.");
return null;
}
return $ghostReplay;
}
/**
* Fetch current map
*/
public function getMap() {
if (!$this->iControl->client) return null;
if (!$this->iControl->client->query('GetCurrentMapInfo')) {
trigger_error("Couldn't fetch map info. " . $this->iControl->getClientErrorText());
return null;
}
return $this->iControl->client->getResponse();
}
/**
* Waits for the server to have the given status
*/
public function waitForStatus($client, $statusCode = 4) {
$client->query('GetStatus');
$response = $client->getResponse();
// Check if server reached given status
if ($response['Code'] === 4) return true;
// Server not yet in given status -> Wait for it...
$waitBegin = time();
$timeoutTags = $this->iControl->config->xpath('timeout');
$maxWaitTime = (!empty($timeoutTags) ? (int) $timeoutTags[0] : 20);
$lastStatus = $response['Name'];
error_log("Waiting for server to reach status " . $statusCode . "...");
error_log("Current Status: " . $lastStatus);
while ($response['Code'] !== 4) {
sleep(1);
$client->query('GetStatus');
$response = $client->getResponse();
if ($lastStatus !== $response['Name']) {
error_log("New Status: " . $response['Name']);
$lastStatus = $response['Name'];
}
if (time() - $maxWaitTime > $waitBegin) {
// It took too long to reach the status
trigger_error(
"Server couldn't reach status " . $statusCode . " after " . $maxWaitTime . " seconds! " .
$this->iControl->getClientErrorText());
return false;
}
}
return true;
}
}
?>

View File

@ -0,0 +1,297 @@
<?php
namespace iControl;
/**
* Stats class
*
* @author steeffeen
*/
class Stats {
/**
* Constants
*/
const TABLE_STATS_SERVER = 'ic_stats_server';
const TABLE_STATS_PLAYERS = 'ic_stats_players';
/**
* Private properties
*/
private $iControl = null;
private $config = null;
/**
* Constuct stats manager
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Load config
$this->config = Tools::loadConfig('stats.iControl.xml');
$this->loadSettings();
// Init database tables
$this->initTables();
// Register for needed callbacks
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_ENDMAP, $this, 'handleEndMap');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCHAT, $this, 'handlePlayerChat');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCONNECT, $this, 'handlePlayerConnect');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERDISCONNECT, $this, 'handlePlayerDisconnect');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_TM_PLAYERFINISH, $this, 'handlePlayerFinish');
}
/**
* Create the database tables
*/
private function initTables() {
$query = "";
// Server stats
$query .= "CREATE TABLE IF NOT EXISTS `" . self::TABLE_STATS_SERVER . "` (
`index` int(11) NOT NULL AUTO_INCREMENT,
`day` date NOT NULL,
`connectCount` int(11) NOT NULL DEFAULT '0',
`maxPlayerCount` int(11) NOT NULL DEFAULT '0',
`playedMaps` int(11) NOT NULL DEFAULT '0',
`finishCount` int(11) NOT NULL DEFAULT '0',
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`index`),
UNIQUE KEY `day` (`day`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores server stats' AUTO_INCREMENT=1;";
// Player stats
$query .= "CREATE TABLE IF NOT EXISTS `" . self::TABLE_STATS_PLAYERS . "` (
`index` int(11) NOT NULL AUTO_INCREMENT,
`Login` varchar(100) NOT NULL,
`playTime` int(11) NOT NULL DEFAULT '0',
`connectCount` int(11) NOT NULL DEFAULT '0',
`chatCount` int(11) NOT NULL DEFAULT '0',
`finishCount` int(11) NOT NULL DEFAULT '0',
`hitCount` int(11) NOT NULL DEFAULT '0',
`eliminationCount` int(11) NOT NULL DEFAULT '0',
`lastJoin` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`index`),
UNIQUE KEY `Login` (`Login`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Tracks player stats' AUTO_INCREMENT=1;";
// Perform queries
if (!$this->iControl->database->multiQuery($query)) {
trigger_error("Creating stats tables failed.");
}
}
/**
* Load settings from config
*/
private function loadSettings() {
$this->settings = new \stdClass();
$this->settings->track_server_connects = Tools::checkSetting($this->config, 'track_server_connects');
$this->settings->track_server_max_players = Tools::checkSetting($this->config, 'track_server_max_players');
$this->settings->track_server_played_maps = Tools::checkSetting($this->config, 'track_server_played_maps');
$this->settings->track_server_finishes = Tools::checkSetting($this->config, 'track_server_finishes');
$this->settings->track_player_connects = Tools::checkSetting($this->config, 'track_player_connects');
$this->settings->track_player_playtime = Tools::checkSetting($this->config, 'track_player_playtime');
$this->settings->track_player_chats = Tools::checkSetting($this->config, 'track_player_chats');
$this->settings->track_player_finishes = Tools::checkSetting($this->config, 'track_player_finishes');
}
/**
* Handle EndMap callback
*/
public function handleEndMap($callback) {
$multiquery = "";
// Track played server maps
if ($this->settings->track_server_played_maps) {
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` (
`day`,
`playedMaps`
) VALUES (
CURDATE(),
1
) ON DUPLICATE KEY UPDATE
`playedMaps` = `playedMaps` + VALUES(`playedMaps`)
;";
}
// Perform query
if (!$this->iControl->database->multiQuery($multiquery)) {
trigger_error("Perform queries on end map failed.");
}
}
/**
* Handle PlayerChat callback
*/
public function handlePlayerChat($callback) {
if ($callback[1][0] <= 0) return;
$multiquery = "";
$login = $callback[1][1];
// Track chats
if ($this->settings->track_player_chats) {
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` (
`Login`,
`chatCount`
) VALUES (
'" . $this->iControl->database->escape($login) . "',
1
) ON DUPLICATE KEY UPDATE
`chatCount` = `chatCount` + VALUES(`chatCount`)
;";
}
// Perform query
if (!$this->iControl->database->multiQuery($multiquery)) {
trigger_error("Perform queries on player chat failed.");
}
}
/**
* Handle PlayerConnect callback
*/
public function handlePlayerConnect($callback) {
$multiquery = "";
$login = $callback[1][0];
// Track server connect
if ($this->settings->track_server_connects) {
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` (
`day`,
`connectCount`
) VALUES (
CURDATE(),
1
) ON DUPLICATE KEY UPDATE
`connectCount` = `connectCount` + VALUES(`connectCount`)
;";
}
// Track server max players
if ($this->settings->track_server_max_players) {
$players = $this->iControl->server->getPlayers();
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` (
`day`,
`maxPlayerCount`
) VALUES (
CURDATE(),
" . count($players) . "
) ON DUPLICATE KEY UPDATE
`maxPlayerCount` = GREATEST(`maxPlayerCount`, VALUES(`maxPlayerCount`))
;";
}
// Track player connect
if ($this->settings->track_player_connects) {
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` (
`Login`,
`lastJoin`,
`connectCount`
) VALUES (
'" . $this->iControl->database->escape($login) . "',
NOW(),
1
) ON DUPLICATE KEY UPDATE
`lastJoin` = VALUES(`lastJoin`),
`connectCount` = `connectCount` + VALUES(`connectCount`)
;";
}
// Perform query
if (!$this->iControl->database->multiQuery($multiquery)) {
trigger_error("Perform queries on player connect failed.");
}
}
/**
* Handle PlayerDisconnect callback
*/
public function handlePlayerDisconnect($callback) {
$multiquery = "";
$login = $callback[1][0];
// Track player playtime
if ($this->settings->track_player_playtime) {
$query = "SELECT `lastJoin` FROM `" . self::TABLE_STATS_PLAYERS . "`
WHERE `Login` = '" . $this->iControl->database->escape($login) . "'
;";
$result = $this->iControl->database->query($query);
if (!$result) {
// Error
trigger_error("Error selecting player join time from '" . $login . "'.");
}
else {
// Add play time
while ($row = $result->fetch_object()) {
if (!property_exists($row, 'lastJoin')) continue;
$lastJoin = strtotime($row->lastJoin);
$lastJoin = ($lastJoin > $this->iControl->startTime ? $lastJoin : $this->iControl->startTime);
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` (
`Login`,
`playTime`
) VALUES (
'" . $this->iControl->database->escape($login) . "',
TIMESTAMPDIFF(SECOND, '" . Tools::timeToTimestamp($lastJoin) . "', NOW())
) ON DUPLICATE KEY UPDATE
`playTime` = `playTime` + VALUES(`playTime`)
;";
break;
}
}
}
// Perform query
if (!$this->iControl->database->multiQuery($multiquery)) {
trigger_error("Perform queries on player connect failed.");
}
}
/**
* Handle the PlayerFinish callback
*/
public function handlePlayerFinish($callback) {
if ($callback[1][0] <= 0) return;
if ($callback[1][2] <= 0) return;
$multiquery = "";
$login = $callback[1][1];
// Track server finishes
if ($this->settings->track_server_finishes) {
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_SERVER . "` (
`day`,
`finishCount`
) VALUES (
CURDATE(),
1
) ON DUPLICATE KEY UPDATE
`finishCount` = `finishCount` + VALUES(`finishCount`)
;";
}
// Track player finishes
if ($this->settings->track_player_finishes) {
$multiquery .= "INSERT INTO `" . self::TABLE_STATS_PLAYERS . "` (
`Login`,
`finishCount`
) VALUES (
'" . $this->iControl->database->escape($login) . "',
1
) ON DUPLICATE KEY UPDATE
`finishCount` = `finishCount` + VALUES(`finishCount`)
;";
}
// Perform query
if (!$this->iControl->database->multiQuery($multiquery)) {
trigger_error("Perform queries on player finish failed.");
}
}
}
?>

View File

@ -0,0 +1,240 @@
<?php
namespace iControl;
/**
* Class for basic tools
*
* @author steeffeen
*/
class Tools {
/**
* Check if the given setting is enabled
*
* @param simple_xml_element $config
* @param string $setting
*/
public static function checkSetting($config, $setting) {
$settings = $config->xpath('//' . $setting);
if (empty($settings)) {
return false;
}
else {
foreach ($settings as $setting) {
return self::toBool((string) $setting[0]);
}
}
}
/**
* Check if the given data describes a player
*
* @param array $player
* @return bool
*/
public static function isPlayer($player) {
if (!$player || !is_array($player)) return false;
if (!array_key_exists('PlayerId', $player) || !is_int($player['PlayerId']) || $player['PlayerId'] <= 0) return false;
return true;
}
/**
* Convert the given time int to mysql timestamp
*
* @param int $time
* @return string
*/
public static function timeToTimestamp($time) {
return date("Y-m-d H:i:s", $time);
}
/**
* Add alignment attributes to an xml element
*
* @param simple_xml_element $xml
* @param string $halign
* @param string $valign
*/
public static function addAlignment($xml, $halign = 'center', $valign = 'center2') {
if (!is_object($xml) || !method_exists($xml, 'addAttribute')) return;
if (!property_exists($xml, 'halign')) $xml->addAttribute('halign', $halign);
if (!property_exists($xml, 'valign')) $xml->addAttribute('valign', $valign);
}
/**
* Add translate attribute to an xml element
*
* @param simple_xml_element $xml
* @param bool $translate
*/
public static function addTranslate($xml, $translate = true) {
if (!is_object($xml) || !method_exists($xml, 'addAttribute')) return;
if (!property_exists($xml, 'translate')) $xml->addAttribute('translate', ($translate ? 1 : 0));
}
/**
* Load a remote file
*
* @param string $url
* @return string || null
*/
public static function loadFile($url) {
if (!$url) return false;
$urlData = parse_url($url);
$port = (isset($urlData['port']) ? $urlData['port'] : 80);
$fsock = fsockopen($urlData['host'], $port);
stream_set_timeout($fsock, 3);
$query = 'GET ' . $urlData['path'] . ' HTTP/1.0' . PHP_EOL;
$query .= 'Host: ' . $urlData['host'] . PHP_EOL;
$query .= 'Content-Type: UTF-8' . PHP_EOL;
$query .= 'User-Agent: iControl v' . iControl::VERSION . PHP_EOL;
$query .= PHP_EOL;
fwrite($fsock, $query);
$buffer = '';
$info = array('timed_out' => false);
while (!feof($fsock) && !$info['timed_out']) {
$buffer .= fread($fsock, 1024);
$info = stream_get_meta_data($fsock);
}
fclose($fsock);
if ($info['timed_out'] || !$buffer) {
return null;
}
if (substr($buffer, 9, 3) != "200") {
return null;
}
$result = explode("\r\n\r\n", $buffer, 2);
if (count($result) < 2) {
return null;
}
return $result[1];
}
/**
* Formats the given time (milliseconds)
*
* @param int $time
* @return string
*/
public static function formatTime($time) {
if (!is_int($time)) $time = (int) $time;
$milliseconds = $time % 1000;
$seconds = floor($time / 1000);
$minutes = floor($seconds / 60);
$hours = floor($minutes / 60);
$minutes -= $hours * 60;
$seconds -= $hours * 60 + $minutes * 60;
$format = ($hours > 0 ? $hours . ':' : '');
$format .= ($hours > 0 && $minutes < 10 ? '0' : '') . $minutes . ':';
$format .= ($seconds < 10 ? '0' : '') . $seconds . ':';
$format .= ($milliseconds < 100 ? '0' : '') . ($milliseconds < 10 ? '0' : '') . $milliseconds;
return $format;
}
/**
* Convert given data to real boolean
*
* @param
* mixed data
*/
public static function toBool($var) {
if ($var === true) return true;
if ($var === false) return false;
if ($var === null) return false;
if (is_object($var)) {
$var = (string) $var;
}
if (is_int($var)) {
return ($var > 0);
}
else if (is_string($var)) {
$text = strtolower($var);
if ($text === 'true' || $text === 'yes') {
return true;
}
else if ($text === 'false' || $text === 'no') {
return false;
}
else {
return ((int) $text > 0);
}
}
else {
return (bool) $var;
}
}
/**
* Converts the given boolean to an int representation
*
* @param bool $bool
* @return int
*/
public static function boolToInt($bool) {
return ($bool ? 1 : 0);
}
/**
* Build new simple xml element
*
* @param string $name
* @param string $id
* @return \SimpleXMLElement
*/
public static function newManialinkXml($id = null) {
$xml = new \SimpleXMLElement('<?xml version="1.0" encoding="UTF-8" standalone="yes"?><manialink/>');
$xml->addAttribute('version', '1');
if ($id) $xml->addAttribute('id', $id);
return $xml;
}
/**
* Load config xml-file
*
* @param string $fileName
* @return \SimpleXMLElement
*/
public static function loadConfig($fileName) {
// Load config file from configs folder
$fileLocation = ICONTROL . '/configs/' . $fileName;
if (!file_exists($fileLocation)) {
trigger_error("Config file doesn't exist! (" . $fileName . ")", E_USER_ERROR);
}
return simplexml_load_file($fileLocation);
}
/**
* Send the given manialink to players
*
* @param string $manialink
* @param array $logins
*/
public static function sendManialinkPage($client, $manialink, $logins = null, $timeout = 0, $hideOnClick = false) {
if (!$client || !$manialink) return;
if (!$logins) {
// Send manialink to all players
$client->query('SendDisplayManialinkPage', $manialink, $timeout, $hideOnClick);
}
else if (is_array($logins)) {
// Send manialink to players
foreach ($logins as $login) {
$client->query('SendDisplayManialinkPageToLogin', $login, $manialink, $timeout, $hideOnClick);
}
}
else if (is_string($logins)) {
// Send manialink to player
$client->query('SendDisplayManialinkPageToLogin', $logins, $manialink, $timeout, $hideOnClick);
}
}
}
?>

2
application/iControl.bat Normal file
View File

@ -0,0 +1,2 @@
REM set the path to your php.exe here
START "" /B "D:\Programme\xampp\php\php.exe" -f "iControl.php" 2>&1

26
application/iControl.php Normal file
View File

@ -0,0 +1,26 @@
<?php
namespace iControl;
define('ICONTROL', __DIR__);
require_once __DIR__ . '/core/core.iControl.php';
// Set process settings
ini_set('memory_limit', '128M');
if (function_exists('date_default_timezone_get') && function_exists('date_default_timezone_set')) {
date_default_timezone_set(@date_default_timezone_get());
}
// Error handling
ini_set('log_errors', 1);
ini_set('error_reporting', -1);
ini_set('error_log', 'iControl_' . getmypid() . '.log');
// Start iControl
error_log('Loading iControl v' . iControl::VERSION . '!');
$iControl = new iControl();
$iControl->run(true);
?>

3
application/iControl.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/sh
php iControl.php 2>&1 &
echo $! > iControl.pid

View File

@ -0,0 +1,85 @@
<?php
namespace iControl;
/**
* iControl Chatlog Plugin
*
* @author steeffeen
*/
class Plugin_Chatlog {
/**
* Constants
*/
const VERSION = '1.0';
/**
* Private properties
*/
private $iControl = null;
private $config = null;
private $settings = null;
/**
* Constuct chatlog plugin
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Load config
$this->config = Tools::loadConfig('chatlog.plugin.xml');
// Check for enabled setting
if (!Tools::toBool($this->config->enabled)) return;
// Load settings
$this->loadSettings();
// Register for callbacks
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCHAT, $this, 'handlePlayerChatCallback');
error_log('Chatlog Pugin v' . self::VERSION . ' ready!');
}
/**
* Load settings from config
*/
private function loadSettings() {
$this->settings = new \stdClass();
// File name
$fileName = (string) $this->config->filename;
$this->settings->fileName = ICONTROL . '/' . $fileName;
// log_server_messages
$log_server_messages = $this->config->xpath('log_server_messages');
$this->settings->log_server_messages = ($log_server_messages ? (Tools::toBool($log_server_messages[0])) : false);
}
/**
* Handle PlayerChat callback
*/
public function handlePlayerChatCallback($callback) {
$data = $callback[1];
if ($data[0] <= 0 && !$this->settings->log_server_messages) {
// Skip server message
return;
}
$this->logText($data[2], $data[1]);
}
/**
* Log the given message
*
* @param string $message
* @param string $login
*/
private function logText($text, $login = null) {
$message = date(iControl::DATE) . '>> ' . ($login ? $login . ': ' : '') . $text . PHP_EOL;
file_put_contents($this->settings->fileName, $message, FILE_APPEND);
}
}
?>

View File

@ -0,0 +1,305 @@
<?php
namespace iControl;
/**
* iControl Karma Plugin
*
* @author : steeffeen
*/
class Plugin_Karma {
/**
* Constants
*/
const VERSION = '1.0';
const MLID_KARMA = 'KarmaPlugin.MLID';
const TABLE_KARMA = 'ic_karma';
/**
* Private properties
*/
private $iControl = null;
private $config = null;
private $sendManialinkRequested = -1;
/**
* Construct plugin
*
* @param object $iControl
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Load config
$this->config = Tools::loadConfig('karma.plugin.xml');
if (!Tools::toBool($this->config->enabled)) return;
// Init database
$this->initDatabase();
// Register for callbacks
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_ONINIT, $this, 'handleOnInitCallback');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_BEGINMAP, $this, 'handleBeginMapCallback');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCONNECT, $this, 'handlePlayerConnectCallback');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERMANIALINKPAGEANSWER, $this,
'handleManialinkPageAnswerCallback');
error_log('Karma Pugin v' . self::VERSION . ' ready!');
}
/**
* Repetitive actions
*/
public function loop() {
if ($this->sendManialinkRequested > 0 && $this->sendManialinkRequested <= time()) {
$this->sendManialinkRequested = -1;
// Send manialink to all players
$players = $this->iControl->server->getPlayers();
foreach ($players as $player) {
$login = $player['Login'];
$manialink = $this->buildManialink($login);
if (!$manialink) {
// Skip and retry
$this->sendManialinkRequested = time() + 5;
continue;
}
Tools::sendManialinkPage($this->iControl->client, $manialink->asXml(), $login);
}
}
}
/**
* Handle OnInit iControl callback
*
* @param array $callback
*/
public function handleOnInitCallback($callback) {
// Send manialink to all players once
$this->sendManialinkRequested = time() + 3;
}
/**
* Handle iControl BeginMap callback
*
* @param array $callback
*/
public function handleBeginMapCallback($callback) {
// Send manialink to all players once
$this->sendManialinkRequested = time() + 2;
}
/**
* Handle PlayerConnect callback
*
* @param array $callback
*/
public function handlePlayerConnectCallback($callback) {
$login = $callback[1][0];
$manialink = $this->buildManialink($login);
if (!$manialink) return;
Tools::sendManialinkPage($this->iControl->client, $manialink->asXml(), $login);
}
/**
* Create necessary tables
*/
private function initDatabase() {
$query = "CREATE TABLE IF NOT EXISTS `" . self::TABLE_KARMA . "` (
`index` int(11) NOT NULL AUTO_INCREMENT,
`mapIndex` int(11) NOT NULL,
`playerIndex` int(11) NOT NULL,
`vote` tinyint(1) NOT NULL,
`changed` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`index`),
UNIQUE KEY `player_map_vote` (`mapIndex`, `playerIndex`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Save players map votes' AUTO_INCREMENT=1;";
$result = $this->iControl->database->query($query);
if ($this->iControl->database->mysqli->error) {
trigger_error('MySQL Error on creating karma table. ' . $this->iControl->database->mysqli->error, E_USER_ERROR);
}
}
/**
* Handle ManialinkPageAnswer callback
*
* @param array $callback
*/
public function handleManialinkPageAnswerCallback($callback) {
$action = $callback[1][2];
if (substr($action, 0, strlen(self::MLID_KARMA)) !== self::MLID_KARMA) return;
// Get vote
$action = substr($action, -4);
$vote = null;
switch ($action) {
case '.pos':
{
$vote = 1;
break;
}
case '.neu':
{
$vote = 0;
break;
}
case '.neg':
{
$vote = -1;
break;
}
default:
{
return;
}
}
// Save vote
$login = $callback[1][1];
$playerIndex = $this->iControl->database->getPlayerIndex($login);
$map = $this->iControl->server->getMap();
$mapIndex = $this->iControl->database->getMapIndex($map['UId']);
$query = "INSERT INTO `" . self::TABLE_KARMA . "` (
`mapIndex`,
`playerIndex`,
`vote`
) VALUES (
" . $mapIndex . ",
" . $playerIndex . ",
" . $vote . "
) ON DUPLICATE KEY UPDATE
`vote` = VALUES(`vote`);";
$result = $this->iControl->database->query($query);
if (!$result) return;
// Send success message
$this->iControl->chat->sendSuccess('Vote successfully updated!', $login);
// Send updated manialink
$this->sendManialinkRequested = time() + 1;
}
/**
* Build karma voting manialink xml for the given login
*/
private function buildManialink($login) {
// Get config
$title = (string) $this->config->title;
$pos_x = (float) $this->config->pos_x;
$pos_y = (float) $this->config->pos_y;
$mysqli = $this->iControl->database->mysqli;
// Get indezes
$playerIndex = $this->iControl->database->getPlayerIndex($login);
if ($playerIndex === null) return null;
$map = $this->iControl->server->getMap();
if (!$map) return null;
$mapIndex = $this->iControl->database->getMapIndex($map['UId']);
if ($mapIndex === null) return null;
// Get votings
$query = "SELECT
(SELECT `vote` FROM `" .
self::TABLE_KARMA . "` WHERE `mapIndex` = " . $mapIndex . " AND `playerIndex` = " . $playerIndex . ") as `playerVote`,
(SELECT COUNT(`vote`) FROM `" .
self::TABLE_KARMA . "` WHERE `mapIndex` = " . $mapIndex . " AND `vote` = 1) AS `positiveVotes`,
(SELECT COUNT(`vote`) FROM `" .
self::TABLE_KARMA . "` WHERE `mapIndex` = " . $mapIndex . " AND `vote` = 0) AS `neutralVotes`,
(SELECT COUNT(`vote`) FROM `" .
self::TABLE_KARMA . "` WHERE `mapIndex` = " . $mapIndex . " AND `vote` = -1) AS `negativeVotes`
FROM `" . self::TABLE_KARMA . "`;";
$result = $mysqli->query($query);
if ($mysqli->error) {
trigger_error('MySQL ERROR: ' . $mysqli->error);
}
$votes = $result->fetch_assoc();
if (!$votes) {
$votes = array('playerVote' => null, 'positiveVotes' => 0, 'neutralVotes' => 0, 'negativeVotes' => 0);
}
// Build manialink
$xml = Tools::newManialinkXml(self::MLID_KARMA);
$frameXml = $xml->addChild('frame');
$frameXml->addAttribute('posn', $pos_x . ' ' . $pos_y);
// Title
$labelXml = $frameXml->addChild('label');
Tools::addAlignment($labelXml);
$labelXml->addAttribute('posn', '0 4.5 -1');
$labelXml->addAttribute('sizen', '22 0');
$labelXml->addAttribute('style', 'TextTitle1');
$labelXml->addAttribute('textsize', '1');
$labelXml->addAttribute('text', $title);
// Background
$quadXml = $frameXml->addChild('quad');
Tools::addAlignment($quadXml);
$quadXml->addAttribute('sizen', '23 15 -2');
$quadXml->addAttribute('style', 'Bgs1InRace');
$quadXml->addAttribute('substyle', 'BgTitleShadow');
// Votes
for ($i = 1; $i >= -1; $i--) {
$x = $i * 7.;
// Vote button
$quadXml = $frameXml->addChild('quad');
Tools::addAlignment($quadXml);
$quadXml->addAttribute('posn', $x . ' 0 0');
$quadXml->addAttribute('sizen', '6 6');
$quadXml->addAttribute('style', 'Icons64x64_1');
// Vote count
$labelXml = $frameXml->addChild('label');
Tools::addAlignment($labelXml);
$labelXml->addAttribute('posn', $x . ' -4.5 0');
$labelXml->addAttribute('style', 'TextTitle1');
$labelXml->addAttribute('textsize', '2');
if ((string) $i === $votes['playerVote']) {
// Player vote X
$voteQuadXml = $frameXml->addChild('quad');
Tools::addAlignment($voteQuadXml);
$voteQuadXml->addAttribute('posn', $x . ' 0 1');
$voteQuadXml->addAttribute('sizen', '6 6');
$voteQuadXml->addAttribute('style', 'Icons64x64_1');
$voteQuadXml->addAttribute('substyle', 'Close');
}
switch ($i) {
case 1:
{
// Positive
$quadXml->addAttribute('substyle', 'LvlGreen');
$quadXml->addAttribute('action', self::MLID_KARMA . '.pos');
$labelXml->addAttribute('text', $votes['positiveVotes']);
break;
}
case 0:
{
// Neutral
$quadXml->addAttribute('substyle', 'LvlYellow');
$quadXml->addAttribute('action', self::MLID_KARMA . '.neu');
$labelXml->addAttribute('text', $votes['neutralVotes']);
break;
}
case -1:
{
// Negative
$quadXml->addAttribute('substyle', 'LvlRed');
$quadXml->addAttribute('action', self::MLID_KARMA . '.neg');
$labelXml->addAttribute('text', $votes['negativeVotes']);
break;
}
}
}
return $xml;
}
}
?>

View File

@ -0,0 +1,63 @@
<?php
namespace iControl;
/**
* iControl Obstacle Plugin
*
* @author steeffeen
*/
class Plugin_Obstacle extends Plugin {
/**
* Constants
*/
const CB_JUMPTO = 'Obstacle.JumpTo';
const VERSION = '1.0';
/**
* Private properties
*/
private $iControl = null;
private $config = null;
/**
* Constuct obstacle plugin
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Load config
$this->config = Tools::loadConfig('obstacle.plugin.xml');
// Check for enabled setting
if (!Tools::toBool($this->config->enabled)) return;
// Register for jump command
$this->iControl->commands->registerCommandHandler('jumpto', $this, 'command_jumpto');
error_log('Obstacle Pugin v' . self::VERSION . ' ready!');
}
/**
* Handle jumpto command
*/
public function command_jumpto($chat) {
$login = $chat[1][1];
$rightLevel = (string) $this->config->jumps_rightlevel;
if (!$this->iControl->authentication->checkRight($login, $rightLevel)) {
// Not allowed
$this->iControl->authentication->sendNotAllowed($login);
}
else {
// Send jump callback
$params = explode(' ', $chat[1][2], 2);
$param = $login . ";" . $params[1] . ";";
if (!$this->iControl->client->query('TriggerModeScriptEvent', self::CB_JUMPTO, $param)) {
trigger_error("Couldn't send jump callback for '" . $login . "'. " . $this->iControl->getClientErrorText());
}
}
}
}
?>

View File

@ -0,0 +1,37 @@
<?php
namespace iControl;
/**
* Abstract iControl plugin class
*/
abstract class Plugin_Name {
/**
* Constants
*/
const VERSION = '0.1';
/**
* Private properties
*/
private $iControl = null;
/**
* Construct plugin
*
* @param object $iControl
*/
public function __construct($iControl) {
$this->iControl = $iControl;
error_log('Pugin v' . self::VERSION . ' ready!');
}
/**
* Perform actions during each loop
*/
public function loop() {
}
}
?>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,677 @@
<?php
namespace iControl;
// TODO: Jump message "now playing stadium"
// TODO: put inactive server in idle (keeping same map)
// TODO: let next server wait for the first player
// TODO: check compatibility with other modes (laps, ...)
// TODO: max players setting
/**
* iControl United Plugin
*
* @author steeffeen
*/
class Plugin_United {
/**
* Constants
*/
const VERSION = '1.0';
const ML_ADDFAVORITE = 'MLID_UnitedPlugin.AddFavorite';
/**
* Private properties
*/
private $iControl = null;
private $config = null;
private $settings = null;
private $gameServer = array();
private $lobbies = array();
private $currentClientIndex = 0;
private $lastStatusCheck = 0;
private $finishedBegin = -1;
private $switchServerRequested = -1;
private $manialinks = array();
/**
* Constuct plugin
*/
public function __construct($iControl) {
$this->iControl = $iControl;
// Load config
$this->config = Tools::loadConfig('united.plugin.xml');
$this->loadSettings();
// Check for enabled setting
if (!$this->settings->enabled) return;
// Load clients
$this->loadClients();
// Register for callbacks
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_ONINIT, $this, 'handleOnInitCallback');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_IC_5_SECOND, $this, 'handle5Seconds');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERCONNECT, $this, 'handlePlayerConnect');
$this->iControl->callbacks->registerCallbackHandler(Callbacks::CB_MP_PLAYERMANIALINKPAGEANSWER, $this,
'handleManialinkPageAnswer');
// Register for commands
$this->iControl->commands->registerCommandHandler('nextserver', $this, 'handleNextServerCommand');
if ($this->settings->widgets_enabled) {
// Build addfavorite manialink
$this->buildFavoriteManialink();
}
error_log('United Pugin v' . self::VERSION . ' ready!');
}
/**
* Handle iControl OnInit callback
*
* @param array $callback
*/
public function handleOnInitCallback($callback) {
if ($this->settings->widgets_enabled) {
// Send widgets to all players
if (Tools::toBool($this->config->widgets->addfavorite->enabled)) {
// Send favorite widget
if (!$this->iControl->client->query('SendDisplayManialinkPage', $this->manialinks[self::ML_ADDFAVORITE]->asXml(), 0,
false)) {
trigger_error("Couldn't send favorite widget! " . $this->iControl->getClientErrorText());
}
}
}
}
/**
* Load settings from config
*/
private function loadSettings() {
$this->settings = new \stdClass();
// Enabled
$this->settings->enabled = Tools::toBool($this->config->enabled);
// Timeout
$timeout = $this->iControl->server->config->xpath('timeout');
if ($timeout) {
$this->settings->timeout = (int) $timeout[0];
}
else {
$this->settings->timeout = 30;
}
// Game mode
$mode = $this->config->xpath('mode');
if ($mode) {
$mode = (int) $mode[0];
if ($mode < 1 || $mode > 6) {
$this->settings->gamemode = 2;
}
else {
$this->settings->gamemode = $mode;
}
}
// Server status
$hide_game_server = $this->config->xpath('hide_game_server');
if ($hide_game_server) {
$this->settings->hide_game_server = Tools::toBool($hide_game_server[0]);
}
else {
$this->settings->hide_game_server = true;
}
// Passwords
$lobbyPassword = $this->config->xpath('lobbies/password');
if ($lobbyPassword) {
$this->settings->lobbyPassword = (string) $lobbyPassword[0];
}
else {
$this->settings->lobbyPassword = '';
}
$gamePassword = $this->config->xpath('gameserver/password');
if ($gamePassword) {
$this->settings->gamePassword = (string) $gamePassword[0];
}
else {
$this->settings->gamePassword = '';
}
// Widgets
$this->settings->widgets_enabled = Tools::toBool($this->config->widgets->enabled);
}
/**
* Loop events on clients
*/
public function loop() {
if (!$this->settings->enabled) return;
// Check callbacks all clients
$clients = array_merge($this->gameServer, $this->lobbies);
$currentServer = $this->gameServer[$this->currentClientIndex];
foreach ($clients as $index => $client) {
$client->resetError();
$client->readCB();
$callbacks = $client->getCBResponses();
if (!is_array($callbacks) || $client->isError()) {
trigger_error("Error reading server callbacks! " . $this->iControl->getClientErrorText($client));
}
else {
if ($client == $currentServer) {
// Currently active game server
foreach ($callbacks as $index => $callback) {
$callbackName = $callback[0];
switch ($callbackName) {
case Callbacks::CB_MP_ENDMAP:
{
$this->switchToNextServer(false);
break;
}
}
}
if ($this->lastStatusCheck + 2 > time()) continue;
$this->lastStatusCheck = time();
if (!$client->query('CheckEndMatchCondition')) {
trigger_error("Couldn't get game server status. " . $this->iControl->getClientErrorText($client));
}
else {
$response = $client->getResponse();
switch ($response) {
case 'Finished':
{
if ($this->finishedBegin < 0) {
$this->finishedBegin = time();
}
else if ($this->finishedBegin + 13 <= time()) {
$this->switchToNextServer(true);
}
break;
}
default:
{
$this->finishedBegin = -1;
break;
}
}
}
}
else {
// Lobby or inactive game server -> Redirect players
foreach ($callbacks as $callback) {
switch ($callback[0]) {
case Callbacks::CB_MP_PLAYERCONNECT:
{
$this->playerJoinedLobby($client, $callback);
break;
}
}
}
}
}
}
// Check for switch server request
if ($this->switchServerRequested > 0 && $this->switchServerRequested <= time()) {
$this->switchServerRequested = -1;
// Switch server
$this->switchToNextServer(true);
}
}
/**
* Handle 5 seconds callback
*/
public function handle5Seconds($callback = null) {
// Update lobby infos
$players = $this->iControl->server->getPlayers();
if (is_array($players)) {
$playerCount = count($players);
$playerLevel = 0.;
if ($playerCount > 0) {
foreach ($players as $player) {
$playerLevel += $player['LadderRanking'];
}
$playerLevel /= $playerCount;
}
foreach ($this->lobbies as $lobby) {
if (!$lobby->query('SetLobbyInfo', true, $playerCount, 255, $playerLevel)) {
trigger_error("Couldn't update lobby info. " . $this->iControl->getClientErrorText($lobby));
}
}
}
// Check for not-redirected players
$clients = array_merge($this->gameServer, $this->lobbies);
$joinLink = $this->getJoinLink();
foreach ($clients as $client) {
if ($client == $this->gameServer[$this->currentClientIndex]) continue;
$players = $this->iControl->server->getPlayers($client);
if (!is_array($players)) continue;
foreach ($players as $player) {
$login = $player['Login'];
if (!$client->query('SendOpenLinkToLogin', $login, $joinLink, 1)) {
trigger_error(
"Couldn't redirect player '" . $login . "' to active game server. " .
$this->iControl->getClientErrorText($client));
}
}
}
}
/**
* Handle player manialink page answer callback
*/
public function handleManialinkPageAnswer($callback) {
$login = $callback[1][1];
$action = $callback[1][2];
switch ($action) {
case self::ML_ADDFAVORITE:
{
// Open manialink to add server logins to favorite
$serverLogins = array();
$add_all = Tools::toBool($this->config->widgets->addfavorite->add_all);
if ($add_all) {
// Add all server
foreach ($this->gameServer as $serverClient) {
array_push($serverLogins, $this->iControl->server->getLogin($serverClient));
}
foreach ($this->lobbies as $serverClient) {
array_push($serverLogins, $this->iControl->server->getLogin($serverClient));
}
}
else {
// Add only current server
array_push($serverLogins, $this->iControl->server->getLogin());
}
// Build manialink url
$manialink = 'icontrol?favorite';
foreach ($serverLogins as $serverLogin) {
$manialink .= '&' . $serverLogin;
}
// Send url to player
if (!$this->iControl->client->query('SendOpenLinkToLogin', $login, $manialink, 1)) {
trigger_error(
"Couldn't open manialink to add server to favorite for '" . $login . "'! " .
$this->iControl->getClientErrorText());
}
break;
}
}
}
/**
* Switch to the next server
*
* @param bool $simulateMapEnd
* Simulate end of the map by sending callbacks
*/
private function switchToNextServer($simulateMapEnd) {
$this->finishedBegin = -1;
$oldClient = $this->gameServer[$this->currentClientIndex];
$random_order = Tools::toBool($this->config->random_order);
if ($random_order) {
// Random next server
$this->currentClientIndex = rand(0, count($this->gameServer) - 1);
}
else {
// Next server in list
$this->currentClientIndex++;
}
if ($this->currentClientIndex >= count($this->gameServer)) $this->currentClientIndex = 0;
$newClient = $this->gameServer[$this->currentClientIndex];
if ($newClient == $oldClient) return;
// Restart map on next game server
if (!$newClient->query('RestartMap')) {
trigger_error("Couldn't restart map on next game server. " . $this->iControl->getClientErrorText($newClient));
}
if ($simulateMapEnd) {
// Simulate EndMap on old client
$this->iControl->callbacks->triggerCallback(Callbacks::CB_IC_ENDMAP, array(Callbacks::CB_IC_ENDMAP));
}
// Transfer players to next server
$joinLink = $this->getJoinLink($newClient);
if (!$oldClient->query('GetPlayerList', 255, 0)) {
trigger_error("Couldn't get player list. " . $this->iControl->getClientErrorText($oldClient));
}
else {
$playerList = $oldClient->getResponse();
foreach ($playerList as $player) {
$login = $player['Login'];
if (!$oldClient->query('SendOpenLinkToLogin', $login, $joinLink, 1)) {
trigger_error("Couldn't redirect player to next game server. " . $this->iControl->getClientErrorText($oldClient));
}
}
$this->iControl->client = $newClient;
}
// Trigger client updated callback
$this->iControl->callbacks->triggerCallback(Callbacks::CB_IC_CLIENTUPDATED, "Plugin_United.SwitchedServer");
if ($simulateMapEnd) {
// Simulate BeginMap on new client
$map = $this->iControl->server->getMap();
if ($map) {
$this->iControl->callbacks->triggerCallback(Callbacks::CB_IC_BEGINMAP, array(Callbacks::CB_IC_BEGINMAP, array($map)));
}
}
}
/**
* Handle nextserver command
*
* @param mixed $command
*/
public function handleNextServerCommand($command) {
if (!$command) return;
$login = $command[1][1];
if (!$this->iControl->authentication->checkRight($login, 'operator')) {
// Not allowed
$this->iControl->authentication->sendNotAllowed($login);
return;
}
// Request skip to next server
$this->switchServerRequested = time() + 3;
// Send chat message
$this->iControl->chat->sendInformation("Switching to next server in 3 seconds...");
}
/**
* Handle PlayerConnect callback
*/
public function playerJoinedLobby($client, $callback) {
if (!$client) return;
$data = $callback[1];
$login = $data[0];
// Redirect player to current game server
$gameserver = $this->gameServer[$this->currentClientIndex];
$joinLink = $this->getJoinLink($gameserver, !$data[1]);
if (!$client->query('SendOpenLinkToLogin', $login, $joinLink, 1)) {
trigger_error(
"United Plugin: Couldn't redirect player to current game server. " . $this->iControl->getClientErrorText($client));
}
}
/**
* Connect to the game server defined in the config
*/
private function loadClients() {
$gameserver = $this->config->xpath('gameserver/server');
$lobbies = $this->config->xpath('lobbies/server');
$clientsConfig = array_merge($gameserver, $lobbies);
foreach ($clientsConfig as $index => $serv) {
$isGameServer = (in_array($serv, $gameserver));
$host = $serv->xpath('host');
$port = $serv->xpath('port');
if (!$host || !$port) {
trigger_error("Invalid configuration!", E_USER_ERROR);
}
$host = (string) $host[0];
$port = (string) $port[0];
error_log("Connecting to united " . ($isGameServer ? 'game' : 'lobby') . " server at " . $host . ":" . $port . "...");
$client = new \IXR_ClientMulticall_Gbx();
// Connect
if (!$client->InitWithIp($host, $port, $this->settings->timeout)) {
trigger_error(
"Couldn't connect to united " . ($isGameServer ? 'game' : lobby) . " server! " . $client->getErrorMessage() .
"(" . $client->getErrorCode() . ")", E_USER_ERROR);
}
$login = $serv->xpath('login');
$pass = $serv->xpath('pass');
if (!$login || !$pass) {
trigger_error("Invalid configuration!", E_USER_ERROR);
}
$login = (string) $login[0];
$pass = (string) $pass[0];
// Authenticate
if (!$client->query('Authenticate', $login, $pass)) {
trigger_error(
"Couldn't authenticate on united " . ($isGameServer ? 'game' : 'lobby') . " server with user '" . $login . "'! " .
$client->getErrorMessage() . "(" . $client->getErrorCode() . ")", E_USER_ERROR);
}
// Enable callback system
if (!$client->query('EnableCallbacks', true)) {
trigger_error("Couldn't enable callbacks! " . $client->getErrorMessage() . "(" . $client->getErrorCode() . ")",
E_USER_ERROR);
}
// Wait for server to be ready
if (!$this->iControl->server->waitForStatus($client, 4)) {
trigger_error("Server couldn't get ready!", E_USER_ERROR);
}
// Set api version
if (!$client->query('SetApiVersion', iControl::API_VERSION)) {
trigger_error(
"Couldn't set API version '" . iControl::API_VERSION . "'! This might cause problems. " .
$this->iControl->getClientErrorText($client));
}
// Set server settings
$password = ($isGameServer ? $this->settings->gamePassword : $this->settings->lobbyPassword);
$hideServer = ($isGameServer && $this->settings->hide_game_server ? 1 : 0);
// Passwords
if (!$client->query('SetServerPassword', $password)) {
trigger_error("Couldn't set server join password. " . $this->iControl->getClientErrorText($client));
}
if (!$client->query('SetServerPasswordForSpectator', $password)) {
trigger_error("Couldn't set server spec password. " . $this->iControl->getClientErrorText($client));
}
// Show/Hide server
if (!$client->query('SetHideServer', $hideServer)) {
trigger_error(
"Couldn't set server '" . ($hideServer == 0 ? 'shown' : 'hidden') . "'. " .
$this->iControl->getClientErrorText($client));
}
// Enable service announces
if (!$client->query("DisableServiceAnnounces", false)) {
trigger_error("Couldn't enable service announces. " . $this->iControl->getClientErrorText($client));
}
// Set game mode
if (!$client->query('SetGameMode', $this->settings->gamemode)) {
trigger_error(
"Couldn't set game mode (" . $this->settings->gamemode . "). " . $this->iControl->getClientErrorText($client));
}
else if (!$client->query('RestartMap')) {
trigger_error("Couldn't restart map to change game mode. " . $this->iControl->getClientErrorText($client));
}
// Save client
$client->index = $index;
if ($isGameServer) {
array_push($this->gameServer, $client);
if (count($this->gameServer) === 1) {
$this->iControl->client = $client;
}
}
else {
array_push($this->lobbies, $client);
}
}
error_log("United Plugin: Connected to all game and lobby server!");
}
/**
* Handle PlayerConnect callback
*
* @param array $callback
*/
public function handlePlayerConnect($callback) {
if ($this->settings->widgets_enabled) {
// Send manialinks to the client
$login = $callback[1][0];
if (Tools::toBool($this->config->widgets->addfavorite->enabled)) {
// Send favorite widget
if (!$this->iControl->client->query('SendDisplayManialinkPageToLogin', $login,
$this->manialinks[self::ML_ADDFAVORITE]->asXml(), 0, false)) {
trigger_error("Couldn't send favorite widget to player '" . $login . "'! " . $this->iControl->getClientErrorText());
}
}
}
}
/**
* Build join link for the given client
*/
private function getJoinLink(&$client = null, $play = true) {
if (!$client) {
$client = $this->gameServer[$this->currentClientIndex];
}
if (!$client->query('GetSystemInfo')) {
trigger_error("Couldn't fetch server system info. " . $this->iControl->getClientErrorText($client));
return null;
}
else {
$systemInfo = $client->getResponse();
$password = '';
if (!$client->query('GetServerPassword')) {
trigger_error("Couldn't get server password. " . $this->iControl->getClientErrorText($client));
}
else {
$password = $client->getResponse();
}
return '#q' . ($play ? 'join' : 'spectate') . '=' . $systemInfo['ServerLogin'] .
(strlen($password) > 0 ? ':' . $password : '') . '@' . $systemInfo['TitleId'];
}
}
/**
* Build manialink for addfavorite button
*/
private function buildFavoriteManialink() {
// Load configs
$config = $this->config->widgets->addfavorite;
if (!Tools::toBool($config->enabled)) return;
$pos_x = (float) $config->pos_x;
$pos_y = (float) $config->pos_y;
$height = (float) $config->height;
$width = (float) $config->width;
$add_all = Tools::toBool($config->add_all);
// Build manialink
$xml = Tools::newManialinkXml(self::ML_ADDFAVORITE);
$frameXml = $xml->addChild('frame');
$frameXml->addAttribute('posn', $pos_x . ' ' . $pos_y);
// Background
$quadXml = $frameXml->addChild('quad');
Tools::addAlignment($quadXml);
$quadXml->addAttribute('posn', '0 0 0');
$quadXml->addAttribute('sizen', $width . ' ' . $height);
$quadXml->addAttribute('style', 'Bgs1InRace');
$quadXml->addAttribute('substyle', 'BgTitleShadow');
$quadXml->addAttribute('action', self::ML_ADDFAVORITE);
// Heart
$quadXml = $frameXml->addChild('quad');
Tools::addAlignment($quadXml);
$quadXml->addAttribute('id', 'Quad_AddFavorite');
$quadXml->addAttribute('posn', '0 0 1');
$quadXml->addAttribute('sizen', ($width - 1.) . ' ' . ($height - 0.8));
$quadXml->addAttribute('style', 'Icons64x64_1');
$quadXml->addAttribute('substyle', 'StateFavourite');
$quadXml->addAttribute('scriptevents', '1');
// Tooltip
$tooltipFrameXml = $frameXml->addChild('frame');
$tooltipFrameXml->addAttribute('id', 'Frame_FavoriteTooltip');
$tooltipFrameXml->addAttribute('posn', '0 ' . ($pos_y >= 0 ? '-' : '') . '13');
$tooltipFrameXml->addAttribute('hidden', '1');
$quadXml = $tooltipFrameXml->addChild('quad');
Tools::addAlignment($quadXml);
$quadXml->addAttribute('posn', '0 0 2');
$quadXml->addAttribute('sizen', '28 16');
$quadXml->addAttribute('style', 'Bgs1InRace');
$quadXml->addAttribute('substyle', 'BgTitleShadow');
$labelXml = $tooltipFrameXml->addChild('label');
Tools::addAlignment($labelXml);
Tools::addTranslate($labelXml);
$labelXml->addAttribute('posn', '0 0 3');
$labelXml->addAttribute('sizen', '26 0');
$labelXml->addAttribute('style', 'TextTitle1');
$labelXml->addAttribute('textsize', '2');
$labelXml->addAttribute('autonewline', '1');
$countText = '';
if ($add_all) {
$count = count($this->gameServer) + count($this->lobbies);
$countText = 'all ' . $count . ' ';
}
$labelXml->addAttribute('text', 'Add ' . $countText . 'server to Favorite!');
// Script for tooltip
$script = '
declare Frame_FavoriteTooltip <=> (Page.GetFirstChild("Frame_FavoriteTooltip") as CMlFrame);
while (True) {
yield;
foreach (Event in PendingEvents) {
switch (Event.Type) {
case CMlEvent::Type::MouseOver: {
switch (Event.ControlId) {
case "Quad_AddFavorite": {
Frame_FavoriteTooltip.Visible = True;
}
}
}
case CMlEvent::Type::MouseOut: {
switch (Event.ControlId) {
case "Quad_AddFavorite": {
Frame_FavoriteTooltip.Visible = False;
}
}
}
}
}
}';
$xml->addChild('script', $script);
$this->manialinks[self::ML_ADDFAVORITE] = $xml;
}
}
?>

44
application/readme.txt Normal file
View File

@ -0,0 +1,44 @@
**********************************************
* *
* iControl ManiaPlanet Server Control *
* Written by steeffeen *
* Contact: mail@steeffeen.com *
* *
**********************************************
SETUP:
1. Copy all files into your desired directory.
2. Configure the needed settings:
2.1 Open the file 'configs/server.iControl.xml'.
Enter your maniaplanet server information.
2.2 Open the file 'configs/database.iControl.xml'
Enter your mysql server information or disable database usage if you don't have a mysql server available.
2.3 Open the file 'configs/authentication.iControl.xml'.
Add the player logins who should have access to the commands of iControl.
3. (Optional) Enable or disable the available plugins in the file 'configs/plugins.iControl.xml'.
4. (Optional) Edit the other config files in 'configs/' in order to customize your iControl to fit your needs.
5. Run the tool via the shell script 'iControl.sh' (UNIX) or the batch file 'iControl.bat' (Windows)
6. Enjoy!
INFORMATION:
- iControl is only tested on UNIX machines
- even though it might run properly on Windows I can't promise it will work all the time
- furthermore I can't promise that there won't be a feature in the future that makes it impossible to run iControl under Windows
- in order to run iControl under Windows you have to alter the file iControl.bat and enter the path to your php.exe
- Tests were performed using PHP Version 5.4
- If you notice problems with other version please let me know
- Please report bugs by writing a mail to mail@steeffeen.com