Skip to content

Commit

Permalink
Implemented SourceQuery
Browse files Browse the repository at this point in the history
  • Loading branch information
jmbluethner committed May 20, 2019
1 parent c9e48af commit c2e542f
Show file tree
Hide file tree
Showing 18 changed files with 1,636 additions and 0 deletions.
34 changes: 34 additions & 0 deletions scq/Example.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
require __DIR__ . '/SourceQuery/bootstrap.php';

use xPaw\SourceQuery\SourceQuery;

// For the sake of this example
Header( 'Content-Type: text/plain' );
Header( 'X-Content-Type-Options: nosniff' );

// Edit this ->
define( 'SQ_SERVER_ADDR', '216.52.148.47' );
define( 'SQ_SERVER_PORT', 27015 );
define( 'SQ_TIMEOUT', 1 );
define( 'SQ_ENGINE', SourceQuery::SOURCE );
// Edit this <-

$Query = new SourceQuery( );

try
{
$Query->Connect( SQ_SERVER_ADDR, SQ_SERVER_PORT, SQ_TIMEOUT, SQ_ENGINE );

print_r( $Query->GetInfo( ) );
print_r( $Query->GetPlayers( ) );
print_r( $Query->GetRules( ) );
}
catch( Exception $e )
{
echo $e->getMessage( );
}
finally
{
$Query->Disconnect( );
}
131 changes: 131 additions & 0 deletions scq/SourceQuery/BaseSocket.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php
/**
* @author Pavel Djundik <[email protected]>
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/

namespace xPaw\SourceQuery;

use xPaw\SourceQuery\Exception\InvalidPacketException;

/**
* Base socket interface
*
* @package xPaw\SourceQuery
*
* @uses xPaw\SourceQuery\Exception\InvalidPacketException
*/
abstract class BaseSocket
{
public $Socket;
public $Engine;

public $Address;
public $Port;
public $Timeout;

public function __destruct( )
{
$this->Close( );
}

abstract public function Close( );
abstract public function Open( $Address, $Port, $Timeout, $Engine );
abstract public function Write( $Header, $String = '' );
abstract public function Read( $Length = 1400 );

protected function ReadInternal( $Buffer, $Length, $SherlockFunction )
{
if( $Buffer->Remaining( ) === 0 )
{
throw new InvalidPacketException( 'Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY );
}

$Header = $Buffer->GetLong( );

if( $Header === -1 ) // Single packet
{
// We don't have to do anything
}
else if( $Header === -2 ) // Split packet
{
$Packets = [];
$IsCompressed = false;
$ReadMore = false;

do
{
$RequestID = $Buffer->GetLong( );

switch( $this->Engine )
{
case SourceQuery::GOLDSOURCE:
{
$PacketCountAndNumber = $Buffer->GetByte( );
$PacketCount = $PacketCountAndNumber & 0xF;
$PacketNumber = $PacketCountAndNumber >> 4;

break;
}
case SourceQuery::SOURCE:
{
$IsCompressed = ( $RequestID & 0x80000000 ) !== 0;
$PacketCount = $Buffer->GetByte( );
$PacketNumber = $Buffer->GetByte( ) + 1;

if( $IsCompressed )
{
$Buffer->GetLong( ); // Split size

$PacketChecksum = $Buffer->GetUnsignedLong( );
}
else
{
$Buffer->GetShort( ); // Split size
}

break;
}
}

$Packets[ $PacketNumber ] = $Buffer->Get( );

$ReadMore = $PacketCount > sizeof( $Packets );
}
while( $ReadMore && $SherlockFunction( $Buffer, $Length ) );

$Data = Implode( $Packets );

// TODO: Test this
if( $IsCompressed )
{
// Let's make sure this function exists, it's not included in PHP by default
if( !Function_Exists( 'bzdecompress' ) )
{
throw new \RuntimeException( 'Received compressed packet, PHP doesn\'t have Bzip2 library installed, can\'t decompress.' );
}

$Data = bzdecompress( $Data );

if( CRC32( $Data ) !== $PacketChecksum )
{
throw new InvalidPacketException( 'CRC32 checksum mismatch of uncompressed packet data.', InvalidPacketException::CHECKSUM_MISMATCH );
}
}

$Buffer->Set( SubStr( $Data, 4 ) );
}
else
{
throw new InvalidPacketException( 'Socket read: Raw packet header mismatch. (0x' . DecHex( $Header ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
}

return $Buffer;
}
}
199 changes: 199 additions & 0 deletions scq/SourceQuery/Buffer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<?php
/**
* @author Pavel Djundik <[email protected]>
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/

namespace xPaw\SourceQuery;

use xPaw\SourceQuery\Exception\InvalidPacketException;

/**
* Class Buffer
*
* @package xPaw\SourceQuery
*
* @uses xPaw\SourceQuery\Exception\InvalidPacketException
*/
class Buffer
{
/**
* Buffer
*
* @var string
*/
private $Buffer;

/**
* Buffer length
*
* @var int
*/
private $Length;

/**
* Current position in buffer
*
* @var int
*/
private $Position;

/**
* Sets buffer
*
* @param string $Buffer Buffer
*/
public function Set( $Buffer )
{
$this->Buffer = $Buffer;
$this->Length = StrLen( $Buffer );
$this->Position = 0;
}

/**
* Get remaining bytes
*
* @return int Remaining bytes in buffer
*/
public function Remaining( )
{
return $this->Length - $this->Position;
}

/**
* Gets data from buffer
*
* @param int $Length Bytes to read
*
* @return string
*/
public function Get( $Length = -1 )
{
if( $Length === 0 )
{
return '';
}

$Remaining = $this->Remaining( );

if( $Length === -1 )
{
$Length = $Remaining;
}
else if( $Length > $Remaining )
{
return '';
}

$Data = SubStr( $this->Buffer, $this->Position, $Length );

$this->Position += $Length;

return $Data;
}

/**
* Get byte from buffer
*
* @return int
*/
public function GetByte( )
{
return Ord( $this->Get( 1 ) );
}

/**
* Get short from buffer
*
* @return int
*/
public function GetShort( )
{
if( $this->Remaining( ) < 2 )
{
throw new InvalidPacketException( 'Not enough data to unpack a short.', InvalidPacketException::BUFFER_EMPTY );
}

$Data = UnPack( 'v', $this->Get( 2 ) );

return $Data[ 1 ];
}

/**
* Get long from buffer
*
* @return int
*/
public function GetLong( )
{
if( $this->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Not enough data to unpack a long.', InvalidPacketException::BUFFER_EMPTY );
}

$Data = UnPack( 'l', $this->Get( 4 ) );

return $Data[ 1 ];
}

/**
* Get float from buffer
*
* @return float
*/
public function GetFloat( )
{
if( $this->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Not enough data to unpack a float.', InvalidPacketException::BUFFER_EMPTY );
}

$Data = UnPack( 'f', $this->Get( 4 ) );

return $Data[ 1 ];
}

/**
* Get unsigned long from buffer
*
* @return int
*/
public function GetUnsignedLong( )
{
if( $this->Remaining( ) < 4 )
{
throw new InvalidPacketException( 'Not enough data to unpack an usigned long.', InvalidPacketException::BUFFER_EMPTY );
}

$Data = UnPack( 'V', $this->Get( 4 ) );

return $Data[ 1 ];
}

/**
* Read one string from buffer ending with null byte
*
* @return string
*/
public function GetString( )
{
$ZeroBytePosition = StrPos( $this->Buffer, "\0", $this->Position );

if( $ZeroBytePosition === false )
{
return '';
}

$String = $this->Get( $ZeroBytePosition - $this->Position );

$this->Position++;

return $String;
}
}
19 changes: 19 additions & 0 deletions scq/SourceQuery/Exception/AuthenticationException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php
/**
* @author Pavel Djundik <[email protected]>
*
* @link https://xpaw.me
* @link https://github.com/xPaw/PHP-Source-Query
*
* @license GNU Lesser General Public License, version 2.1
*
* @internal
*/

namespace xPaw\SourceQuery\Exception;

class AuthenticationException extends SourceQueryException
{
const BAD_PASSWORD = 1;
const BANNED = 2;
}
Loading

0 comments on commit c2e542f

Please sign in to comment.