commit 98aaf7333ef495f8b3b03e4da030261a526f41c2 Author: Brendan Golden Date: Sun Oct 1 02:36:07 2023 +0100 initial commit Signed-off-by: Brendan Golden diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5a2b261 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,55 @@ +# Borrowed from: https://gitlab.skynet.ie/compsoc/skynet/website/2023/-/blob/main/.gitattributes +# Documents +*.pdf filter=lfs diff=lfs merge=lfs -text +*.doc filter=lfs diff=lfs merge=lfs -text +*.docx filter=lfs diff=lfs merge=lfs -text + + +# Excel +*.xls filter=lfs diff=lfs merge=lfs -text +*.xlsx filter=lfs diff=lfs merge=lfs -text +*.xlsm filter=lfs diff=lfs merge=lfs -text + + +# Powerpoints +*.ppt filter=lfs diff=lfs merge=lfs -text +*.pptx filter=lfs diff=lfs merge=lfs -text +*.ppsx filter=lfs diff=lfs merge=lfs -text + + +# Images +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text + +# Fonts +*.ttf filter=lfs diff=lfs merge=lfs -text +*.eot filter=lfs diff=lfs merge=lfs -text +*.woff filter=lfs diff=lfs merge=lfs -text +*.woff2 filter=lfs diff=lfs merge=lfs -text + +# Video +*.mkv filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.wmv filter=lfs diff=lfs merge=lfs -text + + +# Misc +*.zip filter=lfs diff=lfs merge=lfs -text + + +# ET4011 +*.cbe filter=lfs diff=lfs merge=lfs -text +*.pbs filter=lfs diff=lfs merge=lfs -text + + +# Open/Libre office +# from https://www.libreoffice.org/discover/what-is-opendocument/ +*.odt filter=lfs diff=lfs merge=lfs -text +*.ods filter=lfs diff=lfs merge=lfs -text +*.odp filter=lfs diff=lfs merge=lfs -text +*.odg filter=lfs diff=lfs merge=lfs -text + + +# QT +*.ui filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d4f86b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/.idea +boilerplate.html +result +/result \ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..f0cd4cc --- /dev/null +++ b/css/main.css @@ -0,0 +1,78 @@ +/* CSS is the sound you don't want to hear from a creeper */ + +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + background: url(../img/bg.jpg) repeat-x #000; +} + +#container { + width: 490px; + margin: 30px auto; +} + +h3 { + margin: 0; + padding: 0; + color: #eee; +} + +p, ol p { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: 12px 0 0 0; + padding: 0; + line-height: 150%; + font-size: 14px; + font-style: normal; + color: #aaa; +} + +ol { + font: italic 1em Georgia, Times, serif; + color: #999999; +} + +ol p { + color: #ccc; +} + +listitem { + margin: 12px 0; +} + +em { + font-style: normal; + color: #eee; +} + +a { + color: #a0cee5; + text-decoration: none; + font-weight: normal; + font-style: normal; + border-bottom: 1px dashed #666; +} + +a:hover { + color: #a6d4ea; +} + +.hint { + font-size: small; + color: #aaa; +} + +.map { + text-transform: uppercase; + font-size: small; + color: #666; +} + +#info { + color: #cbcbcb; + border: 1px solid #444; + text-align: left; + text-shadow: #000 0px 1px 3px; + background-color: #2c2c2c; + margin-top: 20px; + padding: 14px; +} \ No newline at end of file diff --git a/img/CompSoc_Logo_PNG.png b/img/CompSoc_Logo_PNG.png new file mode 100644 index 0000000..053a503 --- /dev/null +++ b/img/CompSoc_Logo_PNG.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ff9b08e03e7555618bc08fa4ed04076ec835bc1525b1a8efc7ec7ac1c9548308 +size 279790 diff --git a/img/bg.jpg b/img/bg.jpg new file mode 100644 index 0000000..0f8ef88 --- /dev/null +++ b/img/bg.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b8ce0cd23f53854aa7290c82e3a6841eff07900393e891f793d4b6cc2945da2 +size 122221 diff --git a/img/error.png b/img/error.png new file mode 100644 index 0000000..9f3c7c2 --- /dev/null +++ b/img/error.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04e3fe200c58adf82199f056b40ee8baa59f3a2c1a035c3df02719acc7ee83e7 +size 616 diff --git a/img/header.jpg b/img/header.jpg new file mode 100644 index 0000000..d6d09b8 --- /dev/null +++ b/img/header.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ae7768ab74d5b892a19af01bcc11e1c17cfbf9649415f63f2ba728c522b97cd +size 35661 diff --git a/img/success.png b/img/success.png new file mode 100644 index 0000000..c413799 --- /dev/null +++ b/img/success.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7ffef2c4091bc35d312a44ff11dcfdede0f04bfedf0b36becb05f23859b727f8 +size 804 diff --git a/img/warning.png b/img/warning.png new file mode 100644 index 0000000..2f39057 --- /dev/null +++ b/img/warning.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ffeb4340493c727ecdacbe617f81b9fee32748a13b0fad5bb7ddc6f847099e7 +size 533 diff --git a/index.html b/index.html new file mode 100644 index 0000000..fc1d77d --- /dev/null +++ b/index.html @@ -0,0 +1,10 @@ + + + + Redirect + + + +

