├── LICENSE
├── README.md
└── src
├── MinecraftPing.php
├── MinecraftPingException.php
├── MinecraftSkin.php
├── faces
└── .gitkeep
└── index.php
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 mgp25
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Minecraft Server Status
2 |
3 | This library can be used to query Minecraft servers for some basic information and player's skin.
4 |
5 | You will be able to retrieve:
6 |
7 | - Server's version
8 | - Number of players online
9 | - Server status
10 | - Server's description
11 | - Server's image
12 | - Mods installed in the server
13 | - Player's skin and face
14 |
15 | **Using:** `PHP-Minecraft-Query from xPaw`
16 |
17 | ### Examples
18 | ```php
19 | Query() );
27 | }
28 | catch( MinecraftPingException $e )
29 | {
30 | echo $e->getMessage();
31 | }
32 | finally
33 | {
34 | $Query->Close();
35 | }
36 | ```
37 |
38 | ```php
39 | getFace('mgp25'); // image is downloaded and stored in /faces, path is returned
44 | ```
45 |
--------------------------------------------------------------------------------
/src/MinecraftPing.php:
--------------------------------------------------------------------------------
1 | Query();
23 | * echo '';
24 | *
25 | */
26 |
27 | private $Socket;
28 | private $ServerAddress;
29 | private $ServerPort;
30 | private $Timeout;
31 |
32 | public function __construct( $Address, $Port = 25565, $Timeout = 2 )
33 | {
34 | $this->ServerAddress = $Address;
35 | $this->ServerPort = (int)$Port;
36 | $this->Timeout = (int)$Timeout;
37 |
38 | $this->Connect( );
39 | }
40 |
41 | public function __destruct( )
42 | {
43 | $this->Close( );
44 | }
45 |
46 | public function Close( )
47 | {
48 | if( $this->Socket !== null )
49 | {
50 | fclose( $this->Socket );
51 |
52 | $this->Socket = null;
53 | }
54 | }
55 |
56 | public function Connect( )
57 | {
58 | $connectTimeout = $this->Timeout;
59 | $this->Socket = @fsockopen( $this->ServerAddress, $this->ServerPort, $errno, $errstr, $connectTimeout );
60 |
61 | if( !$this->Socket )
62 | {
63 | throw new MinecraftPingException( "Failed to connect or create a socket: $errno ($errstr)" );
64 | }
65 |
66 | // Set Read/Write timeout
67 | stream_set_timeout( $this->Socket, $this->Timeout );
68 | }
69 |
70 | public function Query( )
71 | {
72 | $TimeStart = microtime(true); // for read timeout purposes
73 |
74 | // See http://wiki.vg/Protocol (Status Ping)
75 | $Data = "\x00"; // packet ID = 0 (varint)
76 |
77 | $Data .= "\x04"; // Protocol version (varint)
78 | $Data .= Pack( 'c', StrLen( $this->ServerAddress ) ) . $this->ServerAddress; // Server (varint len + UTF-8 addr)
79 | $Data .= Pack( 'n', $this->ServerPort ); // Server port (unsigned short)
80 | $Data .= "\x01"; // Next state: status (varint)
81 |
82 | $Data = Pack( 'c', StrLen( $Data ) ) . $Data; // prepend length of packet ID + data
83 |
84 | fwrite( $this->Socket, $Data ); // handshake
85 | fwrite( $this->Socket, "\x01\x00" ); // status ping
86 |
87 | $Length = $this->ReadVarInt( ); // full packet length
88 |
89 | if( $Length < 10 )
90 | {
91 | return FALSE;
92 | }
93 |
94 | fgetc( $this->Socket ); // packet type, in server ping it's 0
95 |
96 | $Length = $this->ReadVarInt( ); // string length
97 |
98 | $Data = "";
99 | do
100 | {
101 | if (microtime(true) - $TimeStart > $this->Timeout)
102 | {
103 | throw new MinecraftPingException( 'Server read timed out' );
104 | }
105 |
106 | $Remainder = $Length - StrLen( $Data );
107 | $block = fread( $this->Socket, $Remainder ); // and finally the json string
108 | // abort if there is no progress
109 | if (!$block)
110 | {
111 | throw new MinecraftPingException( 'Server returned too few data' );
112 | }
113 |
114 | $Data .= $block;
115 | } while( StrLen($Data) < $Length );
116 |
117 | if( $Data === FALSE )
118 | {
119 | throw new MinecraftPingException( 'Server didn\'t return any data' );
120 | }
121 |
122 | $Data = JSON_Decode( $Data, true );
123 |
124 | if( JSON_Last_Error( ) !== JSON_ERROR_NONE )
125 | {
126 | if( Function_Exists( 'json_last_error_msg' ) )
127 | {
128 | throw new MinecraftPingException( JSON_Last_Error_Msg( ) );
129 | }
130 | else
131 | {
132 | throw new MinecraftPingException( 'JSON parsing failed' );
133 | }
134 |
135 | return FALSE;
136 | }
137 |
138 | return $Data;
139 | }
140 |
141 | public function QueryOldPre17( )
142 | {
143 | fwrite( $this->Socket, "\xFE\x01" );
144 | $Data = fread( $this->Socket, 512 );
145 | $Len = StrLen( $Data );
146 |
147 | if( $Len < 4 || $Data[ 0 ] !== "\xFF" )
148 | {
149 | return FALSE;
150 | }
151 |
152 | $Data = SubStr( $Data, 3 ); // Strip packet header (kick message packet and short length)
153 | $Data = iconv( 'UTF-16BE', 'UTF-8', $Data );
154 |
155 | // Are we dealing with Minecraft 1.4+ server?
156 | if( $Data[ 1 ] === "\xA7" && $Data[ 2 ] === "\x31" )
157 | {
158 | $Data = Explode( "\x00", $Data );
159 |
160 | return Array(
161 | 'HostName' => $Data[ 3 ],
162 | 'Players' => IntVal( $Data[ 4 ] ),
163 | 'MaxPlayers' => IntVal( $Data[ 5 ] ),
164 | 'Protocol' => IntVal( $Data[ 1 ] ),
165 | 'Version' => $Data[ 2 ]
166 | );
167 | }
168 |
169 | $Data = Explode( "\xA7", $Data );
170 |
171 | return Array(
172 | 'HostName' => SubStr( $Data[ 0 ], 0, -1 ),
173 | 'Players' => isset( $Data[ 1 ] ) ? IntVal( $Data[ 1 ] ) : 0,
174 | 'MaxPlayers' => isset( $Data[ 2 ] ) ? IntVal( $Data[ 2 ] ) : 0,
175 | 'Protocol' => 0,
176 | 'Version' => '1.3'
177 | );
178 | }
179 |
180 | private function ReadVarInt( )
181 | {
182 | $i = 0;
183 | $j = 0;
184 |
185 | while( true )
186 | {
187 | $k = @fgetc( $this->Socket );
188 |
189 | if( $k === FALSE )
190 | {
191 | return 0;
192 | }
193 |
194 | $k = Ord( $k );
195 |
196 | $i |= ( $k & 0x7F ) << $j++ * 7;
197 |
198 | if( $j > 5 )
199 | {
200 | throw new MinecraftPingException( 'VarInt too big' );
201 | }
202 |
203 | if( ( $k & 0x80 ) != 128 )
204 | {
205 | break;
206 | }
207 | }
208 |
209 | return $i;
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/MinecraftPingException.php:
--------------------------------------------------------------------------------
1 | getSteveSkin();
33 | }
34 | return $output;
35 | }
36 |
37 | public function getSteveSkin()
38 | {
39 | // Default Steve Skin: https://minecraft.net/images/steve.png
40 | $output = 'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAFDUlEQVR42u2a20sUURzH97G0LKMotPuWbVpslj1olJ';
41 | $output .= 'XdjCgyisowsSjzgrB0gSKyC5UF1ZNQWEEQSBQ9dHsIe+zJ/+nXfM/sb/rN4ZwZ96LOrnPgyxzP/M7Z+X7OZc96JpE';
42 | $output .= 'ISfWrFhK0YcU8knlozeJKunE4HahEqSc2nF6zSEkCgGCyb+82enyqybtCZQWAzdfVVFgBJJNJn1BWFgC49/VpwGVl';
43 | $output .= 'D0CaxQiA5HSYEwBM5sMAdKTqygcAG9+8coHKY/XXAZhUNgDYuBSPjJL/GkzVVhAEU5tqK5XZ7cnFtHWtq/TahdSw2';
44 | $output .= 'l0HUisr1UKIWJQBAMehDuqiDdzndsP2EZECAG1ZXaWMwOCODdXqysLf++uXUGv9MhUHIByDOijjdiSAoH3ErANQD7';
45 | $output .= '3C7TXXuGOsFj1d4YH4OTJAEy8y9Hd0mCaeZ5z8dfp88zw1bVyiYhCLOg1ZeAqC0ybaDttHRGME1DhDeVWV26u17lR';
46 | $output .= 'APr2+mj7dvULfHw2q65fhQRrLXKDfIxkau3ZMCTGIRR3URR5toU38HbaPiMwUcKfBAkoun09PzrbQ2KWD1JJaqswj';
47 | $output .= 'deweoR93rirzyCMBCmIQizqoizZkm2H7iOgAcHrMHbbV9KijkUYv7qOn55sdc4fo250e+vUg4329/Xk6QB/6DtOws';
48 | $output .= '+dHDGJRB3XRBve+XARt+4hIrAF4UAzbnrY0ve07QW8uHfB+0LzqanMM7qVb+3f69LJrD90/1axiEIs6qIs21BTITo';
49 | $output .= 'ewfcSsA+Bfb2x67OoR1aPPzu2i60fSNHRwCw221Suz0O3jO+jh6V1KyCMGse9721XdN5ePutdsewxS30cwuMjtC86';
50 | $output .= '0T5JUKpXyKbSByUn7psi5l+juDlZYGh9324GcPKbkycaN3jUSAGxb46IAYPNZzW0AzgiQ5tVnzLUpUDCAbakMQXXr';
51 | $output .= 'OtX1UMtHn+Q9/X5L4wgl7t37r85OSrx+TYl379SCia9KXjxRpiTjIZTBFOvrV1f8ty2eY/T7XJ81FQAwmA8ASH1ob';
52 | $output .= '68r5PnBsxA88/xAMh6SpqW4HRnLBrkOA9Xv5wPAZjAUgOkB+SHxgBgR0qSMh0zmZRsmwDJm1gFg2PMDIC8/nAHIMl';
53 | $output .= 's8x8GgzOsG5WiaqREgYzDvpTwjLDy8NM15LpexDEA3LepjU8Z64my+8PtDCmUyRr+fFwA2J0eAFYA0AxgSgMmYBMZ';
54 | $output .= 'TwFQnO9RNAEaHOj2DXF5UADmvAToA2ftyxZYA5BqgmZZApDkdAK4mAKo8GzPlr8G8AehzMAyA/i1girUA0HtYB2Ca';
55 | $output .= 'IkUBEHQ/cBHSvwF0AKZFS5M0ZwMQtEaEAmhtbSUoDADH9ff3++QZ4o0I957e+zYAMt6wHkhzpjkuAcgpwNcpA7AZD';
56 | $output .= 'LsvpwiuOkBvxygA6Bsvb0HlaeKIF2EbADZpGiGzBsA0gnwQHGOhW2snRpbpPexbAB2Z1oicAMQpTnGKU5ziFKc4xS';
57 | $output .= 'lOcYpTnOIUpzgVmgo+XC324WfJAdDO/+ceADkCpuMFiFKbApEHkOv7BfzfXt+5gpT8V7rpfYJcDz+jAsB233r6yyB';
58 | $output .= 'sJ0mlBCDofuBJkel4vOwBFPv8fyYAFPJ+wbSf/88UANNRVy4Awo6+Ig2gkCmgA5DHWjoA+X7AlM//owLANkX0w035';
59 | $output .= '9od++pvX8fdMAcj3/QJ9iJsAFPQCxHSnQt8vMJ3v2wCYpkhkAOR7vG7q4aCXoMoSgG8hFAuc/grMdAD4B/kHl9da7';
60 | $output .= 'Ne9AAAAAElFTkSuQmCC';
61 | $output = base64_decode($output);
62 |
63 | return $output;
64 | }
65 |
66 | public function getFace($user = 'steve')
67 | {
68 | if (!file_exists(__DIR__ . '/faces/' . $user . '_face.png')) {
69 | $size = isset($_GET['s']) ? max(8, min(250, $_GET['s'])) : 48;
70 | $view = isset($_GET['v']) ? substr($_GET['v'], 0, 1) : 'f';
71 | $view = in_array($view, array('f', 'l', 'r', 'b')) ? $view : 'f';
72 |
73 | $skin = $this->get_skin($user);
74 | $im = imagecreatefromstring($skin);
75 | $av = imagecreatetruecolor($size, $size);
76 |
77 | $x = array('f' => 8, 'l' => 16, 'r' => 0, 'b' => 24);
78 |
79 | imagecopyresized($av, $im, 0, 0, $x[$view], 8, $size, $size, 8, 8); // Face
80 | imagecolortransparent($im, imagecolorat($im, 63, 0)); // Black Hat Issue
81 | imagecopyresized($av, $im, 0, 0, $x[$view] + 32, 8, $size, $size, 8, 8); // Accessories
82 |
83 | imagepng($av, __DIR__."/faces/" . $user . '_face.png');
84 |
85 | imagedestroy($im);
86 | imagedestroy($av);
87 | }
88 |
89 | return "/faces/$user" . '_face.png';
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/faces/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgp25/Minecraft-Server-Status/b1a8840dd78a61742cc6fef28d91e565cc370799/src/faces/.gitkeep
--------------------------------------------------------------------------------
/src/index.php:
--------------------------------------------------------------------------------
1 | getFace($player);
19 | }
20 | try
21 | {
22 | $Query = new MinecraftPing( $SERVER_IP, $PORT );
23 |
24 | $data = $Query->Query();
25 | $status = 'online';
26 | }
27 | catch( MinecraftPingException $e )
28 | {
29 | echo $e->getMessage();
30 | $status = 'offline';
31 | }
32 | finally
33 | {
34 | $Query->Close();
35 | }
36 |
37 | ?>
38 |
39 |
40 |
IP | 71 |72 | |
Version | 75 |76 | |
Players | 79 |80 | |
Status | 83 |if($status == 'online') { echo " Server is online"; } else { echo " Server is offline";}?> | 84 |
Description | 87 |88 | | Server image | 90 |'; ?> | 91 | 92 |Mods | 93 |95 | 96 | 97 | |