Skip to content

Commit 8810729

Browse files
committed
Implement querying MCPE servers
Fixes #129 Fixes #141
1 parent 2b50d83 commit 8810729

File tree

2 files changed

+88
-0
lines changed

2 files changed

+88
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,7 @@ If the server has query enabled (`enable-query`), then you can use `MinecraftQue
8686
?>
8787
```
8888

89+
For Bedrock servers (MCPE) use `ConnectBedrock` function instead of `Connect`, then `GetInfo` will work.
90+
8991
## License
9092
[MIT](LICENSE)

src/MinecraftQuery.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,38 @@ public function Connect( $Ip, $Port = 25565, $Timeout = 3, $ResolveSRV = true )
5252
}
5353
}
5454

55+
public function ConnectBedrock( $Ip, $Port = 19132, $Timeout = 3, $ResolveSRV = true )
56+
{
57+
if( !is_int( $Timeout ) || $Timeout < 0 )
58+
{
59+
throw new \InvalidArgumentException( 'Timeout must be an integer.' );
60+
}
61+
62+
if( $ResolveSRV )
63+
{
64+
$this->ResolveSRV( $Ip, $Port );
65+
}
66+
67+
$this->Socket = @\fsockopen( 'udp://' . $Ip, (int)$Port, $ErrNo, $ErrStr, $Timeout );
68+
69+
if( $ErrNo || $this->Socket === false )
70+
{
71+
throw new MinecraftQueryException( 'Could not create socket: ' . $ErrStr );
72+
}
73+
74+
\stream_set_timeout( $this->Socket, $Timeout );
75+
\stream_set_blocking( $this->Socket, true );
76+
77+
try
78+
{
79+
$this->GetBedrockStatus();
80+
}
81+
finally
82+
{
83+
FClose( $this->Socket );
84+
}
85+
}
86+
5587
public function GetInfo( )
5688
{
5789
return isset( $this->Info ) ? $this->Info : false;
@@ -166,6 +198,60 @@ private function GetStatus( $Challenge )
166198
}
167199
}
168200

201+
private function GetBedrockStatus( )
202+
{
203+
// hardcoded magic https://github.com/facebookarchive/RakNet/blob/1a169895a900c9fc4841c556e16514182b75faf8/Source/RakPeer.cpp#L135
204+
$OFFLINE_MESSAGE_DATA_ID = \pack( 'c*', 0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78 );
205+
206+
$Command = \pack( 'cQ', 0x01, time() ); // DefaultMessageIDTypes::ID_UNCONNECTED_PING + 64bit current time
207+
$Command .= $OFFLINE_MESSAGE_DATA_ID;
208+
$Command .= \pack( 'Q', 2 ); // 64bit guid
209+
$Length = \strlen( $Command );
210+
211+
if( $Length !== \fwrite( $this->Socket, $Command, $Length ) )
212+
{
213+
throw new MinecraftQueryException( "Failed to write on socket." );
214+
}
215+
216+
$Data = \fread( $this->Socket, 4096 );
217+
218+
if( $Data === false )
219+
{
220+
throw new MinecraftQueryException( "Failed to read from socket." );
221+
}
222+
223+
if( $Data[ 0 ] !== "\x1C" ) // DefaultMessageIDTypes::ID_UNCONNECTED_PONG
224+
{
225+
throw new MinecraftQueryException( "First byte is not ID_UNCONNECTED_PONG." );
226+
}
227+
228+
if( \substr( $Data, 17, 16 ) !== $OFFLINE_MESSAGE_DATA_ID )
229+
{
230+
throw new MinecraftQueryException( "Magic bytes do not match." );
231+
}
232+
233+
// TODO: What are the 2 bytes after the magic?
234+
$Data = \substr( $Data, 35 );
235+
236+
// TODO: If server-name contains a ';' it is not escaped, and will break this parsing
237+
$Data = \explode( ';', $Data );
238+
239+
$this->Info =
240+
[
241+
'GameName' => $Data[ 0 ],
242+
'HostName' => $Data[ 1 ],
243+
'Unknown1' => $Data[ 2 ], // TODO: What is this?
244+
'Version' => $Data[ 3 ],
245+
'Players' => $Data[ 4 ],
246+
'MaxPlayers' => $Data[ 5 ],
247+
'Unknown2' => $Data[ 6 ], // TODO: What is this?
248+
'Map' => $Data[ 7 ],
249+
'GameMode' => $Data[ 8 ],
250+
'Unknown3' => $Data[ 9 ], // TODO: What is this?
251+
];
252+
$this->Players = null;
253+
}
254+
169255
private function WriteData( $Command, $Append = "" )
170256
{
171257
$Command = Pack( 'c*', 0xFE, 0xFD, $Command, 0x01, 0x02, 0x03, 0x04 ) . $Append;

0 commit comments

Comments
 (0)