xPaw Minecraft Query

Connect with other users about what to run on your webhosting (and how to run it) here.
Post Reply
richlen99
New to forums
New to forums
Posts: 4
https://www.youtube.com/channel/UC40BgXanDqOYoVCYFDSTfHA
Joined: Wed Jun 22, 2016 9:57 pm

xPaw Minecraft Query

Post by richlen99 »

I'm using a PHP script to query my server that is purchased through NFO, and sometimes the script works but sometimes it doesn't.

I know for a fact that it isn't my server not being on or responding to queries because I use others on the internet and they work just fine. I am using the free webhosting, but as far as I am aware it only restricts inbound requests to the webserver, not the webserver outbound.

I am using fsockopen(), and here is the page I get.

http://www.sentinelsigma.xyz/minecraft/

It says it can open the socket, it just fails to receive the status. Any insight?

here i my code:
(from the page I posted)

Code: Select all

<?php
	use xPaw\MinecraftQuery;
	use xPaw\MinecraftQueryException;
    
    require($_SERVER['DOCUMENT_ROOT'].'/_config/_vars.php');
    
	// Edit this ->
	define( 'MQ_SERVER_ADDR', $mc_ip );
	define( 'MQ_SERVER_PORT', $mc_port );
	define( 'MQ_TIMEOUT', 1 );
	// Edit this <-

	// Display everything in browser, because some people can't look in logs for errors
	Error_Reporting( E_ALL | E_STRICT );
	Ini_Set( 'display_errors', true );

	require $_SERVER['DOCUMENT_ROOT'].'/_assets/query/MinecraftQuery.php';
	require $_SERVER['DOCUMENT_ROOT'].'/_assets/query/MinecraftQueryException.php';

	$Timer = MicroTime( true );

	$Query = new MinecraftQuery( );

	try
	{
		$Query->Connect( MQ_SERVER_ADDR, MQ_SERVER_PORT, MQ_TIMEOUT );
	}
	catch( MinecraftQueryException $e )
	{
		$Exception = $e;
	}

	$Timer = Number_Format( MicroTime( true ) - $Timer, 4, '.', '' );
?>
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="utf-8">
	<title>SentinelSigma Minecraft</title>
    <? include($_SERVER['DOCUMENT_ROOT'].'/_config/_header.php');?>
    <style type="text/css">
    .table thead th {
			background-color: #D9230F;
			border-color: #D9230F !important;
			color: #FFF;
		}
    body{
        background-color: #F5F5F5;
    }
	</style>
    <script>
    function refresh() {
      var img = document.getElementById("refresh");
      img.setAttribute("class", "fa fa-refresh fa-spin");
    }
    </script>
</head>

<body>
    <div name="top"></div>
    <?php include($_SERVER['DOCUMENT_ROOT'].'/_config/_nav.php');?>
    <div class="container" style="padding-top: 30px;">
    <?php if( isset( $Exception ) ): ?>
           <!-- <div class="panel panel-danger">
                <div class="panel-heading"><i style="padding-left:8px;" class="fa fa-exclamation-triangle fa-2x" aria-hidden="true"></i><strong style="font-size:20px;">   Oh no!</strong><?php //echo htmlspecialchars( $Exception->getMessage( ) ); ?></div>
                <p><?php //echo nl2br( $e->getTraceAsString(), false ); ?></p>
                <div style="padding-left:8px; padding-bottom:12px;">
                    <h2>Something went wrong...</h2>
                    <p>The server could be off, or could be experiencing some technical difficulties. Try refreshing!</p>
                    <a style="text-align:right;" onclick="refresh()" href="javascript:history.go(0)" class="btn btn-default"><i class="fa fa-refresh" id="refresh" aria-hidden="true"></i> Refresh</a>
                </div>
            </div>
            -->
		<div class="panel panel-primary">
			<div class="panel-heading"><?php echo htmlspecialchars( $Exception->getMessage( ) ); ?></div>
			<p><?php echo nl2br( $e->getTraceAsString(), false ); ?></p>
		</div>
    <?php else: ?>
		<div class="row">
			<div class="col-sm-6">
				<table class="table table-bordered table-striped table-hover">
					<thead>
						<tr>
							<th colspan="2"><i class="fa fa-dashboard" aria-hidden="true"></i> Server Info <em>(queried in <?php echo $Timer; ?>s)</em></th>
						</tr>
					</thead>
					<tbody>
                <?php if( ( $Info = $Query->GetInfo( ) ) !== false ): ?>
                <?php foreach( $Info as $InfoKey => $InfoValue ): ?>
						<tr class="warning">
							<td><?php echo htmlspecialchars( $InfoKey ); ?></td>
							<td>
                            <?php
                                if( Is_Array( $InfoValue ) )
                                {
                                    echo "<pre>".implode("\n",$InfoValue)."</pre>";
                                }
                                else
                                {
                                    echo htmlspecialchars( $InfoValue );
                                }
                            ?>
                            </td>
						</tr>
            <?php endforeach; ?>
            <?php else: ?>
						<tr>
							<td colspan="2">No information received.</td>
						</tr>
            <?php endif; ?>
					</tbody>
				</table>
			</div>
			<div class="col-sm-6">
				<table class="table table-bordered table-striped table-hover">
					<thead>
						<tr>
							<th><i class="fa fa-users" aria-hidden="true"></i> Players</th>
						</tr>
					</thead>
					<tbody>
            <?php if( ( $Players = $Query->GetPlayers( ) ) !== false ): ?>
            <?php foreach( $Players as $Player ): ?>
						<tr class="warning">
							<td>
                                <?php 
                                        echo "<img style='padding-right:12px;' src='https://minotar.net/helm/$Player/40.png'></img>";
                                        echo "<b>";echo htmlspecialchars( $Player ); echo"</b>";
                                ?>
                            </td>
						</tr>
            <?php endforeach; ?>
            <?php else: ?>
						<tr class="danger">
							<td>No players are connected.</td>
						</tr>
            <?php endif; ?>
					</tbody>
				</table>
                <p style="text-align:center;"><a href="#join" class="btn btn-primary btn-block" role="button" data-toggle="modal" data-target="#serverInfo">Join now!</a></p>

			</div>
        </div>
