summaryrefslogtreecommitdiff
path: root/web/minecraft.php
diff options
context:
space:
mode:
authornavewindre <boneyaard@gmail.com>2025-11-11 08:11:24 +0100
committernavewindre <boneyaard@gmail.com>2025-11-11 08:11:24 +0100
commitf5e29189f70c5c8532916504a1a22f8c586f6e73 (patch)
tree9bf42144e608260527766e128268b380231ed95b /web/minecraft.php
parent6442494822d12c23cdd609031c4039d3309b64f6 (diff)
new web
Diffstat (limited to 'web/minecraft.php')
-rw-r--r--web/minecraft.php300
1 files changed, 0 insertions, 300 deletions
diff --git a/web/minecraft.php b/web/minecraft.php
deleted file mode 100644
index 4f9dbc7..0000000
--- a/web/minecraft.php
+++ /dev/null
@@ -1,300 +0,0 @@
-<?php
-
-class MinecraftQuery
-{
- /*
- * Class written by xPaw
- *
- * Website: http://xpaw.me
- * GitHub: https://github.com/xPaw/PHP-Minecraft-Query
- */
-
- const STATISTIC = 0x00;
- const HANDSHAKE = 0x09;
-
- private $Socket;
- private $Players;
- private $Info;
-
- public function Connect( $Ip, $Port = 25565, $Timeout = 3, $ResolveSRV = true )
- {
- if( !is_int( $Timeout ) || $Timeout < 0 )
- {
- throw new \InvalidArgumentException( 'Timeout must be an integer.' );
- }
-
- if( $ResolveSRV )
- {
- $this->ResolveSRV( $Ip, $Port );
- }
-
- $this->Socket = @\fsockopen( 'udp://' . $Ip, (int)$Port, $ErrNo, $ErrStr, (float)$Timeout );
-
- if( $ErrNo || $this->Socket === false )
- {
- throw new MinecraftQueryException( 'Could not create socket: ' . $ErrStr );
- }
-
- \stream_set_timeout( $this->Socket, $Timeout );
- \stream_set_blocking( $this->Socket, true );
-
- try
- {
- $Challenge = $this->GetChallenge( );
-
- $this->GetStatus( $Challenge );
- }
- finally
- {
- \fclose( $this->Socket );
- }
- }
-
- public function ConnectBedrock( $Ip, $Port = 19132, $Timeout = 3, $ResolveSRV = true )
- {
- if( !is_int( $Timeout ) || $Timeout < 0 )
- {
- throw new \InvalidArgumentException( 'Timeout must be an integer.' );
- }
-
- if( $ResolveSRV )
- {
- $this->ResolveSRV( $Ip, $Port );
- }
-
- $this->Socket = @\fsockopen( 'udp://' . $Ip, (int)$Port, $ErrNo, $ErrStr, (float)$Timeout );
-
- if( $ErrNo || $this->Socket === false )
- {
- throw new MinecraftQueryException( 'Could not create socket: ' . $ErrStr );
- }
-
- \stream_set_timeout( $this->Socket, $Timeout );
- \stream_set_blocking( $this->Socket, true );
-
- try
- {
- $this->GetBedrockStatus();
- }
- finally
- {
- \fclose( $this->Socket );
- }
- }
-
- public function GetInfo( )
- {
- return isset( $this->Info ) ? $this->Info : false;
- }
-
- public function GetPlayers( )
- {
- return isset( $this->Players ) ? $this->Players : false;
- }
-
- private function GetChallenge( )
- {
- $Data = $this->WriteData( self :: HANDSHAKE );
-
- if( $Data === false )
- {
- throw new MinecraftQueryException( 'Failed to receive challenge.' );
- }
-
- return \pack( 'N', $Data );
- }
-
- private function GetStatus( $Challenge )
- {
- $Data = $this->WriteData( self :: STATISTIC, $Challenge . \pack( 'c*', 0x00, 0x00, 0x00, 0x00 ) );
-
- if( !$Data )
- {
- throw new MinecraftQueryException( 'Failed to receive status.' );
- }
-
- $Last = '';
- $Info = Array( );
-
- $Data = \substr( $Data, 11 ); // splitnum + 2 int
- $Data = \explode( "\x00\x00\x01player_\x00\x00", $Data );
-
- if( \count( $Data ) !== 2 )
- {
- throw new MinecraftQueryException( 'Failed to parse server\'s response.' );
- }
-
- $Players = \substr( $Data[ 1 ], 0, -2 );
- $Data = \explode( "\x00", $Data[ 0 ] );
-
- // Array with known keys in order to validate the result
- // It can happen that server sends custom strings containing bad things (who can know!)
- $Keys = Array(
- 'hostname' => 'HostName',
- 'gametype' => 'GameType',
- 'version' => 'Version',
- 'plugins' => 'Plugins',
- 'map' => 'Map',
- 'numplayers' => 'Players',
- 'maxplayers' => 'MaxPlayers',
- 'hostport' => 'HostPort',
- 'hostip' => 'HostIp',
- 'game_id' => 'GameName'
- );
-
- foreach( $Data as $Key => $Value )
- {
- if( ~$Key & 1 )
- {
- if( !isset( $Keys[ $Value ] ) )
- {
- $Last = false;
- continue;
- }
-
- $Last = $Keys[ $Value ];
- $Info[ $Last ] = '';
- }
- else if( $Last != false )
- {
- $Info[ $Last ] = mb_convert_encoding( $Value, 'UTF-8' );
- }
- }
-
- // Ints
- $Info[ 'Players' ] = (int)$Info[ 'Players' ];
- $Info[ 'MaxPlayers' ] = (int)$Info[ 'MaxPlayers' ];
- $Info[ 'HostPort' ] = (int)$Info[ 'HostPort' ];
-
- // Parse "plugins", if any
- if( $Info[ 'Plugins' ] )
- {
- $Data = \explode( ": ", $Info[ 'Plugins' ], 2 );
-
- $Info[ 'RawPlugins' ] = $Info[ 'Plugins' ];
- $Info[ 'Software' ] = $Data[ 0 ];
-
- if( \count( $Data ) == 2 )
- {
- $Info[ 'Plugins' ] = \explode( "; ", $Data[ 1 ] );
- }
- }
- else
- {
- $Info[ 'Software' ] = 'Vanilla';
- }
-
- $this->Info = $Info;
-
- if( empty( $Players ) )
- {
- $this->Players = null;
- }
- else
- {
- $this->Players = \explode( "\x00", $Players );
- }
- }
-
- private function GetBedrockStatus( )
- {
- // hardcoded magic https://github.com/facebookarchive/RakNet/blob/1a169895a900c9fc4841c556e16514182b75faf8/Source/RakPeer.cpp#L135
- $OFFLINE_MESSAGE_DATA_ID = \pack( 'c*', 0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE, 0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78 );
-
- $Command = \pack( 'cQ', 0x01, time() ); // DefaultMessageIDTypes::ID_UNCONNECTED_PING + 64bit current time
- $Command .= $OFFLINE_MESSAGE_DATA_ID;
- $Command .= \pack( 'Q', 2 ); // 64bit guid
- $Length = \strlen( $Command );
-
- if( $Length !== \fwrite( $this->Socket, $Command, $Length ) )
- {
- throw new MinecraftQueryException( "Failed to write on socket." );
- }
-
- $Data = \fread( $this->Socket, 4096 );
-
- if( empty( $Data ) )
- {
- throw new MinecraftQueryException( "Failed to read from socket." );
- }
-
- if( $Data[ 0 ] !== "\x1C" ) // DefaultMessageIDTypes::ID_UNCONNECTED_PONG
- {
- throw new MinecraftQueryException( "First byte is not ID_UNCONNECTED_PONG." );
- }
-
- if( \substr( $Data, 17, 16 ) !== $OFFLINE_MESSAGE_DATA_ID )
- {
- throw new MinecraftQueryException( "Magic bytes do not match." );
- }
-
- // TODO: What are the 2 bytes after the magic?
- $Data = \substr( $Data, 35 );
-
- // TODO: If server-name contains a ';' it is not escaped, and will break this parsing
- $Data = \explode( ';', $Data );
-
- $this->Info =
- [
- 'GameName' => $Data[ 0 ] ?? null,
- 'HostName' => $Data[ 1 ] ?? null,
- 'Protocol' => $Data[ 2 ] ?? null,
- 'Version' => $Data[ 3 ] ?? null,
- 'Players' => isset( $Data[ 4 ] ) ? (int)$Data[ 4 ] : 0,
- 'MaxPlayers' => isset( $Data[ 5 ] ) ? (int)$Data[ 5 ] : 0,
- 'ServerId' => $Data[ 6 ] ?? null,
- 'Map' => $Data[ 7 ] ?? null,
- 'GameMode' => $Data[ 8 ] ?? null,
- 'NintendoLimited' => $Data[ 9 ] ?? null,
- 'IPv4Port' => isset( $Data[ 10 ] ) ? (int)$Data[ 10 ] : 0,
- 'IPv6Port' => isset( $Data[ 11 ] ) ? (int)$Data[ 11 ] : 0,
- 'Extra' => $Data[ 12 ] ?? null, // What is this?
- ];
- $this->Players = null;
- }
-
- private function WriteData( $Command, $Append = "" )
- {
- $Command = \pack( 'c*', 0xFE, 0xFD, $Command, 0x01, 0x02, 0x03, 0x04 ) . $Append;
- $Length = \strlen( $Command );
-
- if( $Length !== \fwrite( $this->Socket, $Command, $Length ) )
- {
- throw new MinecraftQueryException( "Failed to write on socket." );
- }
-
- $Data = \fread( $this->Socket, 4096 );
-
- if( $Data === false )
- {
- throw new MinecraftQueryException( "Failed to read from socket." );
- }
-
- if( \strlen( $Data ) < 5 || $Data[ 0 ] != $Command[ 2 ] )
- {
- return false;
- }
-
- return \substr( $Data, 5 );
- }
-
- private function ResolveSRV( &$Address, &$Port )
- {
- if( \ip2long( $Address ) !== false )
- {
- return;
- }
-
- $Record = @\dns_get_record( '_minecraft._tcp.' . $Address, DNS_SRV );
-
- if( empty( $Record ) )
- {
- return;
- }
-
- if( isset( $Record[ 0 ][ 'target' ] ) )
- {
- $Address = $Record[ 0 ][ 'target' ];
- }
- }
-}