PHP Queries

From Pragma
Jump to: navigation, search

You can query a list of all servers from the master server, or ping individually servers to check if they're reachable from a source.

You can use the following wrapper which uses the standard PHP socket library to dispatch queries:

<?php
	$SCI_PROTOCOL_VERSION = 2;
	abstract class SciType
	{
		const UNSIGNED_INT = 4;
		const UNSIGNED_SHORT = 2;
		const UNSIGNED_CHAR = 1;
	};

	abstract class SciRequestFilter
	{
		const NONE = 0; // No Filter; All servers will be retrieved
		const F_OR = 1; // Currently unused
		const F_AND = 2; // Currently unused
		const F_NOT_EMPTY = 4; // Only servers which aren't empty
		const F_NOT_FULL = 8; // No full servers
		const F_EMPTY = 16; // Only empty servers
		const F_NO_PASSWORD = 32; // Only servers without password protection
	};

	class SciServerData
	{
		public $engineVersionMajor = 0;
		public $engineVersionMinor = 0;
		public $engineVersionRevision = 0;
		public $ip;
		public $tcpPort = 0;
		public $udpPort = 0;
		public $players = 0;
		public $maxPlayers = 0;
		public $bots = 0;
		public $name;
		public $map;
		public $gameMode;
		public $password = false;
	};

	class SciDataStream
	{
		private $m_data = '';
		private $m_readOffset = 0;
		function __construct($data=null)
		{
			if($data === null)
				return;
			$this->m_data = $data;
		}
		public function WriteUnsignedInt($i) {$this->m_data .= pack("V",$i);}
		public function WriteUnsignedShort($i) {$this->m_data .= pack("v",$i);}
		public function WriteUnsignedChar($i) {$this->m_data .= pack("C",$i);}
		public function Read($sz,$format=null)
		{
			$sub = substr($this->m_data,$this->m_readOffset,$sz);
			$this->m_readOffset += $sz;
			if($format === null)
				return $sub;
			$r = unpack($format,$sub);
			if(empty($r))
				return 0;
			return $r[1];
		}
		public function ReadUnsignedInt() {return $this->Read(SciType::UNSIGNED_INT,"V");}
		public function ReadUnsignedShort() {return $this->Read(SciType::UNSIGNED_SHORT,"v");}
		public function ReadUnsignedChar() {return $this->Read(SciType::UNSIGNED_CHAR,"C");}
		public function ReadBool() {return ($this->ReadUnsignedChar() === 0) ? false : true;}
		public function &GetData() {return $this->m_data;}
		public function GetDataSize() {return strlen($this->m_data);}
		public function GetReadOffset() {return $this->m_readOffset;}
		public function SetReadOffset($off) {$this->m_readOffset = $off;}
		public function Eof() {return ($this->m_readOffset >= $this->GetDataSize()) ? true : false;}
		public function ReadString()
		{
			$str = '';
			$sz = $this->GetDataSize();
			$data = &$this->GetData();
			$offset = &$this->m_readOffset;
			if($this->Eof())
				return $str;
			while($data[$offset] !== "\0")
			{
				$str .= $data[$offset];
				$offset++;
				if($this->Eof())
					return $str;
			}
			$offset++;
			return $str;
		}
		public function WriteString($str)
		{
			$this->m_data .= $str . "\0";
		}
	};

	abstract class SciQuery
	{
		static private $m_masterServerIp = "85.214.192.20";
		static private $m_masterServerPort = 29155;
		function __construct()
		{}
		static private function Read($sock,$sz)
		{
			$data = socket_read($sock,$sz);
			if($data === false)
				return false;
			return new SciDataStream($data);
		}
		static private function Write($sock,$stream,$ip=null,$port=null)
		{
			if($ip === null)
				$ip = self::$m_masterServerIp;
			if($port === null)
				$port = self::$m_masterServerPort;
			return socket_sendto($sock,$stream->GetData(),$stream->GetDataSize(),0,$ip,$port);
		}
		static private function SendData($sock,$id,$stream,$ip=null,$port=null)
		{
			$header = new SciDataStream();
			$header->WriteUnsignedInt($SCI_PROTOCOL_VERSION); // Protocol Version
			$header->WriteUnsignedInt($id); // Query ID
			$header->WriteUnsignedShort($stream->GetDataSize()); // Body Size
			return (self::Write($sock,$header,$ip,$port) !== -1 && self::Write($sock,$stream,$ip,$port) !== -1) ? true : false;
		}
		static private function ReadData($sock)
		{
			$header = self::Read($sock,SciType::UNSIGNED_INT *2 +SciType::UNSIGNED_SHORT); // Wait for header
			if($header === false)
				return false;
			$header->SetReadOffset(SciType::UNSIGNED_INT *2);
			$szBody = $header->ReadUnsignedShort();
			return self::Read($sock,$szBody);
		}
		static private function GetSocketError()
		{
			return socket_strerror(socket_last_error());
		}
		static private function InitializeSocket()
		{
			$sock = socket_create(AF_INET,SOCK_DGRAM,SOL_UDP);
			if($sock === false)
				throw new Exception(self::GetSocketError());
			$t = array('sec' => 2,'usec' => 0);
			socket_set_option($sock,SOL_SOCKET,SO_RCVTIMEO,$t);
			socket_set_option($sock,SOL_SOCKET,SO_SNDTIMEO,$t);
			return $sock;
		}
		static public function RequestServers($filter=SciRequestFilter::NONE,$attrFilters=array())
		{
			$sock = self::InitializeSocket();
			$body = new SciDataStream();
			$body->WriteUnsignedInt($filter); // Request Filter
			$body->WriteUnsignedChar(count($attrFilters)); // Filter Count
			foreach($attrFilters AS $k => &$v)
			{
				$body->WriteString($k);
				$body->WriteString($v);
			}
			if(self::SendData($sock,1502,$body) === false)
			{
				socket_close($sock);
				throw new Exception(self::GetSocketError());
			}
			$response = self::ReadData($sock);
			socket_close($sock);
			if($response === false)
				throw new Exception(self::GetSocketError());
			$numServers = $response->ReadUnsignedInt();
			$servers = array();
			for($i=0;$i<$numServers;$i++)
			{
				$data = new SciServerData();
				$data->ip = $response->ReadString();
				$data->engineVersionMajor = $response->ReadUnsignedInt();
				$data->engineVersionMinor = $response->ReadUnsignedInt();
				$data->engineVersionRevision = $response->ReadUnsignedInt();
				$data->tcpPort = $response->ReadUnsignedShort();
				$data->udpPort = $response->ReadUnsignedShort();
				$data->players = $response->ReadUnsignedShort();
				$data->maxPlayers = $response->ReadUnsignedShort();
				$data->bots = $response->ReadUnsignedShort();
				$data->name = $response->ReadString();
				$data->map = $response->ReadString();
				$data->gameMode = $response->ReadString();
				$data->password = $response->ReadBool();
				$servers[] = $data;
			}
			return $servers;
		}
	};