Redirecting . . .

+ + diff --git a/index.php b/index.php new file mode 100644 index 0000000..baddb1b --- /dev/null +++ b/index.php @@ -0,0 +1,69 @@ + + + + + Skynet Games Server + + + + +
+ + +
+

Welcome to the UL CompSoc Minecraft server

+

To play on the server, you must: +

    +
  1. Own a copy of Minecraft

  2. +
  3. Be a member of the UL Computer Society
    + Learn how to join the society here

  4. +
  5. Get your Minecraft username added to our server's whitelist
    + Email minecraft@skynet.ie

  6. +
+

Once that's done, connect to "games.skynet.ie" from the multiplayer section of Minecraft. Enjoy!

+

You can view a live map of our world at games.skynet.ie/map/

+ +

If you are interested in adding more games to this server contact compsoc@skynet.ie and we will see what we can do, we are trialing this server with just Minecraft at the moment

+

Minecraft Server Status:

+ connect()){ + $info = $server->get_info(); + echo "

Server is Online

"; + //print_r($info); + $players = $info['players']; + //print_r($players); + if(count($players)>0){ + echo "

Members Online Now: "; + for($x=0; $x" . $players[$x] . ""; + } + else{ + echo "" . $players[$x] . ", "; + } + } + echo "

"; + } + else{ + echo "

There are currently no members online

"; + } + } + else{ + echo "

Server is Offline

"; + } + } + else{ + echo "

Server is Undergoing Maintence

"; + } + ?> +
+ +

