Godleydemon

MinecraftPing.php

Oct 23rd, 2020
103
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 5.29 KB | None | 0 0
  1. <?php
  2.  
  3. namespace xPaw;
  4.  
  5. class MinecraftPing
  6. {
  7.     /*
  8.      * Queries Minecraft server
  9.      * Returns array on success, false on failure.
  10.      *
  11.      * WARNING: This method was added in snapshot 13w41a (Minecraft 1.7)
  12.      *
  13.      * Written by xPaw
  14.      *
  15.      * Website: http://xpaw.me
  16.      * GitHub: https://github.com/xPaw/PHP-Minecraft-Query
  17.      *
  18.      * ---------
  19.      *
  20.      * This method can be used to get server-icon.png too.
  21.      * Something like this:
  22.      *
  23.      * $Server = new MinecraftPing( 'localhost' );
  24.      * $Info = $Server->Query();
  25.      * echo '<img width="64" height="64" src="' . Str_Replace( "\n", "", $Info[ 'favicon' ] ) . '">';
  26.      *
  27.      */
  28.  
  29.     private $Socket;
  30.     private $ServerAddress;
  31.     private $ServerPort;
  32.     private $Timeout;
  33.  
  34.     public function __construct( $Address, $Port = 25566, $Timeout = 2, $ResolveSRV = true )
  35.     {
  36.         $this->ServerAddress = $Address;
  37.         $this->ServerPort = (int)$Port;
  38.         $this->Timeout = (int)$Timeout;
  39.  
  40.         if( $ResolveSRV )
  41.         {
  42.             $this->ResolveSRV();
  43.         }
  44.  
  45.         $this->Connect( );
  46.     }
  47.  
  48.     public function __destruct( )
  49.     {
  50.         $this->Close( );
  51.     }
  52.  
  53.     public function Close( )
  54.     {
  55.         if( $this->Socket !== null )
  56.         {
  57.             fclose( $this->Socket );
  58.  
  59.             $this->Socket = null;
  60.         }
  61.     }
  62.  
  63.     public function Connect( )
  64.     {
  65.         $connectTimeout = $this->Timeout;
  66.         $this->Socket = @fsockopen( $this->ServerAddress, $this->ServerPort, $errno, $errstr, $connectTimeout );
  67.  
  68.         if( !$this->Socket )
  69.         {
  70.             $this->Socket = null;
  71.            
  72.             throw new MinecraftPingException( "Failed to connect or create a socket: $errno ($errstr)" );
  73.         }
  74.  
  75.         // Set Read/Write timeout
  76.         stream_set_timeout( $this->Socket, $this->Timeout );
  77.     }
  78.  
  79.     public function Query( )
  80.     {
  81.         $TimeStart = microtime(true); // for read timeout purposes
  82.  
  83.         // See http://wiki.vg/Protocol (Status Ping)
  84.         $Data = "\x00"; // packet ID = 0 (varint)
  85.  
  86.         $Data .= "\x04"; // Protocol version (varint)
  87.         $Data .= Pack( 'c', StrLen( $this->ServerAddress ) ) . $this->ServerAddress; // Server (varint len + UTF-8 addr)
  88.         $Data .= Pack( 'n', $this->ServerPort ); // Server port (unsigned short)
  89.         $Data .= "\x01"; // Next state: status (varint)
  90.  
  91.         $Data = Pack( 'c', StrLen( $Data ) ) . $Data; // prepend length of packet ID + data
  92.  
  93.         fwrite( $this->Socket, $Data ); // handshake
  94.         fwrite( $this->Socket, "\x01\x00" ); // status ping
  95.  
  96.         $Length = $this->ReadVarInt( ); // full packet length
  97.  
  98.         if( $Length < 10 )
  99.         {
  100.             return FALSE;
  101.         }
  102.  
  103.         $this->ReadVarInt( ); // packet type, in server ping it's 0
  104.  
  105.         $Length = $this->ReadVarInt( ); // string length
  106.  
  107.         $Data = "";
  108.         do
  109.         {
  110.             if (microtime(true) - $TimeStart > $this->Timeout)
  111.             {
  112.                 throw new MinecraftPingException( 'Server read timed out' );
  113.             }
  114.  
  115.             $Remainder = $Length - StrLen( $Data );
  116.             $block = fread( $this->Socket, $Remainder ); // and finally the json string
  117.             // abort if there is no progress
  118.             if (!$block)
  119.             {
  120.                 throw new MinecraftPingException( 'Server returned too few data' );
  121.             }
  122.  
  123.             $Data .= $block;
  124.         } while( StrLen($Data) < $Length );
  125.  
  126.         if( $Data === FALSE )
  127.         {
  128.             throw new MinecraftPingException( 'Server didn\'t return any data' );
  129.         }
  130.  
  131.         $Data = JSON_Decode( $Data, true );
  132.  
  133.         if( JSON_Last_Error( ) !== JSON_ERROR_NONE )
  134.         {
  135.             if( Function_Exists( 'json_last_error_msg' ) )
  136.             {
  137.                 throw new MinecraftPingException( JSON_Last_Error_Msg( ) );
  138.             }
  139.             else
  140.             {
  141.                 throw new MinecraftPingException( 'JSON parsing failed' );
  142.             }
  143.  
  144.             return FALSE;
  145.         }
  146.  
  147.         return $Data;
  148.     }
  149.  
  150.     public function QueryOldPre17( )
  151.     {
  152.         fwrite( $this->Socket, "\xFE\x01" );
  153.         $Data = fread( $this->Socket, 512 );
  154.         $Len = StrLen( $Data );
  155.  
  156.         if( $Len < 4 || $Data[ 0 ] !== "\xFF" )
  157.         {
  158.             return FALSE;
  159.         }
  160.  
  161.         $Data = SubStr( $Data, 3 ); // Strip packet header (kick message packet and short length)
  162.         $Data = iconv( 'UTF-16BE', 'UTF-8', $Data );
  163.  
  164.         // Are we dealing with Minecraft 1.4+ server?
  165.         if( $Data[ 1 ] === "\xA7" && $Data[ 2 ] === "\x31" )
  166.         {
  167.             $Data = Explode( "\x00", $Data );
  168.  
  169.             return Array(
  170.                 'HostName'   => $Data[ 3 ],
  171.                 'Players'    => IntVal( $Data[ 4 ] ),
  172.                 'MaxPlayers' => IntVal( $Data[ 5 ] ),
  173.                 'Protocol'   => IntVal( $Data[ 1 ] ),
  174.                 'Version'    => $Data[ 2 ]
  175.             );
  176.         }
  177.  
  178.         $Data = Explode( "\xA7", $Data );
  179.  
  180.         return Array(
  181.             'HostName'   => SubStr( $Data[ 0 ], 0, -1 ),
  182.             'Players'    => isset( $Data[ 1 ] ) ? IntVal( $Data[ 1 ] ) : 0,
  183.             'MaxPlayers' => isset( $Data[ 2 ] ) ? IntVal( $Data[ 2 ] ) : 0,
  184.             'Protocol'   => 0,
  185.             'Version'    => '1.3'
  186.         );
  187.     }
  188.  
  189.     private function ReadVarInt( )
  190.     {
  191.         $i = 0;
  192.         $j = 0;
  193.  
  194.         while( true )
  195.         {
  196.             $k = @fgetc( $this->Socket );
  197.  
  198.             if( $k === FALSE )
  199.             {
  200.                 return 0;
  201.             }
  202.  
  203.             $k = Ord( $k );
  204.  
  205.             $i |= ( $k & 0x7F ) << $j++ * 7;
  206.  
  207.             if( $j > 5 )
  208.             {
  209.                 throw new MinecraftPingException( 'VarInt too big' );
  210.             }
  211.  
  212.             if( ( $k & 0x80 ) != 128 )
  213.             {
  214.                 break;
  215.             }
  216.         }
  217.  
  218.         return $i;
  219.     }
  220.  
  221.     private function ResolveSRV()
  222.     {
  223.         if( ip2long( $this->ServerAddress ) !== false )
  224.         {
  225.             return;
  226.         }
  227.  
  228.         $Record = @dns_get_record( '_minecraft._tcp.' . $this->ServerAddress, DNS_SRV );
  229.  
  230.         if( empty( $Record ) )
  231.         {
  232.             return;
  233.         }
  234.  
  235.         if( isset( $Record[ 0 ][ 'target' ] ) )
  236.         {
  237.             $this->ServerAddress = $Record[ 0 ][ 'target' ];
  238.         }
  239.  
  240.         if( isset( $Record[ 0 ][ 'port' ] ) )
  241.         {
  242.             $this->ServerPort = $Record[ 0 ][ 'port' ];
  243.         }
  244.     }
  245. }
  246.  
Add Comment
Please, Sign In to add comment