?>


Save it on your server as "sci_query.php", then you can use it like this:

<?php
	require_once("sci_query.php");
	try
	{
		// Send the query to the master server. See the descriptions in the 'SciRequestFilter'-class for more information about the filters.
		// 'RequestServers' returns an array of 'SciServerData'-objects.
		$servers = SciQuery::RequestServers(SciRequestFilter::F_NOT_FULL | SciRequestFilter::F_NO_PASSWORD,array(
			"hostname" => "Sci*yt", // All servers that match "*pr*a*" (e.g. "Pragma"), case doesn't matter
			"map" => "fork",
			"gamemode" => "deathmatch",
			"version" => "1.0.0"
		));
		for($i=0;$i<count($servers);$i++)
		{
			$sv = $servers[$i];
			echo "#" . $i . " - " . $sv->name . "<br/>";
			echo "IP: " . $sv->ip . "<br/>";
			echo "Engine Version: " . $sv->engineVersionMajor . "." . $sv->engineVersionMinor . "." . $sv->engineVersionRevision . "<br/>";
			echo "Port: " . $sv->tcpPort . "<br/>";
			echo "Players: " . $sv->players . "/" . $sv->maxPlayers . "<br/>";
			echo "Bots: " . $sv->bots . "<br/>";
			echo "Map: " . $sv->map . "<br/>";
			echo "Game Mode: " . $sv->gameMode . "<br/>";
			echo "Password Protected: " . (($sv->password === true) ? "Yes" : "No") . "<br/>";
			echo "<br/>";
		}
	}
	catch(Exception $e)
	{
		echo "Unable to retrieve server list: " . $e->getMessage() . "<br/>";
	}
?>