+
+ + + diff --git a/lib/MinecraftPing.php b/lib/MinecraftPing.php new file mode 100644 index 0000000..48c6052 --- /dev/null +++ b/lib/MinecraftPing.php @@ -0,0 +1,245 @@ +Query(); + * echo ''; + * + */ + + private $Socket; + private $ServerAddress; + private $ServerPort; + private $Timeout; + + public function __construct( $Address, $Port = 25565, $Timeout = 2, $ResolveSRV = true ) + { + $this->ServerAddress = $Address; + $this->ServerPort = (int)$Port; + $this->Timeout = (int)$Timeout; + + if( $ResolveSRV ) + { + $this->ResolveSRV(); + } + + $this->Connect( ); + } + + public function __destruct( ) + { + $this->Close( ); + } + + public function Close( ) + { + if( $this->Socket !== null ) + { + fclose( $this->Socket ); + + $this->Socket = null; + } + } + + public function Connect( ) + { + $connectTimeout = $this->Timeout; + $this->Socket = @fsockopen( $this->ServerAddress, $this->ServerPort, $errno, $errstr, $connectTimeout ); + + if( !$this->Socket ) + { + $this->Socket = null; + + throw new MinecraftPingException( "Failed to connect or create a socket: $errno ($errstr)" ); + } + + // Set Read/Write timeout + stream_set_timeout( $this->Socket, $this->Timeout ); + } + + public function Query( ) + { + $TimeStart = microtime(true); // for read timeout purposes + + // See http://wiki.vg/Protocol (Status Ping) + $Data = "\x00"; // packet ID = 0 (varint) + + $Data .= "\x04"; // Protocol version (varint) + $Data .= Pack( 'c', StrLen( $this->ServerAddress ) ) . $this->ServerAddress; // Server (varint len + UTF-8 addr) + $Data .= Pack( 'n', $this->ServerPort ); // Server port (unsigned short) + $Data .= "\x01"; // Next state: status (varint) + + $Data = Pack( 'c', StrLen( $Data ) ) . $Data; // prepend length of packet ID + data + + fwrite( $this->Socket, $Data ); // handshake + fwrite( $this->Socket, "\x01\x00" ); // status ping + + $Length = $this->ReadVarInt( ); // full packet length + + if( $Length < 10 ) + { + return FALSE; + } + + fgetc( $this->Socket ); // packet type, in server ping it's 0 + + $Length = $this->ReadVarInt( ); // string length + + $Data = ""; + do + { + if (microtime(true) - $TimeStart > $this->Timeout) + { + throw new MinecraftPingException( 'Server read timed out' ); + } + + $Remainder = $Length - StrLen( $Data ); + $block = fread( $this->Socket, $Remainder ); // and finally the json string + // abort if there is no progress + if (!$block) + { + throw new MinecraftPingException( 'Server returned too few data' ); + } + + $Data .= $block; + } while( StrLen($Data) < $Length ); + + if( $Data === FALSE ) + { + throw new MinecraftPingException( 'Server didn\'t return any data' ); + } + + $Data = JSON_Decode( $Data, true ); + + if( JSON_Last_Error( ) !== JSON_ERROR_NONE ) + { + if( Function_Exists( 'json_last_error_msg' ) ) + { + throw new MinecraftPingException( JSON_Last_Error_Msg( ) ); + } + else + { + throw new MinecraftPingException( 'JSON parsing failed' ); + } + + return FALSE; + } + + return $Data; + } + + public function QueryOldPre17( ) + { + fwrite( $this->Socket, "\xFE\x01" ); + $Data = fread( $this->Socket, 512 ); + $Len = StrLen( $Data ); + + if( $Len < 4 || $Data[ 0 ] !== "\xFF" ) + { + return FALSE; + } + + $Data = SubStr( $Data, 3 ); // Strip packet header (kick message packet and short length) + $Data = iconv( 'UTF-16BE', 'UTF-8', $Data ); + + // Are we dealing with Minecraft 1.4+ server? + if( $Data[ 1 ] === "\xA7" && $Data[ 2 ] === "\x31" ) + { + $Data = Explode( "\x00", $Data ); + + return Array( + 'HostName' => $Data[ 3 ], + 'Players' => IntVal( $Data[ 4 ] ), + 'MaxPlayers' => IntVal( $Data[ 5 ] ), + 'Protocol' => IntVal( $Data[ 1 ] ), + 'Version' => $Data[ 2 ] + ); + } + + $Data = Explode( "\xA7", $Data ); + + return Array( + 'HostName' => SubStr( $Data[ 0 ], 0, -1 ), + 'Players' => isset( $Data[ 1 ] ) ? IntVal( $Data[ 1 ] ) : 0, + 'MaxPlayers' => isset( $Data[ 2 ] ) ? IntVal( $Data[ 2 ] ) : 0, + 'Protocol' => 0, + 'Version' => '1.3' + ); + } + + private function ReadVarInt( ) + { + $i = 0; + $j = 0; + + while( true ) + { + $k = @fgetc( $this->Socket ); + + if( $k === FALSE ) + { + return 0; + } + + $k = Ord( $k ); + + $i |= ( $k & 0x7F ) << $j++ * 7; + + if( $j > 5 ) + { + throw new MinecraftPingException( 'VarInt too big' ); + } + + if( ( $k & 0x80 ) != 128 ) + { + break; + } + } + + return $i; + } + + private function ResolveSRV() + { + if( ip2long( $this->ServerAddress ) !== false ) + { + return; + } + + $Record = dns_get_record( '_minecraft._tcp.' . $this->ServerAddress, DNS_SRV ); + + if( empty( $Record ) ) + { + return; + } + + if( isset( $Record[ 0 ][ 'target' ] ) ) + { + $this->ServerAddress = $Record[ 0 ][ 'target' ]; + } + + if( isset( $Record[ 0 ][ 'port' ] ) ) + { + $this->ServerPort = $Record[ 0 ][ 'port' ]; + } + } +} diff --git a/lib/MinecraftPingException.php b/lib/MinecraftPingException.php new file mode 100644 index 0000000..bc6b927 --- /dev/null +++ b/lib/MinecraftPingException.php @@ -0,0 +1,8 @@ +ResolveSRV( $Ip, $Port ); + } + + $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 ); + } + 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, $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( !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 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( $Data === false ) + { + 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 ], + 'HostName' => $Data[ 1 ], + 'Unknown1' => $Data[ 2 ], // TODO: What is this? + 'Version' => $Data[ 3 ], + 'Players' => $Data[ 4 ], + 'MaxPlayers' => $Data[ 5 ], + 'Unknown2' => $Data[ 6 ], // TODO: What is this? + 'Map' => $Data[ 7 ], + 'GameMode' => $Data[ 8 ], + 'Unknown3' => $Data[ 9 ], // TODO: 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' ]; + } + } +} diff --git a/lib/MinecraftQueryException.php b/lib/MinecraftQueryException.php new file mode 100644 index 0000000..5984bf0 --- /dev/null +++ b/lib/MinecraftQueryException.php @@ -0,0 +1,8 @@ +host = $host; + $this->port = $port; + $this->timeout = $timeout; + + if (is_array($host)) + { + $this->host = $host['host']; + $this->port = empty($host['port'])?$port:$host['port']; + $this->timeout = empty($host['timeout'])?$timeout:$host['timeout']; + $auto_connect = empty($host['auto_connect'])?$auto_connect:$host['auto_connect']; + } + + if ($auto_connect === true) { + $this->connect(); + } + + } + + /** + * Returns the description of the last error produced. + * + * @return String - Last error string. + */ + public function get_error() { + return $this->errstr; + } + + /** + * Checks whether or not the current connection is established. + * + * @return boolean - True if connected; false otherwise. + */ + public function is_connected() { + if (empty($this->token)) return false; + return true; + } + + /** + * Disconnects! + * duh + */ + public function disconnect() { + if ($this->socket) { + fclose($this->socket); + } + } + + /** + * Connects to the host via UDP with the provided credentials. + * @return boolean - true if successful, false otherwise. + */ + public function connect() + { + $this->socket = fsockopen( 'udp://' . $this->host, $this->port, $errno, $errstr, $this->timeout ); + + if (!$this->socket) + { + $this->errstr = $errstr; + return false; + } + + stream_set_timeout( $this->socket, $this->timeout ); + stream_set_blocking( $this->socket, true ); + + return $this->get_challenge(); + + } + + /** + * Authenticates with the host server and saves the authentication token to a class var. + * + * @return boolean - True if succesfull; false otherwise. + */ + private function get_challenge() + { + if (!$this->socket) + { + return false; + } + + //build packet to get challenge. + $packet = pack("c3N", 0xFE, 0xFD, Query::TYPE_HANDSHAKE, Query::SESSION_ID); + + //write packet + if ( fwrite($this->socket, $packet, strlen($packet)) === FALSE) { + $this->errstr = "Unable to write to socket"; + return false; + } + + //read packet. + $response = fread($this->socket, 2056); + + if (empty($response)) { + $this->errstr = "Unable to authenticate connection"; + return false; + } + + $response_data = unpack("c1type/N1id/a*token", $response); + + if (!isset($response_data['token']) || empty($response_data['token'])) { + $this->errstr = "Unable to authenticate connection."; + return false; + } + + $this->token = $response_data['token']; + + return true; + + } + + /** + * Gets all the info from the server. + * + * @return boolean|array - Returns the data in an array, or false if there was an error. + */ + public function get_info() + { + if (!$this->is_connected()) { + $this->errstr = "Not connected to host"; + return false; + } + //build packet to get info + $packet = pack("c3N2", 0xFE, 0xFD, Query::TYPE_STAT, Query::SESSION_ID, $this->token); + + //add the full stat thingy. + $packet = $packet . pack("c4", 0x00, 0x00, 0x00, 0x00); + + //write packet + if (!fwrite($this->socket, $packet, strlen($packet))) { + $this->errstr = "Unable to write to socket."; + return false; + } + + //read packet header + $response = fread($this->socket, 16); + //$response = stream_get_contents($this->socket); + + // first byte is type. next 4 are id. dont know what the last 11 are for. + $response_data = unpack("c1type/N1id", $response); + + //read the rest of the stream. + $response = fread($this->socket, 2056); + + //split the response into 2 parts. + $payload = explode ( "\x00\x01player_\x00\x00" , $response); + + $info_raw = explode("\x00", rtrim($payload[0], "\x00")); + + //extract key->value chunks from info + $info = array(); + foreach (array_chunk($info_raw, 2) as $pair) { + list($key, $value) = $pair; + //strip possible color format codes from hostname + if ($key == "hostname") { + $value = $this->strip_color_codes($value); + } + $info[$key] = $value; + } + + //get player data. + $players_raw = rtrim($payload[1], "\x00"); + $players = array(); + if (!empty($players_raw)) { + $players = explode("\x00", $players_raw); + } + + //attach player data to info for simplicity + $info['players'] = $players; + + return $info; + } + + /** + * Clears Minecraft color codes from a string. + * + * @param String $string - the string to remove the codes from + * @return String - a clean string. + */ + public function strip_color_codes($string) { + return preg_replace('/[\x00-\x1F\x80-\xFF]./', '', $string); + } + +} diff --git a/test.php b/test.php new file mode 100644 index 0000000..198bbef --- /dev/null +++ b/test.php @@ -0,0 +1,25 @@ +Query() ); + } + catch( MinecraftPingException $e ) + { + echo $e->getMessage(); + } + finally + { + if( $Query ) + { + $Query->Close(); + } + } +?> diff --git a/test1.php b/test1.php new file mode 100644 index 0000000..1927c2d --- /dev/null +++ b/test1.php @@ -0,0 +1,15 @@ +connect()){ + $info = $server->get_info(); + print_r($info); +} +else{ + echo "Server Down"; +} +?> diff --git a/test2.php b/test2.php new file mode 100644 index 0000000..9225efc --- /dev/null +++ b/test2.php @@ -0,0 +1,21 @@ +Connect( 'localhost', 25555 ); + + print_r( $Query->GetInfo( ) ); + print_r( $Query->GetPlayers( ) ); + } + catch( MinecraftQueryException $e ) + { + echo $e->getMessage( ); + } +?>