Guest User

Untitled

a guest
Jan 2nd, 2017
23
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <?php
  2. class MinecraftQueryException extends Exception
  3. {
  4. // Exception thrown by MinecraftQuery class
  5. }
  6.  
  7. class MinecraftQuery
  8. {
  9. /*
  10. * Class written by xPaw
  11. *
  12. * Website: http://xpaw.me
  13. * GitHub: https://github.com/xPaw/PHP-Minecraft-Query
  14. */
  15.  
  16. const STATISTIC = 0x00;
  17. const HANDSHAKE = 0x09;
  18.  
  19. private $Socket;
  20. private $Players;
  21. private $Info;
  22.  
  23. public function Connect( $Ip, $Port = 25565, $Timeout = 3 )
  24. {
  25. $this->IP = gethostbyname($Ip);
  26. if( !is_int( $Timeout ) || $Timeout < 0 )
  27. {
  28. throw new InvalidArgumentException( 'Timeout must be an integer.' );
  29. }
  30.  
  31. $this->Socket = @FSockOpen( 'udp://' . $this->IP, (int)$Port, $ErrNo, $ErrStr, $Timeout );
  32.  
  33. if( $ErrNo || $this->Socket === false )
  34. {
  35. // throw new MinecraftQueryException( 'Could not create socket: ' . $ErrStr );
  36.  
  37. return;
  38. }
  39.  
  40. Stream_Set_Timeout( $this->Socket, $Timeout );
  41. Stream_Set_Blocking( $this->Socket, true );
  42.  
  43. try
  44. {
  45. $Challenge = $this->GetChallenge( );
  46.  
  47. $this->GetStatus( $Challenge );
  48. }
  49. catch( MinecraftQueryException $e ) { }
  50.  
  51. FClose( $this->Socket );
  52. }
  53.  
  54. public function GetInfo( )
  55. {
  56. return isset( $this->Info ) ? $this->Info : false;
  57. }
  58.  
  59. public function GetPlayers( )
  60. {
  61. return isset( $this->Players ) ? $this->Players : false;
  62. }
  63.  
  64. private function GetChallenge( )
  65. {
  66. $Data = $this->WriteData( self :: HANDSHAKE );
  67.  
  68. if( $Data === false )
  69. {
  70. throw new MinecraftQueryException( 'Failed to receive challenge.' );
  71. }
  72.  
  73. return Pack( 'N', $Data );
  74. }
  75.  
  76. private function GetStatus( $Challenge )
  77. {
  78. $Data = $this->WriteData( self :: STATISTIC, $Challenge . Pack( 'c*', 0x00, 0x00, 0x00, 0x00 ) );
  79.  
  80. if( !$Data )
  81. {
  82. throw new MinecraftQueryException( 'Failed to receive status.' );
  83. }
  84.  
  85. $Last = '';
  86. $Info = Array( );
  87.  
  88. $Data = SubStr( $Data, 11 ); // splitnum + 2 int
  89. $Data = Explode( "\x00\x00\x01player_\x00\x00", $Data );
  90.  
  91. if( Count( $Data ) !== 2 )
  92. {
  93. throw new MinecraftQueryException( 'Failed to parse server\'s response.' );
  94. }
  95.  
  96. $Players = SubStr( $Data[ 1 ], 0, -2 );
  97. $Data = Explode( "\x00", $Data[ 0 ] );
  98.  
  99. // Array with known keys in order to validate the result
  100. // It can happen that server sends custom strings containing bad things (who can know!)
  101. $Keys = Array(
  102. 'hostname' => 'HostName',
  103. 'gametype' => 'GameType',
  104. 'version' => 'Version',
  105. 'plugins' => 'Plugins',
  106. 'map' => 'Map',
  107. 'numplayers' => 'Players',
  108. 'maxplayers' => 'MaxPlayers',
  109. 'hostport' => 'HostPort',
  110. 'hostip' => 'HostIp',
  111. 'game_id' => 'GameName'
  112. );
  113.  
  114. foreach( $Data as $Key => $Value )
  115. {
  116. if( ~$Key & 1 )
  117. {
  118. if( !Array_Key_Exists( $Value, $Keys ) )
  119. {
  120. $Last = false;
  121. continue;
  122. }
  123.  
  124. $Last = $Keys[ $Value ];
  125. $Info[ $Last ] = '';
  126. }
  127. else if( $Last != false )
  128. {
  129. $Info[ $Last ] = $Value;
  130. }
  131. }
  132.  
  133. // Ints
  134. $Info[ 'Players' ] = IntVal( $Info[ 'Players' ] );
  135. $Info[ 'MaxPlayers' ] = IntVal( $Info[ 'MaxPlayers' ] );
  136. $Info[ 'HostPort' ] = IntVal( $Info[ 'HostPort' ] );
  137.  
  138. // Parse "plugins", if any
  139. if( $Info[ 'Plugins' ] )
  140. {
  141. $Data = Explode( ": ", $Info[ 'Plugins' ], 2 );
  142.  
  143. $Info[ 'RawPlugins' ] = $Info[ 'Plugins' ];
  144. $Info[ 'Software' ] = $Data[ 0 ];
  145.  
  146. if( Count( $Data ) == 2 )
  147. {
  148. $Info[ 'Plugins' ] = Explode( "; ", $Data[ 1 ] );
  149. }
  150. }
  151. else
  152. {
  153. $Info[ 'Software' ] = 'Vanilla';
  154. }
  155.  
  156. $this->Info = $Info;
  157.  
  158. if( $Players )
  159. {
  160. $this->Players = Explode( "\x00", $Players );
  161. }
  162. }
  163.  
  164. private function WriteData( $Command, $Append = "" )
  165. {
  166. $Command = Pack( 'c*', 0xFE, 0xFD, $Command, 0x01, 0x02, 0x03, 0x04 ) . $Append;
  167. $Length = StrLen( $Command );
  168.  
  169. if( $Length !== FWrite( $this->Socket, $Command, $Length ) )
  170. {
  171. throw new MinecraftQueryException( "Failed to write on socket." );
  172. }
  173.  
  174. $Data = FRead( $this->Socket, 4096 );
  175.  
  176. if( $Data === false )
  177. {
  178. throw new MinecraftQueryException( "Failed to read from socket." );
  179. }
  180.  
  181. if( StrLen( $Data ) < 5 || $Data[ 0 ] != $Command[ 2 ] )
  182. {
  183. return false;
  184. }
  185.  
  186. return SubStr( $Data, 5 );
  187. }
  188. }
RAW Paste Data