@@ -52,6 +52,38 @@ public function Connect( $Ip, $Port = 25565, $Timeout = 3, $ResolveSRV = true )
52
52
}
53
53
}
54
54
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
+
55
87
public function GetInfo ( )
56
88
{
57
89
return isset ( $ this ->Info ) ? $ this ->Info : false ;
@@ -166,6 +198,60 @@ private function GetStatus( $Challenge )
166
198
}
167
199
}
168
200
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
+
169
255
private function WriteData ( $ Command , $ Append = "" )
170
256
{
171
257
$ Command = Pack ( 'c* ' , 0xFE , 0xFD , $ Command , 0x01 , 0x02 , 0x03 , 0x04 ) . $ Append ;
0 commit comments