<!-- Modal -->
    <div class="modal fade" id="serverInfo" tabindex="-1" role="dialog" aria-labelledby="serverInfoLabel" aria-hidden="true">
      <div class="modal-dialog" role="document">
        <div class="modal-content">
          <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
              <span aria-hidden="true">&times;</span>
            </button>
            <h4 class="modal-title" id="serverInfoLabel">Server Address</h4>
          </div>
          <div class="modal-body">
            <h2 style="text-align:center;">Copy and paste the address to your game!</h2>
            <div class="well">
                <h3 style="text-align:center;"><?php echo $mc_ipSRV;?></h1>
            </div>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
          </div>
        </div>
      </div>
    </div>
    <?php include($_SERVER['DOCUMENT_ROOT'].'/_config/_footer.php'); ?>
		</div>
<?php endif; ?>
<?php include($_SERVER['DOCUMENT_ROOT'].'/_config/_lastload.php');?>
</body>
</html>
from the query class:

Code: Select all

<?php

namespace xPaw;

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 )
	{
		if( !is_int( $Timeout ) || $Timeout < 0 )
		{
			throw new \InvalidArgumentException( 'Timeout must be an integer.' );
		}

		$this->Socket = @FSockOpen( 'udp://' . $Ip, (int)$Port, $ErrNo, $ErrStr, $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 );
		}
		// We catch this because we want to close the socket, not very elegant
		catch( MinecraftQueryException $e )
		{
			FClose( $this->Socket );

			throw new MinecraftQueryException( $e->getMessage( ) );
		}

		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( !Array_Key_Exists( $Value, $Keys ) )
				{
					$Last = false;
					continue;
				}

				$Last = $Keys[ $Value ];
				$Info[ $Last ] = '';
			}
			else if( $Last != false )
			{
				$Info[ $Last ] = mb_convert_encoding( $Value, 'UTF-8' );
			}
		}

		// Ints
		$Info[ 'Players' ]    = IntVal( $Info[ 'Players' ] );
		$Info[ 'MaxPlayers' ] = IntVal( $Info[ 'MaxPlayers' ] );
		$Info[ 'HostPort' ]   = IntVal( $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 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 );
	}
}
User avatar
Edge100x
Founder
Founder
Posts: 12945
Joined: Thu Apr 18, 2002 11:04 pm
Location: Seattle
Contact:

Re: xPaw Minecraft Query

Post by Edge100x »

The regular query system is not very good -- it tries to imitate a protocol spec copied from elsewhere but doesn't implement it correctly, resulting in problems such as fragmented packets sometimes going out. Does this problem happen mostly when your server is busy? Have you tried switching over to Minequery?

Right now your script is saying 'Could not create socket: Failed to parse address ""', which may mean that the server IP address is not correctly specified. I have not tried using the library that you're experimenting with, though, and I haven't thoroughly examined your code.
richlen99
New to forums
New to forums
Posts: 4
Joined: Wed Jun 22, 2016 9:57 pm

Re: xPaw Minecraft Query

Post by richlen99 »

the query sometimes works, sometimes doesnt. and i will look into minequery. thanks!
and no, it can happen when busy or not.

and it says it can open the socket, it just fails to receive the status.
Post Reply