@@ -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