From f3684b4783997a4b4f6bfb67db31a17d328fbd90 Mon Sep 17 00:00:00 2001 From: xPaw Date: Thu, 26 Jun 2014 13:18:35 +0300 Subject: [PATCH] Split source and goldsource RCON into different classess --- SourceQuery/GoldSourceRcon.class.php | 127 +++++++++++++ SourceQuery/Rcon.class.php | 258 --------------------------- SourceQuery/SourceQuery.class.php | 26 ++- SourceQuery/SourceRcon.class.php | 137 ++++++++++++++ 4 files changed, 287 insertions(+), 261 deletions(-) create mode 100644 SourceQuery/GoldSourceRcon.class.php delete mode 100644 SourceQuery/Rcon.class.php create mode 100644 SourceQuery/SourceRcon.class.php diff --git a/SourceQuery/GoldSourceRcon.class.php b/SourceQuery/GoldSourceRcon.class.php new file mode 100644 index 0000000..5d54251 --- /dev/null +++ b/SourceQuery/GoldSourceRcon.class.php @@ -0,0 +1,127 @@ +Buffer = $Buffer; + $this->Socket = $Socket; + } + + public function Close( ) + { + $this->RconChallenge = 0; + $this->RconRequestId = 0; + $this->RconPassword = 0; + } + + public function Open( ) + { + // + } + + public function Write( $Header, $String = '' ) + { + $Command = Pack( 'cccca*', 0xFF, 0xFF, 0xFF, 0xFF, $String ); + $Length = StrLen( $Command ); + + return $Length === FWrite( $this->Socket->Socket, $Command, $Length ); + } + + public function Read( $Length = 1400 ) + { + // GoldSource RCON has same structure as Query + $this->Socket->Read( ); + + if( $this->Buffer->GetByte( ) !== SourceQuery :: S2A_RCON ) + { + return false; + } + + $Buffer = $this->Buffer->Get( ); + $Trimmed = Trim( $Buffer ); + + if( $Trimmed === 'Bad rcon_password.' + || $Trimmed === 'You have been banned from this server.' ) + { + throw new SourceQueryException( $Trimmed ); + } + + $ReadMore = false; + + // There is no indentifier of the end, so we just need to continue reading + // TODO: Needs to be looked again, it causes timeouts + do + { + $this->Socket->Read( ); + + $ReadMore = $this->Buffer->Remaining( ) > 0 && $this->Buffer->GetByte( ) === SourceQuery :: S2A_RCON; + + if( $ReadMore ) + { + $Packet = $this->Buffer->Get( ); + $Buffer .= SubStr( $Packet, 0, -2 ); + + // Let's assume if this packet is not long enough, there are no more after this one + $ReadMore = StrLen( $Packet ) > 1000; // use 1300? + } + } + while( $ReadMore ); + + $this->Buffer->Set( Trim( $Buffer ) ); + } + + public function Command( $Command ) + { + if( !$this->RconChallenge ) + { + return false; + } + + $this->Write( 0, 'rcon ' . $this->RconChallenge . ' "' . $this->RconPassword . '" ' . $Command . "\0" ); + $this->Read( ); + + return $this->Buffer->Get( ); + } + + public function Authorize( $Password ) + { + $this->RconPassword = $Password; + + $this->Write( 0, 'challenge rcon' ); + $this->Socket->Read( ); + + if( $this->Buffer->Get( 14 ) !== 'challenge rcon' ) + { + return false; + } + + $this->RconChallenge = Trim( $this->Buffer->Get( ) ); + + return true; + } + } diff --git a/SourceQuery/Rcon.class.php b/SourceQuery/Rcon.class.php deleted file mode 100644 index 260b346..0000000 --- a/SourceQuery/Rcon.class.php +++ /dev/null @@ -1,258 +0,0 @@ -Buffer = $Buffer; - $this->Socket = $Socket; - } - - public function Close( ) - { - if( $this->RconSocket ) - { - FClose( $this->RconSocket ); - - $this->RconSocket = null; - } - - $this->RconChallenge = 0; - $this->RconRequestId = 0; - $this->RconPassword = 0; - } - - public function Open( ) - { - if( !$this->RconSocket && $this->Socket->Engine == SourceQuery :: SOURCE ) - { - $this->RconSocket = @FSockOpen( $this->Socket->Ip, $this->Socket->Port, $ErrNo, $ErrStr, $this->Socket->Timeout ); - - if( $ErrNo || !$this->RconSocket ) - { - throw new SourceQueryException( 'Can\'t connect to RCON server: ' . $ErrStr ); - } - - Stream_Set_Timeout( $this->RconSocket, $this->Socket->Timeout ); - Stream_Set_Blocking( $this->RconSocket, true ); - } - } - - public function Write( $Header, $String = '' ) - { - switch( $this->Socket->Engine ) - { - case SourceQuery :: GOLDSOURCE: - { - $Command = Pack( 'cccca*', 0xFF, 0xFF, 0xFF, 0xFF, $String ); - $Length = StrLen( $Command ); - - return $Length === FWrite( $this->Socket->Socket, $Command, $Length ); - } - case SourceQuery :: SOURCE: - { - // Pack the packet together - $Command = Pack( 'VV', ++$this->RconRequestId, $Header ) . $String . "\x00\x00\x00"; - - // Prepend packet length - $Command = Pack( 'V', StrLen( $Command ) ) . $Command; - $Length = StrLen( $Command ); - - return $Length === FWrite( $this->RconSocket, $Command, $Length ); - } - } - } - - public function Read( $Length = 1400 ) - { - switch( $this->Socket->Engine ) - { - case SourceQuery :: GOLDSOURCE: - { - // GoldSource RCON has same structure as Query - $this->Socket->Read( ); - - if( $this->Buffer->GetByte( ) != SourceQuery :: S2A_RCON ) - { - return false; - } - - $Buffer = $this->Buffer->Get( ); - $Trimmed = Trim( $Buffer ); - - if( $Trimmed == 'Bad rcon_password.' - || $Trimmed == 'You have been banned from this server.' ) - { - throw new SourceQueryException( $Trimmed ); - } - - $ReadMore = false; - - // There is no indentifier of the end, so we just need to continue reading - // TODO: Needs to be looked again, it causes timeouts - do - { - $this->Socket->Read( ); - - $ReadMore = $this->Buffer->Remaining( ) > 0 && $this->Buffer->GetByte( ) == SourceQuery :: S2A_RCON; - - if( $ReadMore ) - { - $Packet = $this->Buffer->Get( ); - $Buffer .= SubStr( $Packet, 0, -2 ); - - // Let's assume if this packet is not long enough, there are no more after this one - $ReadMore = StrLen( $Packet ) > 1000; // use 1300? - } - } - while( $ReadMore ); - - $this->Buffer->Set( Trim( $Buffer ) ); - - break; - } - case SourceQuery :: SOURCE: - { - $this->Buffer->Set( FRead( $this->RconSocket, $Length ) ); - - $Buffer = ""; - - $PacketSize = $this->Buffer->GetLong( ); - - $Buffer .= $this->Buffer->Get( ); - - // TODO: multi packet reading - - $this->Buffer->Set( $Buffer ); - - break; - } - } - } - - public function Command( $Command ) - { - if( !$this->RconChallenge ) - { - return false; - } - - $Buffer = false; - - switch( $this->Socket->Engine ) - { - case SourceQuery :: GOLDSOURCE: - { - $this->Write( 0, 'rcon ' . $this->RconChallenge . ' "' . $this->RconPassword . '" ' . $Command . "\0" ); - $this->Read( ); - - $Buffer = $this->Buffer->Get( ); - - break; - } - case SourceQuery :: SOURCE: - { - $this->Write( SourceQuery :: SERVERDATA_EXECCOMMAND, $Command ); - $this->Read( ); - - $RequestID = $this->Buffer->GetLong( ); - $Type = $this->Buffer->GetLong( ); - - if( $Type == SourceQuery :: SERVERDATA_AUTH_RESPONSE ) - { - throw new SourceQueryException( 'Bad rcon_password.' ); - } - else if( $Type != SourceQuery :: SERVERDATA_RESPONSE_VALUE ) - { - return false; - } - - // TODO: It should use GetString, but there are no null bytes at the end, why? - // $Buffer = $this->Buffer->GetString( ); - $Buffer = $this->Buffer->Get( ); - - break; - } - } - - return $Buffer; - } - - public function Authorize( $Password ) - { - $this->RconPassword = $Password; - - switch( $this->Socket->Engine ) - { - case SourceQuery :: GOLDSOURCE: - { - $this->Write( 0, 'challenge rcon' ); - $this->Socket->Read( ); - - if( $this->Buffer->Get( 14 ) != 'challenge rcon' ) - { - return false; - } - - $this->RconChallenge = Trim( $this->Buffer->Get( ) ); - - break; - } - case SourceQuery :: SOURCE: - { - $this->Write( SourceQuery :: SERVERDATA_AUTH, $Password ); - $this->Read( ); - - $RequestID = $this->Buffer->GetLong( ); - $Type = $this->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 ) - { - $this->Read( ); - - $RequestID = $this->Buffer->GetLong( ); - $Type = $this->Buffer->GetLong( ); - } - - if( $RequestID == -1 || $Type != SourceQuery :: SERVERDATA_AUTH_RESPONSE ) - { - throw new SourceQueryException( 'RCON authorization failed.' ); - } - - $this->RconChallenge = 1; - - break; - } - } - - return true; - } - } diff --git a/SourceQuery/SourceQuery.class.php b/SourceQuery/SourceQuery.class.php index e29cc0f..9dbcb82 100644 --- a/SourceQuery/SourceQuery.class.php +++ b/SourceQuery/SourceQuery.class.php @@ -17,7 +17,8 @@ require __DIR__ . '/Exception.class.php'; require __DIR__ . '/Buffer.class.php'; require __DIR__ . '/Socket.class.php'; - require __DIR__ . '/Rcon.class.php'; + require __DIR__ . '/SourceRcon.class.php'; + require __DIR__ . '/GoldSourceRcon.class.php'; class SourceQuery { @@ -106,7 +107,6 @@ { $this->Buffer = new SourceQueryBuffer( ); $this->Socket = new SourceQuerySocket( $this->Buffer ); - $this->Rcon = new SourceQueryRcon( $this->Buffer, $this->Socket ); } public function __destruct( ) @@ -142,6 +142,22 @@ } $this->Connected = true; + + switch( $this->Socket->Engine ) + { + case SourceQuery :: GOLDSOURCE: + { + $this->Rcon = new SourceQueryGoldSourceRcon( $this->Buffer, $this->Socket ); + + break; + } + case SourceQuery :: SOURCE: + { + $this->Rcon = new SourceQuerySourceRcon( $this->Buffer, $this->Socket ); + + break; + } + } } /** @@ -152,7 +168,11 @@ $this->Connected = false; $this->Socket->Close( ); - $this->Rcon->Close( ); + + if( $this->Rcon ) + { + $this->Rcon->Close( ); + } } /** diff --git a/SourceQuery/SourceRcon.class.php b/SourceQuery/SourceRcon.class.php new file mode 100644 index 0000000..0e44593 --- /dev/null +++ b/SourceQuery/SourceRcon.class.php @@ -0,0 +1,137 @@ +Buffer = $Buffer; + $this->Socket = $Socket; + } + + public function Close( ) + { + if( $this->RconSocket ) + { + FClose( $this->RconSocket ); + + $this->RconSocket = null; + } + + $this->RconRequestId = 0; + } + + public function Open( ) + { + if( !$this->RconSocket ) + { + $this->RconSocket = @FSockOpen( $this->Socket->Ip, $this->Socket->Port, $ErrNo, $ErrStr, $this->Socket->Timeout ); + + if( $ErrNo || !$this->RconSocket ) + { + throw new SourceQueryException( 'Can\'t connect to RCON server: ' . $ErrStr ); + } + + Stream_Set_Timeout( $this->RconSocket, $this->Socket->Timeout ); + Stream_Set_Blocking( $this->RconSocket, true ); + } + } + + public function Write( $Header, $String = '' ) + { + // Pack the packet together + $Command = Pack( 'VV', ++$this->RconRequestId, $Header ) . $String . "\x00\x00\x00"; + + // Prepend packet length + $Command = Pack( 'V', StrLen( $Command ) ) . $Command; + $Length = StrLen( $Command ); + + return $Length === FWrite( $this->RconSocket, $Command, $Length ); + } + + public function Read( $Length = 1400 ) + { + $this->Buffer->Set( FRead( $this->RconSocket, $Length ) ); + + $Buffer = ""; + + $PacketSize = $this->Buffer->GetLong( ); + + $Buffer .= $this->Buffer->Get( ); + + // TODO: multi packet reading + + $this->Buffer->Set( $Buffer ); + } + + public function Command( $Command ) + { + $this->Write( SourceQuery :: SERVERDATA_EXECCOMMAND, $Command ); + $this->Read( ); + + $RequestID = $this->Buffer->GetLong( ); + $Type = $this->Buffer->GetLong( ); + + if( $Type === SourceQuery :: SERVERDATA_AUTH_RESPONSE ) + { + throw new SourceQueryException( 'Bad rcon_password.' ); + } + else if( $Type !== SourceQuery :: SERVERDATA_RESPONSE_VALUE ) + { + return false; + } + + // TODO: It should use GetString, but there are no null bytes at the end, why? + // $Buffer = $this->Buffer->GetString( ); + return $this->Buffer->Get( ); + } + + public function Authorize( $Password ) + { + $this->Write( SourceQuery :: SERVERDATA_AUTH, $Password ); + $this->Read( ); + + $RequestID = $this->Buffer->GetLong( ); + $Type = $this->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 ) + { + $this->Read( ); + + $RequestID = $this->Buffer->GetLong( ); + $Type = $this->Buffer->GetLong( ); + } + + if( $RequestID === -1 || $Type !== SourceQuery :: SERVERDATA_AUTH_RESPONSE ) + { + throw new SourceQueryException( 'RCON authorization failed.' ); + } + + return true; + } + }