* * @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; /** * Class Socket * * @package xPaw\SourceQuery * * @uses xPaw\SourceQuery\Exception\InvalidPacketException * @uses xPaw\SourceQuery\Exception\SocketException */ class Socket { public $Socket; public $Engine; public $Ip; public $Port; public $Timeout; public function Close( ) { if( $this->Socket ) { FClose( $this->Socket ); $this->Socket = null; } } public function Open( $Ip, $Port, $Timeout, $Engine ) { $this->Timeout = $Timeout; $this->Engine = $Engine; $this->Port = $Port; $this->Ip = $Ip; $this->Socket = @FSockOpen( 'udp://' . $Ip, $Port, $ErrNo, $ErrStr, $Timeout ); if( $ErrNo || $this->Socket === false ) { throw new SocketException( 'Could not create socket: ' . $ErrStr, SocketException::COULD_NOT_CREATE_SOCKET ); } Stream_Set_Timeout( $this->Socket, $Timeout ); Stream_Set_Blocking( $this->Socket, true ); } public function Write( $Header, $String = '' ) { $Command = Pack( 'ccccca*', 0xFF, 0xFF, 0xFF, 0xFF, $Header, $String ); $Length = StrLen( $Command ); return $Length === FWrite( $this->Socket, $Command, $Length ); } /** * Reads from socket and returns Buffer. * * @throws InvalidPacketException * * @return Buffer Buffer */ public function Read( $Length = 1400 ) { $Buffer = new Buffer( ); $Buffer->Set( FRead( $this->Socket, $Length ) ); 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 && $this->Sherlock( $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; } private function Sherlock( $Buffer, $Length ) { $Data = FRead( $this->Socket, $Length ); if( StrLen( $Data ) < 4 ) { return false; } $Buffer->Set( $Data ); return $Buffer->GetLong( ) === -2; } }