You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							140 lines
						
					
					
						
							3.6 KiB
						
					
					
				
			
		
		
	
	
							140 lines
						
					
					
						
							3.6 KiB
						
					
					
				<?php
 | 
						|
	/**
 | 
						|
	 * @author Pavel Djundik
 | 
						|
	 *
 | 
						|
	 * @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;
 | 
						|
	use xPaw\SourceQuery\Exception\SocketException;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Base socket interface
 | 
						|
	 *
 | 
						|
	 * @package xPaw\SourceQuery
 | 
						|
	 *
 | 
						|
	 * @uses xPaw\SourceQuery\Exception\InvalidPacketException
 | 
						|
	 * @uses xPaw\SourceQuery\Exception\SocketException
 | 
						|
	 */
 | 
						|
	abstract class BaseSocket
 | 
						|
	{
 | 
						|
		/** @var resource */
 | 
						|
		public $Socket;
 | 
						|
		public int $Engine;
 | 
						|
		
 | 
						|
		public string $Address;
 | 
						|
		public int $Port;
 | 
						|
		public int $Timeout;
 | 
						|
		
 | 
						|
		public function __destruct( )
 | 
						|
		{
 | 
						|
			$this->Close( );
 | 
						|
		}
 | 
						|
		
 | 
						|
		abstract public function Close( ) : void;
 | 
						|
		abstract public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void;
 | 
						|
		abstract public function Write( int $Header, string $String = '' ) : bool;
 | 
						|
		abstract public function Read( int $Length = 1400 ) : Buffer;
 | 
						|
		
 | 
						|
		protected function ReadInternal( Buffer $Buffer, int $Length, callable $SherlockFunction ) : Buffer
 | 
						|
		{
 | 
						|
			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;
 | 
						|
				$PacketChecksum = null;
 | 
						|
				
 | 
						|
				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;
 | 
						|
						}
 | 
						|
						default:
 | 
						|
						{
 | 
						|
							throw new SocketException( 'Unknown engine.', SocketException::INVALID_ENGINE );
 | 
						|
						}
 | 
						|
					}
 | 
						|
					
 | 
						|
					$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( !is_string( $Data ) || 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;
 | 
						|
		}
 | 
						|
	}
 |