Socket = $Socket; } public function Close( ) : void { if( $this->RconSocket ) { FClose( $this->RconSocket ); $this->RconSocket = null; } $this->RconRequestId = 0; } public function Open( ) : void { if( !$this->RconSocket ) { $RconSocket = @FSockOpen( $this->Socket->Address, $this->Socket->Port, $ErrNo, $ErrStr, $this->Socket->Timeout ); if( $ErrNo || !$RconSocket ) { throw new SocketException( 'Can\'t connect to RCON server: ' . $ErrStr, SocketException::CONNECTION_FAILED ); } $this->RconSocket = $RconSocket; Stream_Set_Timeout( $this->RconSocket, $this->Socket->Timeout ); Stream_Set_Blocking( $this->RconSocket, true ); } } public function Write( int $Header, string $String = '' ) : bool { // Pack the packet together $Command = Pack( 'VV', ++$this->RconRequestId, $Header ) . $String . "\x00\x00"; // Prepend packet length $Command = Pack( 'V', StrLen( $Command ) ) . $Command; $Length = StrLen( $Command ); return $Length === FWrite( $this->RconSocket, $Command, $Length ); } public function Read( ) : Buffer { $Buffer = new Buffer( ); $Buffer->Set( FRead( $this->RconSocket, 4 ) ); if( $Buffer->Remaining( ) < 4 ) { throw new InvalidPacketException( 'Rcon read: Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY ); } $PacketSize = $Buffer->GetLong( ); $Buffer->Set( FRead( $this->RconSocket, $PacketSize ) ); $Data = $Buffer->Get( ); $Remaining = $PacketSize - StrLen( $Data ); while( $Remaining > 0 ) { $Data2 = FRead( $this->RconSocket, $Remaining ); $PacketSize = StrLen( $Data2 ); if( $PacketSize === 0 ) { throw new InvalidPacketException( 'Read ' . strlen( $Data ) . ' bytes from socket, ' . $Remaining . ' remaining', InvalidPacketException::BUFFER_EMPTY ); break; } $Data .= $Data2; $Remaining -= $PacketSize; } $Buffer->Set( $Data ); return $Buffer; } public function Command( string $Command ) : string { $this->Write( SourceQuery::SERVERDATA_EXECCOMMAND, $Command ); $Buffer = $this->Read( ); $Buffer->GetLong( ); // RequestID $Type = $Buffer->GetLong( ); if( $Type === SourceQuery::SERVERDATA_AUTH_RESPONSE ) { throw new AuthenticationException( 'Bad rcon_password.', AuthenticationException::BAD_PASSWORD ); } else if( $Type !== SourceQuery::SERVERDATA_RESPONSE_VALUE ) { throw new InvalidPacketException( 'Invalid rcon response.', InvalidPacketException::PACKET_HEADER_MISMATCH ); } $Data = $Buffer->Get( ); // We do this stupid hack to handle split packets // See https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses if( StrLen( $Data ) >= 4000 ) { $this->Write( SourceQuery::SERVERDATA_REQUESTVALUE ); do { $Buffer = $this->Read( ); $Buffer->GetLong( ); // RequestID if( $Buffer->GetLong( ) !== SourceQuery::SERVERDATA_RESPONSE_VALUE ) { break; } $Data2 = $Buffer->Get( ); if( $Data2 === "\x00\x01\x00\x00\x00\x00" ) { break; } $Data .= $Data2; } while( true ); } return rtrim( $Data, "\0" ); } public function Authorize( string $Password ) : void { $this->Write( SourceQuery::SERVERDATA_AUTH, $Password ); $Buffer = $this->Read( ); $RequestID = $Buffer->GetLong( ); $Type = $Buffer->GetLong( ); // If we receive SERVERDATA_RESPONSE_VALUE, then we need to read again // More info: https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Additional_Comments if( $Type === SourceQuery::SERVERDATA_RESPONSE_VALUE ) { $Buffer = $this->Read( ); $RequestID = $Buffer->GetLong( ); $Type = $Buffer->GetLong( ); } if( $RequestID === -1 || $Type !== SourceQuery::SERVERDATA_AUTH_RESPONSE ) { throw new AuthenticationException( 'RCON authorization failed.', AuthenticationException::BAD_PASSWORD ); } } }