From e62d0116896d24d95f06d838894b3c33fe3d43dd Mon Sep 17 00:00:00 2001
From: Anthony Birkett <antbir@gmail.com>
Date: Sun, 30 May 2021 10:05:34 +0100
Subject: [PATCH] Bring in CS-Fixer, reformat.

---
 .editorconfig                                 |    8 -
 .gitattributes                                |    1 -
 .gitignore                                    |    2 +
 Examples/Example.php                          |   64 +-
 Examples/RconExample.php                      |   64 +-
 Examples/View.php                             |  137 +-
 SourceQuery/BaseSocket.php                    |  264 ++--
 SourceQuery/Buffer.php                        |  346 +++---
 .../Exception/AuthenticationException.php     |   35 +-
 .../Exception/InvalidArgumentException.php    |   33 +-
 .../Exception/InvalidPacketException.php      |   39 +-
 SourceQuery/Exception/SocketException.php     |   39 +-
 .../Exception/SourceQueryException.php        |   33 +-
 SourceQuery/GoldSourceRcon.php                |  281 ++---
 SourceQuery/Socket.php                        |  188 +--
 SourceQuery/SourceQuery.php                   | 1104 ++++++++---------
 SourceQuery/SourceRcon.php                    |  383 +++---
 SourceQuery/bootstrap.php                     |   51 +-
 Tests/Tests.php                               |  648 +++++-----
 composer.json                                 |    3 +-
 20 files changed, 1822 insertions(+), 1901 deletions(-)
 delete mode 100644 .editorconfig
 delete mode 100644 .gitattributes

diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 30e6d46..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,8 +0,0 @@
-# http://editorconfig.org
-
-root = true
-
-[*]
-charset = utf-8
-indent_style = tab
-insert_final_newline = true
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 176a458..0000000
--- a/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-* text=auto
diff --git a/.gitignore b/.gitignore
index d1502b0..b26a347 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
 vendor/
 composer.lock
+.php-cs-fixer.cache
+.idea/
diff --git a/Examples/Example.php b/Examples/Example.php
index caa7efb..a8bc770 100644
--- a/Examples/Example.php
+++ b/Examples/Example.php
@@ -1,34 +1,32 @@
 <?php
-	require __DIR__ . '/../SourceQuery/bootstrap.php';
-
-	use xPaw\SourceQuery\SourceQuery;
-	
-	// For the sake of this example
-	header( 'Content-Type: text/plain' );
-	header( 'X-Content-Type-Options: nosniff' );
-	
-	// Edit this ->
-	define( 'SQ_SERVER_ADDR', 'localhost' );
-	define( 'SQ_SERVER_PORT', 27015 );
-	define( 'SQ_TIMEOUT',     1 );
-	define( 'SQ_ENGINE',      SourceQuery::SOURCE );
-	// Edit this <-
-	
-	$Query = new SourceQuery( );
-	
-	try
-	{
-		$Query->Connect( SQ_SERVER_ADDR, SQ_SERVER_PORT, SQ_TIMEOUT, SQ_ENGINE );
-		
-		print_r( $Query->GetInfo( ) );
-		print_r( $Query->GetPlayers( ) );
-		print_r( $Query->GetRules( ) );
-	}
-	catch( Exception $e )
-	{
-		echo $e->getMessage( );
-	}
-	finally
-	{
-		$Query->Disconnect( );
-	}
+
+declare(strict_types=1);
+
+require __DIR__ . '/../SourceQuery/bootstrap.php';
+
+use xPaw\SourceQuery\SourceQuery;
+
+// For the sake of this example
+header('Content-Type: text/plain');
+header('X-Content-Type-Options: nosniff');
+
+// Edit this ->
+define('SQ_SERVER_ADDR', 'localhost');
+define('SQ_SERVER_PORT', 27015);
+define('SQ_TIMEOUT', 1);
+define('SQ_ENGINE', SourceQuery::SOURCE);
+// Edit this <-
+
+$Query = new SourceQuery();
+
+try {
+    $Query->Connect(SQ_SERVER_ADDR, SQ_SERVER_PORT, SQ_TIMEOUT, SQ_ENGINE);
+
+    print_r($Query->GetInfo());
+    print_r($Query->GetPlayers());
+    print_r($Query->GetRules());
+} catch (Exception $e) {
+    echo $e->getMessage();
+} finally {
+    $Query->Disconnect();
+}
diff --git a/Examples/RconExample.php b/Examples/RconExample.php
index 5888415..d1e22de 100644
--- a/Examples/RconExample.php
+++ b/Examples/RconExample.php
@@ -1,34 +1,32 @@
 <?php
-	require __DIR__ . '/../SourceQuery/bootstrap.php';
-
-	use xPaw\SourceQuery\SourceQuery;
-	
-	// For the sake of this example
-	header( 'Content-Type: text/plain' );
-	header( 'X-Content-Type-Options: nosniff' );
-	
-	// Edit this ->
-	define( 'SQ_SERVER_ADDR', 'localhost' );
-	define( 'SQ_SERVER_PORT', 27015 );
-	define( 'SQ_TIMEOUT',     1 );
-	define( 'SQ_ENGINE',      SourceQuery::SOURCE );
-	// Edit this <-
-	
-	$Query = new SourceQuery( );
-	
-	try
-	{
-		$Query->Connect( SQ_SERVER_ADDR, SQ_SERVER_PORT, SQ_TIMEOUT, SQ_ENGINE );
-		
-		$Query->SetRconPassword( 'my_awesome_password' );
-		
-		var_dump( $Query->Rcon( 'say hello' ) );
-	}
-	catch( Exception $e )
-	{
-		echo $e->getMessage( );
-	}
-	finally
-	{
-		$Query->Disconnect( );
-	}
+
+declare(strict_types=1);
+
+require __DIR__ . '/../SourceQuery/bootstrap.php';
+
+use xPaw\SourceQuery\SourceQuery;
+
+// For the sake of this example
+header('Content-Type: text/plain');
+header('X-Content-Type-Options: nosniff');
+
+// Edit this ->
+define('SQ_SERVER_ADDR', 'localhost');
+define('SQ_SERVER_PORT', 27015);
+define('SQ_TIMEOUT', 1);
+define('SQ_ENGINE', SourceQuery::SOURCE);
+// Edit this <-
+
+$Query = new SourceQuery();
+
+try {
+    $Query->Connect(SQ_SERVER_ADDR, SQ_SERVER_PORT, SQ_TIMEOUT, SQ_ENGINE);
+
+    $Query->SetRconPassword('my_awesome_password');
+
+    var_dump($Query->Rcon('say hello'));
+} catch (Exception $e) {
+    echo $e->getMessage();
+} finally {
+    $Query->Disconnect();
+}
diff --git a/Examples/View.php b/Examples/View.php
index 11cb15e..fb21e20 100644
--- a/Examples/View.php
+++ b/Examples/View.php
@@ -1,43 +1,42 @@
 <?php
-	require __DIR__ . '/../SourceQuery/bootstrap.php';
 
-	use xPaw\SourceQuery\SourceQuery;
-	
-	// Edit this ->
-	define( 'SQ_SERVER_ADDR', 'localhost' );
-	define( 'SQ_SERVER_PORT', 27015 );
-	define( 'SQ_TIMEOUT',     3 );
-	define( 'SQ_ENGINE',      SourceQuery::SOURCE );
-	// Edit this <-
-	
-	$Timer = microtime( true );
-	
-	$Query = new SourceQuery( );
-	
-	$Info    = [];
-	$Rules   = [];
-	$Players = [];
-	$Exception = null;
-	
-	try
-	{
-		$Query->Connect( SQ_SERVER_ADDR, SQ_SERVER_PORT, SQ_TIMEOUT, SQ_ENGINE );
-		//$Query->SetUseOldGetChallengeMethod( true ); // Use this when players/rules retrieval fails on games like Starbound
-		
-		$Info    = $Query->GetInfo( );
-		$Players = $Query->GetPlayers( );
-		$Rules   = $Query->GetRules( );
-	}
-	catch( Exception $e )
-	{
-		$Exception = $e;
-	}
-	finally
-	{
-		$Query->Disconnect( );
-	}
-	
-	$Timer = number_format( microtime( true ) - $Timer, 4, '.', '' );
+declare(strict_types=1);
+
+require __DIR__ . '/../SourceQuery/bootstrap.php';
+
+use xPaw\SourceQuery\SourceQuery;
+
+// Edit this ->
+define('SQ_SERVER_ADDR', 'localhost');
+define('SQ_SERVER_PORT', 27015);
+define('SQ_TIMEOUT', 3);
+define('SQ_ENGINE', SourceQuery::SOURCE);
+// Edit this <-
+
+$Timer = microtime(true);
+
+$Query = new SourceQuery();
+
+$Info    = [];
+$Rules   = [];
+$Players = [];
+$Exception = null;
+
+try {
+    $Query->Connect(SQ_SERVER_ADDR, SQ_SERVER_PORT, SQ_TIMEOUT, SQ_ENGINE);
+    //$Query->SetUseOldGetChallengeMethod( true ); // Use this when players/rules retrieval fails on games like Starbound
+
+    $Info    = $Query->GetInfo();
+    $Players = $Query->GetPlayers();
+    $Rules   = $Query->GetRules();
+} catch (Exception $e) {
+    $Exception = $e;
+} finally {
+    $Query->Disconnect();
+}
+
+$Timer = number_format(microtime(true) - $Timer, 4, '.', '');
+
 ?>
 <!DOCTYPE html>
 <html>
@@ -88,9 +87,9 @@
 	</div>
 		
 	<div class="container">
-<?php if( $Exception !== null ): ?>
+<?php if ($Exception !== null): ?>
 		<div class="panel panel-error">
-			<pre class="panel-body"><?php echo htmlspecialchars( $Exception->__toString( ) ); ?></pre>
+			<pre class="panel-body"><?php echo htmlspecialchars($Exception->__toString()); ?></pre>
 		</div>
 <?php endif; ?>
 		<div class="row">
@@ -103,32 +102,24 @@
 						</tr>
 					</thead>
 					<tbody>
-<?php if( !empty( $Info ) ): ?>
-<?php foreach( $Info as $InfoKey => $InfoValue ): ?>
+<?php if (!empty($Info)): ?>
+<?php foreach ($Info as $InfoKey => $InfoValue): ?>
 						<tr>
-							<td><?php echo htmlspecialchars( $InfoKey ); ?></td>
+							<td><?php echo htmlspecialchars($InfoKey); ?></td>
 							<td><?php
-	if( is_array( $InfoValue ) )
-	{
-		echo "<pre>";
-		print_r( $InfoValue );
-		echo "</pre>";
-	}
-	else
-	{
-		if( $InfoValue === true )
-		{
-			echo 'true';
-		}
-		else if( $InfoValue === false )
-		{
-			echo 'false';
-		}
-		else
-		{
-			echo htmlspecialchars( $InfoValue );
-		}
-	}
+    if (is_array($InfoValue)) {
+        echo "<pre>";
+        print_r($InfoValue);
+        echo "</pre>";
+    } else {
+        if ($InfoValue === true) {
+            echo 'true';
+        } elseif ($InfoValue === false) {
+            echo 'false';
+        } else {
+            echo htmlspecialchars($InfoValue);
+        }
+    }
 ?></td>
 						</tr>
 <?php endforeach; ?>
@@ -144,16 +135,16 @@
 				<table class="table table-bordered table-striped">
 					<thead>
 						<tr>
-							<th>Player <span class="label label-info"><?php echo count( $Players ); ?></span></th>
+							<th>Player <span class="label label-info"><?php echo count($Players); ?></span></th>
 							<th class="frags-column">Frags</th>
 							<th class="frags-column">Time</th>
 						</tr>
 					</thead>
 					<tbody>
-<?php if( !empty( $Players ) ): ?>
-<?php foreach( $Players as $Player ): ?>
+<?php if (!empty($Players)): ?>
+<?php foreach ($Players as $Player): ?>
 						<tr>
-							<td><?php echo htmlspecialchars( $Player[ 'Name' ] ); ?></td>
+							<td><?php echo htmlspecialchars($Player[ 'Name' ]); ?></td>
 							<td><?php echo $Player[ 'Frags' ]; ?></td>
 							<td><?php echo $Player[ 'TimeF' ]; ?></td>
 						</tr>
@@ -172,15 +163,15 @@
 				<table class="table table-bordered table-striped">
 					<thead>
 						<tr>
-							<th colspan="2">Rules <span class="label label-info"><?php echo count( $Rules ); ?></span></th>
+							<th colspan="2">Rules <span class="label label-info"><?php echo count($Rules); ?></span></th>
 						</tr>
 					</thead>
 					<tbody>
-<?php if( !empty( $Rules ) ): ?>
-<?php foreach( $Rules as $Rule => $Value ): ?>
+<?php if (!empty($Rules)): ?>
+<?php foreach ($Rules as $Rule => $Value): ?>
 						<tr>
-							<td><?php echo htmlspecialchars( $Rule ); ?></td>
-							<td><?php echo htmlspecialchars( $Value ); ?></td>
+							<td><?php echo htmlspecialchars($Rule); ?></td>
+							<td><?php echo htmlspecialchars($Value); ?></td>
 						</tr>
 <?php endforeach; ?>
 <?php else: ?>
diff --git a/SourceQuery/BaseSocket.php b/SourceQuery/BaseSocket.php
index 1ce405e..5909f48 100644
--- a/SourceQuery/BaseSocket.php
+++ b/SourceQuery/BaseSocket.php
@@ -1,139 +1,127 @@
 <?php
-	/**
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 *
-	 * @internal
-	 */
-	
-	namespace xPaw\SourceQuery;
-	
-	use xPaw\SourceQuery\Exception\InvalidPacketException;
-	use xPaw\SourceQuery\Exception\SocketException;
-
-	/**
-	 * Base socket interface
-	 *
-	 * @package xPaw\SourceQuery
-	 *
-	 * @uses xPaw\SourceQuery\Exception\InvalidPacketException
-	 * @uses xPaw\SourceQuery\Exception\SocketException
-	 */
-	abstract class BaseSocket
-	{
-		/** @var ?resource */
-		public $Socket;
-		public int $Engine;
-		
-		public string $Address;
-		public int $Port;
-		public int $Timeout;
-		
-		public function __destruct( )
-		{
-			$this->Close( );
-		}
-		
-		abstract public function Close( ) : void;
-		abstract public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void;
-		abstract public function Write( int $Header, string $String = '' ) : bool;
-		abstract public function Read( int $Length = 1400 ) : Buffer;
-		
-		protected function ReadInternal( Buffer $Buffer, int $Length, callable $SherlockFunction ) : Buffer
-		{
-			if( $Buffer->Remaining( ) === 0 )
-			{
-				throw new InvalidPacketException( 'Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY );
-			}
-			
-			$Header = $Buffer->GetLong( );
-			
-			if( $Header === -1 ) // Single packet
-			{
-				// We don't have to do anything
-			}
-			else if( $Header === -2 ) // Split packet
-			{
-				$Packets      = [];
-				$IsCompressed = false;
-				$ReadMore     = false;
-				$PacketChecksum = null;
-				
-				do
-				{
-					$RequestID = $Buffer->GetLong( );
-					
-					switch( $this->Engine )
-					{
-						case SourceQuery::GOLDSOURCE:
-						{
-							$PacketCountAndNumber = $Buffer->GetByte( );
-							$PacketCount          = $PacketCountAndNumber & 0xF;
-							$PacketNumber         = $PacketCountAndNumber >> 4;
-							
-							break;
-						}
-						case SourceQuery::SOURCE:
-						{
-							$IsCompressed         = ( $RequestID & 0x80000000 ) !== 0;
-							$PacketCount          = $Buffer->GetByte( );
-							$PacketNumber         = $Buffer->GetByte( ) + 1;
-							
-							if( $IsCompressed )
-							{
-								$Buffer->GetLong( ); // Split size
-								
-								$PacketChecksum = $Buffer->GetUnsignedLong( );
-							}
-							else
-							{
-								$Buffer->GetShort( ); // Split size
-							}
-							
-							break;
-						}
-						default:
-						{
-							throw new SocketException( 'Unknown engine.', SocketException::INVALID_ENGINE );
-						}
-					}
-					
-					$Packets[ $PacketNumber ] = $Buffer->Get( );
-					
-					$ReadMore = $PacketCount > sizeof( $Packets );
-				}
-				while( $ReadMore && $SherlockFunction( $Buffer, $Length ) );
-				
-				$Data = implode( $Packets );
-				
-				// TODO: Test this
-				if( $IsCompressed )
-				{
-					// Let's make sure this function exists, it's not included in PHP by default
-					if( !function_exists( 'bzdecompress' ) )
-					{
-						throw new \RuntimeException( 'Received compressed packet, PHP doesn\'t have Bzip2 library installed, can\'t decompress.' );
-					}
-					
-					$Data = bzdecompress( $Data );
-					
-					if( !is_string( $Data ) || crc32( $Data ) !== $PacketChecksum )
-					{
-						throw new InvalidPacketException( 'CRC32 checksum mismatch of uncompressed packet data.', InvalidPacketException::CHECKSUM_MISMATCH );
-					}
-				}
-				
-				$Buffer->Set( substr( $Data, 4 ) );
-			}
-			else
-			{
-				throw new InvalidPacketException( 'Socket read: Raw packet header mismatch. (0x' . dechex( $Header ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
-			}
-			
-			return $Buffer;
-		}
-	}
+
+declare(strict_types=1);
+
+/**
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ *
+ * @internal
+ */
+
+namespace xPaw\SourceQuery;
+
+use xPaw\SourceQuery\Exception\InvalidPacketException;
+use xPaw\SourceQuery\Exception\SocketException;
+
+/**
+ * Base socket interface
+ *
+ * @package xPaw\SourceQuery
+ *
+ * @uses xPaw\SourceQuery\Exception\InvalidPacketException
+ * @uses xPaw\SourceQuery\Exception\SocketException
+ */
+abstract class BaseSocket
+{
+    /** @var ?resource */
+    public $Socket;
+    public int $Engine;
+
+    public string $Address;
+    public int $Port;
+    public int $Timeout;
+
+    public function __destruct()
+    {
+        $this->Close();
+    }
+
+    abstract public function Close(): void;
+    abstract public function Open(string $Address, int $Port, int $Timeout, int $Engine): void;
+    abstract public function Write(int $Header, string $String = ''): bool;
+    abstract public function Read(int $Length = 1400): Buffer;
+
+    protected function ReadInternal(Buffer $Buffer, int $Length, callable $SherlockFunction): Buffer
+    {
+        if ($Buffer->Remaining() === 0) {
+            throw new InvalidPacketException('Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY);
+        }
+
+        $Header = $Buffer->GetLong();
+
+        if ($Header === -1) { // Single packet
+            // We don't have to do anything
+        } elseif ($Header === -2) { // Split packet
+            $Packets      = [];
+            $IsCompressed = false;
+            $ReadMore     = false;
+            $PacketChecksum = null;
+
+            do {
+                $RequestID = $Buffer->GetLong();
+
+                switch ($this->Engine) {
+                    case SourceQuery::GOLDSOURCE:
+                    {
+                        $PacketCountAndNumber = $Buffer->GetByte();
+                        $PacketCount          = $PacketCountAndNumber & 0xF;
+                        $PacketNumber         = $PacketCountAndNumber >> 4;
+
+                        break;
+                    }
+                    case SourceQuery::SOURCE:
+                    {
+                        $IsCompressed         = ($RequestID & 0x80000000) !== 0;
+                        $PacketCount          = $Buffer->GetByte();
+                        $PacketNumber         = $Buffer->GetByte() + 1;
+
+                        if ($IsCompressed) {
+                            $Buffer->GetLong(); // Split size
+
+                            $PacketChecksum = $Buffer->GetUnsignedLong();
+                        } else {
+                            $Buffer->GetShort(); // Split size
+                        }
+
+                        break;
+                    }
+                    default:
+                    {
+                        throw new SocketException('Unknown engine.', SocketException::INVALID_ENGINE);
+                    }
+                }
+
+                $Packets[ $PacketNumber ] = $Buffer->Get();
+
+                $ReadMore = $PacketCount > sizeof($Packets);
+            } while ($ReadMore && $SherlockFunction($Buffer, $Length));
+
+            $Data = implode($Packets);
+
+            // TODO: Test this
+            if ($IsCompressed) {
+                // Let's make sure this function exists, it's not included in PHP by default
+                if (!function_exists('bzdecompress')) {
+                    throw new \RuntimeException('Received compressed packet, PHP doesn\'t have Bzip2 library installed, can\'t decompress.');
+                }
+
+                $Data = bzdecompress($Data);
+
+                if (!is_string($Data) || crc32($Data) !== $PacketChecksum) {
+                    throw new InvalidPacketException('CRC32 checksum mismatch of uncompressed packet data.', InvalidPacketException::CHECKSUM_MISMATCH);
+                }
+            }
+
+            $Buffer->Set(substr($Data, 4));
+        } else {
+            throw new InvalidPacketException('Socket read: Raw packet header mismatch. (0x' . dechex($Header) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH);
+        }
+
+        return $Buffer;
+    }
+}
diff --git a/SourceQuery/Buffer.php b/SourceQuery/Buffer.php
index 2452c99..1896261 100644
--- a/SourceQuery/Buffer.php
+++ b/SourceQuery/Buffer.php
@@ -1,177 +1,171 @@
 <?php
-	/**
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 *
-	 * @internal
-	 */
-
-	namespace xPaw\SourceQuery;
-
-	use xPaw\SourceQuery\Exception\InvalidPacketException;
-
-	/**
-	 * Class Buffer
-	 *
-	 * @package xPaw\SourceQuery
-	 *
-	 * @uses xPaw\SourceQuery\Exception\InvalidPacketException
-	 */
-	class Buffer
-	{
-		/**
-		 * Buffer
-		 */
-		private string $Buffer = '';
-		
-		/**
-		 * Buffer length
-		 */
-		private int $Length = 0;
-		
-		/**
-		 * Current position in buffer
-		 */
-		private int $Position = 0;
-		
-		/**
-		 * Sets buffer
-		 */
-		public function Set( string $Buffer ) : void
-		{
-			$this->Buffer   = $Buffer;
-			$this->Length   = strlen( $Buffer );
-			$this->Position = 0;
-		}
-		
-		/**
-		 * Get remaining bytes
-		 *
-		 * @return int Remaining bytes in buffer
-		 */
-		public function Remaining( ) : int
-		{
-			return $this->Length - $this->Position;
-		}
-		
-		/**
-		 * Gets data from buffer
-		 *
-		 * @param int $Length Bytes to read
-		 */
-		public function Get( int $Length = -1 ) : string
-		{
-			if( $Length === 0 )
-			{
-				return '';
-			}
-			
-			$Remaining = $this->Remaining( );
-			
-			if( $Length === -1 )
-			{
-				$Length = $Remaining;
-			}
-			else if( $Length > $Remaining )
-			{
-				return '';
-			}
-			
-			$Data = substr( $this->Buffer, $this->Position, $Length );
-			
-			$this->Position += $Length;
-			
-			return $Data;
-		}
-		
-		/**
-		 * Get byte from buffer
-		 */
-		public function GetByte( ) : int
-		{
-			return ord( $this->Get( 1 ) );
-		}
-		
-		/**
-		 * Get short from buffer
-		 */
-		public function GetShort( ) : int
-		{
-			if( $this->Remaining( ) < 2 )
-			{
-				throw new InvalidPacketException( 'Not enough data to unpack a short.', InvalidPacketException::BUFFER_EMPTY );
-			}
-			
-			$Data = unpack( 'v', $this->Get( 2 ) );
-			
-			return (int)$Data[ 1 ];
-		}
-		
-		/**
-		 * Get long from buffer
-		 */
-		public function GetLong( ) : int
-		{
-			if( $this->Remaining( ) < 4 )
-			{
-				throw new InvalidPacketException( 'Not enough data to unpack a long.', InvalidPacketException::BUFFER_EMPTY );
-			}
-			
-			$Data = unpack( 'l', $this->Get( 4 ) );
-			
-			return (int)$Data[ 1 ];
-		}
-		
-		/**
-		 * Get float from buffer
-		 */
-		public function GetFloat( ) : float
-		{
-			if( $this->Remaining( ) < 4 )
-			{
-				throw new InvalidPacketException( 'Not enough data to unpack a float.', InvalidPacketException::BUFFER_EMPTY );
-			}
-			
-			$Data = unpack( 'f', $this->Get( 4 ) );
-			
-			return (float)$Data[ 1 ];
-		}
-		
-		/**
-		 * Get unsigned long from buffer
-		 */
-		public function GetUnsignedLong( ) : int
-		{
-			if( $this->Remaining( ) < 4 )
-			{
-				throw new InvalidPacketException( 'Not enough data to unpack an usigned long.', InvalidPacketException::BUFFER_EMPTY );
-			}
-			
-			$Data = unpack( 'V', $this->Get( 4 ) );
-			
-			return (int)$Data[ 1 ];
-		}
-		
-		/**
-		 * Read one string from buffer ending with null byte
-		 */
-		public function GetString( ) : string
-		{
-			$ZeroBytePosition = strpos( $this->Buffer, "\0", $this->Position );
-			
-			if( $ZeroBytePosition === false )
-			{
-				return '';
-			}
-			
-			$String = $this->Get( $ZeroBytePosition - $this->Position );
-			
-			$this->Position++;
-			
-			return $String;
-		}
-	}
+
+declare(strict_types=1);
+
+/**
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ *
+ * @internal
+ */
+
+namespace xPaw\SourceQuery;
+
+use xPaw\SourceQuery\Exception\InvalidPacketException;
+
+/**
+ * Class Buffer
+ *
+ * @package xPaw\SourceQuery
+ *
+ * @uses xPaw\SourceQuery\Exception\InvalidPacketException
+ */
+final class Buffer
+{
+    /**
+     * Buffer
+     */
+    private string $Buffer = '';
+
+    /**
+     * Buffer length
+     */
+    private int $Length = 0;
+
+    /**
+     * Current position in buffer
+     */
+    private int $Position = 0;
+
+    /**
+     * Sets buffer
+     */
+    public function Set(string $Buffer): void
+    {
+        $this->Buffer   = $Buffer;
+        $this->Length   = strlen($Buffer);
+        $this->Position = 0;
+    }
+
+    /**
+     * Get remaining bytes
+     *
+     * @return int Remaining bytes in buffer
+     */
+    public function Remaining(): int
+    {
+        return $this->Length - $this->Position;
+    }
+
+    /**
+     * Gets data from buffer
+     *
+     * @param int $Length Bytes to read
+     */
+    public function Get(int $Length = -1): string
+    {
+        if ($Length === 0) {
+            return '';
+        }
+
+        $Remaining = $this->Remaining();
+
+        if ($Length === -1) {
+            $Length = $Remaining;
+        } elseif ($Length > $Remaining) {
+            return '';
+        }
+
+        $Data = substr($this->Buffer, $this->Position, $Length);
+
+        $this->Position += $Length;
+
+        return $Data;
+    }
+
+    /**
+     * Get byte from buffer
+     */
+    public function GetByte(): int
+    {
+        return ord($this->Get(1));
+    }
+
+    /**
+     * Get short from buffer
+     */
+    public function GetShort(): int
+    {
+        if ($this->Remaining() < 2) {
+            throw new InvalidPacketException('Not enough data to unpack a short.', InvalidPacketException::BUFFER_EMPTY);
+        }
+
+        $Data = unpack('v', $this->Get(2));
+
+        return (int)$Data[ 1 ];
+    }
+
+    /**
+     * Get long from buffer
+     */
+    public function GetLong(): int
+    {
+        if ($this->Remaining() < 4) {
+            throw new InvalidPacketException('Not enough data to unpack a long.', InvalidPacketException::BUFFER_EMPTY);
+        }
+
+        $Data = unpack('l', $this->Get(4));
+
+        return (int)$Data[ 1 ];
+    }
+
+    /**
+     * Get float from buffer
+     */
+    public function GetFloat(): float
+    {
+        if ($this->Remaining() < 4) {
+            throw new InvalidPacketException('Not enough data to unpack a float.', InvalidPacketException::BUFFER_EMPTY);
+        }
+
+        $Data = unpack('f', $this->Get(4));
+
+        return (float)$Data[ 1 ];
+    }
+
+    /**
+     * Get unsigned long from buffer
+     */
+    public function GetUnsignedLong(): int
+    {
+        if ($this->Remaining() < 4) {
+            throw new InvalidPacketException('Not enough data to unpack an usigned long.', InvalidPacketException::BUFFER_EMPTY);
+        }
+
+        $Data = unpack('V', $this->Get(4));
+
+        return (int)$Data[ 1 ];
+    }
+
+    /**
+     * Read one string from buffer ending with null byte
+     */
+    public function GetString(): string
+    {
+        $ZeroBytePosition = strpos($this->Buffer, "\0", $this->Position);
+
+        if ($ZeroBytePosition === false) {
+            return '';
+        }
+
+        $String = $this->Get($ZeroBytePosition - $this->Position);
+
+        $this->Position++;
+
+        return $String;
+    }
+}
diff --git a/SourceQuery/Exception/AuthenticationException.php b/SourceQuery/Exception/AuthenticationException.php
index 3e90f6e..2dd78c9 100644
--- a/SourceQuery/Exception/AuthenticationException.php
+++ b/SourceQuery/Exception/AuthenticationException.php
@@ -1,19 +1,22 @@
 <?php
-	/**
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 *
-	 * @internal
-	 */
 
-	namespace xPaw\SourceQuery\Exception;
+declare(strict_types=1);
 
-	class AuthenticationException extends SourceQueryException
-	{
-		const BAD_PASSWORD = 1;
-		const BANNED = 2;
-	}
+/**
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ *
+ * @internal
+ */
+
+namespace xPaw\SourceQuery\Exception;
+
+final class AuthenticationException extends SourceQueryException
+{
+    public const BAD_PASSWORD = 1;
+    public const BANNED = 2;
+}
diff --git a/SourceQuery/Exception/InvalidArgumentException.php b/SourceQuery/Exception/InvalidArgumentException.php
index 3590c48..269b602 100644
--- a/SourceQuery/Exception/InvalidArgumentException.php
+++ b/SourceQuery/Exception/InvalidArgumentException.php
@@ -1,18 +1,21 @@
 <?php
-	/**
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 *
-	 * @internal
-	 */
 
-	namespace xPaw\SourceQuery\Exception;
+declare(strict_types=1);
 
-	class InvalidArgumentException extends SourceQueryException
-	{
-		const TIMEOUT_NOT_INTEGER = 1;
-	}
+/**
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ *
+ * @internal
+ */
+
+namespace xPaw\SourceQuery\Exception;
+
+final class InvalidArgumentException extends SourceQueryException
+{
+    public const TIMEOUT_NOT_INTEGER = 1;
+}
diff --git a/SourceQuery/Exception/InvalidPacketException.php b/SourceQuery/Exception/InvalidPacketException.php
index 9ac900c..142f158 100644
--- a/SourceQuery/Exception/InvalidPacketException.php
+++ b/SourceQuery/Exception/InvalidPacketException.php
@@ -1,21 +1,24 @@
 <?php
-	/**
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 *
-	 * @internal
-	 */
 
-	namespace xPaw\SourceQuery\Exception;
+declare(strict_types=1);
 
-	class InvalidPacketException extends SourceQueryException
-	{
-		const PACKET_HEADER_MISMATCH = 1;
-		const BUFFER_EMPTY = 2;
-		const BUFFER_NOT_EMPTY = 3;
-		const CHECKSUM_MISMATCH = 4;
-	}
+/**
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ *
+ * @internal
+ */
+
+namespace xPaw\SourceQuery\Exception;
+
+final class InvalidPacketException extends SourceQueryException
+{
+    public const PACKET_HEADER_MISMATCH = 1;
+    public const BUFFER_EMPTY = 2;
+    public const BUFFER_NOT_EMPTY = 3;
+    public const CHECKSUM_MISMATCH = 4;
+}
diff --git a/SourceQuery/Exception/SocketException.php b/SourceQuery/Exception/SocketException.php
index 0619398..99a65de 100644
--- a/SourceQuery/Exception/SocketException.php
+++ b/SourceQuery/Exception/SocketException.php
@@ -1,21 +1,24 @@
 <?php
-	/**
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 *
-	 * @internal
-	 */
 
-	namespace xPaw\SourceQuery\Exception;
+declare(strict_types=1);
 
-	class SocketException extends SourceQueryException
-	{
-		const COULD_NOT_CREATE_SOCKET = 1;
-		const NOT_CONNECTED = 2;
-		const CONNECTION_FAILED = 3;
-		const INVALID_ENGINE = 3;
-	}
+/**
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ *
+ * @internal
+ */
+
+namespace xPaw\SourceQuery\Exception;
+
+final class SocketException extends SourceQueryException
+{
+    public const COULD_NOT_CREATE_SOCKET = 1;
+    public const NOT_CONNECTED = 2;
+    public const CONNECTION_FAILED = 3;
+    public const INVALID_ENGINE = 3;
+}
diff --git a/SourceQuery/Exception/SourceQueryException.php b/SourceQuery/Exception/SourceQueryException.php
index e8722f5..7c8bce0 100644
--- a/SourceQuery/Exception/SourceQueryException.php
+++ b/SourceQuery/Exception/SourceQueryException.php
@@ -1,18 +1,21 @@
 <?php
-	/**
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 *
-	 * @internal
-	 */
 
-	namespace xPaw\SourceQuery\Exception;
+declare(strict_types=1);
 
-	abstract class SourceQueryException extends \Exception
-	{
-		// Base exception class
-	}
+/**
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ *
+ * @internal
+ */
+
+namespace xPaw\SourceQuery\Exception;
+
+abstract class SourceQueryException extends \Exception
+{
+    // Base exception class
+}
diff --git a/SourceQuery/GoldSourceRcon.php b/SourceQuery/GoldSourceRcon.php
index ec9f763..f3cd2c2 100644
--- a/SourceQuery/GoldSourceRcon.php
+++ b/SourceQuery/GoldSourceRcon.php
@@ -1,145 +1,138 @@
 <?php
-	/**
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 *
-	 * @internal
-	 */
-
-	namespace xPaw\SourceQuery;
-	
-	use xPaw\SourceQuery\Exception\AuthenticationException;
-	use xPaw\SourceQuery\Exception\InvalidPacketException;
-
-	/**
-	 * Class GoldSourceRcon
-	 *
-	 * @package xPaw\SourceQuery
-	 *
-	 * @uses xPaw\SourceQuery\Exception\AuthenticationException
-	 * @uses xPaw\SourceQuery\Exception\InvalidPacketException
-	 */
-	class GoldSourceRcon
-	{
-		/**
-		 * Points to socket class
-		 * 
-		 * @var BaseSocket
-		 */
-		private $Socket;
-		
-		private string $RconPassword = '';
-		private string $RconChallenge = '';
-		
-		public function __construct( BaseSocket $Socket )
-		{
-			$this->Socket = $Socket;
-		}
-		
-		public function Close( ) : void
-		{
-			$this->RconChallenge = '';
-			$this->RconPassword  = '';
-		}
-		
-		public function Open( ) : void
-		{
-			//
-		}
-		
-		public function Write( int $Header, string $String = '' ) : bool
-		{
-			$Command = pack( 'cccca*', 0xFF, 0xFF, 0xFF, 0xFF, $String );
-			$Length  = strlen( $Command );
-			
-			return $Length === fwrite( $this->Socket->Socket, $Command, $Length );
-		}
-		
-		/**
-		 * @param int $Length
-		 * @throws AuthenticationException
-		 * @return Buffer
-		 */
-		public function Read( int $Length = 1400 ) : Buffer
-		{
-			// GoldSource RCON has same structure as Query
-			$Buffer = $this->Socket->Read( );
-			
-			$StringBuffer = '';
-			$ReadMore = false;
-			
-			// There is no indentifier of the end, so we just need to continue reading
-			do
-			{
-				$ReadMore = $Buffer->Remaining( ) > 0;
-				
-				if( $ReadMore )
-				{
-					if( $Buffer->GetByte( ) !== SourceQuery::S2A_RCON )
-					{
-						throw new InvalidPacketException( 'Invalid rcon response.', InvalidPacketException::PACKET_HEADER_MISMATCH );
-					}
-					
-					$Packet = $Buffer->Get( );
-					$StringBuffer .= $Packet;
-					//$StringBuffer .= SubStr( $Packet, 0, -2 );
-					
-					// Let's assume if this packet is not long enough, there are no more after this one
-					$ReadMore = strlen( $Packet ) > 1000; // use 1300?
-					
-					if( $ReadMore )
-					{
-						$Buffer = $this->Socket->Read( );
-					}
-				}
-			}
-			while( $ReadMore );
-			
-			$Trimmed = trim( $StringBuffer );
-			
-			if( $Trimmed === 'Bad rcon_password.' )
-			{
-				throw new AuthenticationException( $Trimmed, AuthenticationException::BAD_PASSWORD );
-			}
-			else if( $Trimmed === 'You have been banned from this server.' )
-			{
-				throw new AuthenticationException( $Trimmed, AuthenticationException::BANNED );
-			}
-			
-			$Buffer->Set( $Trimmed );
-			
-			return $Buffer;
-		}
-		
-		public function Command( string $Command ) : string
-		{
-			if( !$this->RconChallenge )
-			{
-				throw new AuthenticationException( 'Tried to execute a RCON command before successful authorization.', AuthenticationException::BAD_PASSWORD );
-			}
-			
-			$this->Write( 0, 'rcon ' . $this->RconChallenge . ' "' . $this->RconPassword . '" ' . $Command . "\0" );
-			$Buffer = $this->Read( );
-			
-			return $Buffer->Get( );
-		}
-		
-		public function Authorize( string $Password ) : void
-		{
-			$this->RconPassword = $Password;
-			
-			$this->Write( 0, 'challenge rcon' );
-			$Buffer = $this->Socket->Read( );
-			
-			if( $Buffer->Get( 14 ) !== 'challenge rcon' )
-			{
-				throw new AuthenticationException( 'Failed to get RCON challenge.', AuthenticationException::BAD_PASSWORD );
-			}
-			
-			$this->RconChallenge = trim( $Buffer->Get( ) );
-		}
-	}
+
+declare(strict_types=1);
+
+/**
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ *
+ * @internal
+ */
+
+namespace xPaw\SourceQuery;
+
+use xPaw\SourceQuery\Exception\AuthenticationException;
+use xPaw\SourceQuery\Exception\InvalidPacketException;
+
+/**
+ * Class GoldSourceRcon
+ *
+ * @package xPaw\SourceQuery
+ *
+ * @uses AuthenticationException
+ * @uses InvalidPacketException
+ */
+final class GoldSourceRcon
+{
+    /**
+     * Points to socket class
+     *
+     * @var BaseSocket
+     */
+    private $Socket;
+
+    private string $RconPassword = '';
+    private string $RconChallenge = '';
+
+    public function __construct(BaseSocket $Socket)
+    {
+        $this->Socket = $Socket;
+    }
+
+    public function Close(): void
+    {
+        $this->RconChallenge = '';
+        $this->RconPassword  = '';
+    }
+
+    public function Open(): void
+    {
+        //
+    }
+
+    public function Write(int $Header, string $String = ''): bool
+    {
+        $Command = pack('cccca*', 0xFF, 0xFF, 0xFF, 0xFF, $String);
+        $Length  = strlen($Command);
+
+        return $Length === fwrite($this->Socket->Socket, $Command, $Length);
+    }
+
+    /**
+     * @param int $Length
+     * @throws AuthenticationException
+     * @return Buffer
+     */
+    public function Read(int $Length = 1400): Buffer
+    {
+        // GoldSource RCON has same structure as Query
+        $Buffer = $this->Socket->Read();
+
+        $StringBuffer = '';
+        $ReadMore = false;
+
+        // There is no indentifier of the end, so we just need to continue reading
+        do {
+            $ReadMore = $Buffer->Remaining() > 0;
+
+            if ($ReadMore) {
+                if ($Buffer->GetByte() !== SourceQuery::S2A_RCON) {
+                    throw new InvalidPacketException('Invalid rcon response.', InvalidPacketException::PACKET_HEADER_MISMATCH);
+                }
+
+                $Packet = $Buffer->Get();
+                $StringBuffer .= $Packet;
+                //$StringBuffer .= SubStr( $Packet, 0, -2 );
+
+                // Let's assume if this packet is not long enough, there are no more after this one
+                $ReadMore = strlen($Packet) > 1000; // use 1300?
+
+                if ($ReadMore) {
+                    $Buffer = $this->Socket->Read();
+                }
+            }
+        } while ($ReadMore);
+
+        $Trimmed = trim($StringBuffer);
+
+        if ($Trimmed === 'Bad rcon_password.') {
+            throw new AuthenticationException($Trimmed, AuthenticationException::BAD_PASSWORD);
+        } elseif ($Trimmed === 'You have been banned from this server.') {
+            throw new AuthenticationException($Trimmed, AuthenticationException::BANNED);
+        }
+
+        $Buffer->Set($Trimmed);
+
+        return $Buffer;
+    }
+
+    public function Command(string $Command): string
+    {
+        if (!$this->RconChallenge) {
+            throw new AuthenticationException('Tried to execute a RCON command before successful authorization.', AuthenticationException::BAD_PASSWORD);
+        }
+
+        $this->Write(0, 'rcon ' . $this->RconChallenge . ' "' . $this->RconPassword . '" ' . $Command . "\0");
+        $Buffer = $this->Read();
+
+        return $Buffer->Get();
+    }
+
+    public function Authorize(string $Password): void
+    {
+        $this->RconPassword = $Password;
+
+        $this->Write(0, 'challenge rcon');
+        $Buffer = $this->Socket->Read();
+
+        if ($Buffer->Get(14) !== 'challenge rcon') {
+            throw new AuthenticationException('Failed to get RCON challenge.', AuthenticationException::BAD_PASSWORD);
+        }
+
+        $this->RconChallenge = trim($Buffer->Get());
+    }
+}
diff --git a/SourceQuery/Socket.php b/SourceQuery/Socket.php
index 726a4d1..f5d8bb5 100644
--- a/SourceQuery/Socket.php
+++ b/SourceQuery/Socket.php
@@ -1,95 +1,95 @@
 <?php
-	/**
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 *
-	 * @internal
-	 */
-
-	namespace xPaw\SourceQuery;
-	
-	use xPaw\SourceQuery\Exception\InvalidPacketException;
-	use xPaw\SourceQuery\Exception\SocketException;
-
-	/**
-	 * Class Socket
-	 *
-	 * @package xPaw\SourceQuery
-	 *
-	 * @uses xPaw\SourceQuery\Exception\InvalidPacketException
-	 * @uses xPaw\SourceQuery\Exception\SocketException
-	 */
-	class Socket extends BaseSocket
-	{
-		public function Close( ) : void
-		{
-			if( $this->Socket !== null )
-			{
-				fclose( $this->Socket );
-				
-				$this->Socket = null;
-			}
-		}
-		
-		public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void
-		{
-			$this->Timeout = $Timeout;
-			$this->Engine  = $Engine;
-			$this->Port    = $Port;
-			$this->Address = $Address;
-			
-			$Socket = @fsockopen( 'udp://' . $Address, $Port, $ErrNo, $ErrStr, $Timeout );
-			
-			if( $ErrNo || $Socket === false )
-			{
-				throw new SocketException( 'Could not create socket: ' . $ErrStr, SocketException::COULD_NOT_CREATE_SOCKET );
-			}
-			
-			$this->Socket = $Socket;
-			stream_set_timeout( $this->Socket, $Timeout );
-			stream_set_blocking( $this->Socket, true );
-		}
-		
-		public function Write( int $Header, string $String = '' ) : bool
-		{
-			$Command = pack( 'ccccca*', 0xFF, 0xFF, 0xFF, 0xFF, $Header, $String );
-			$Length  = strlen( $Command );
-			
-			return $Length === fwrite( $this->Socket, $Command, $Length );
-		}
-		
-		/**
-		 * Reads from socket and returns Buffer.
-		 *
-		 * @throws InvalidPacketException
-		 *
-		 * @return Buffer Buffer
-		 */
-		public function Read( int $Length = 1400 ) : Buffer
-		{
-			$Buffer = new Buffer( );
-			$Buffer->Set( fread( $this->Socket, $Length ) );
-			
-			$this->ReadInternal( $Buffer, $Length, [ $this, 'Sherlock' ] );
-			
-			return $Buffer;
-		}
-		
-		public function Sherlock( Buffer $Buffer, int $Length ) : bool
-		{
-			$Data = fread( $this->Socket, $Length );
-			
-			if( strlen( $Data ) < 4 )
-			{
-				return false;
-			}
-			
-			$Buffer->Set( $Data );
-			
-			return $Buffer->GetLong( ) === -2;
-		}
-	}
+
+declare(strict_types=1);
+
+/**
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ *
+ * @internal
+ */
+
+namespace xPaw\SourceQuery;
+
+use xPaw\SourceQuery\Exception\InvalidPacketException;
+use xPaw\SourceQuery\Exception\SocketException;
+
+/**
+ * Class Socket
+ *
+ * @package xPaw\SourceQuery
+ *
+ * @uses InvalidPacketException
+ * @uses SocketException
+ */
+final class Socket extends BaseSocket
+{
+    public function Close(): void
+    {
+        if ($this->Socket !== null) {
+            fclose($this->Socket);
+
+            $this->Socket = null;
+        }
+    }
+
+    public function Open(string $Address, int $Port, int $Timeout, int $Engine): void
+    {
+        $this->Timeout = $Timeout;
+        $this->Engine  = $Engine;
+        $this->Port    = $Port;
+        $this->Address = $Address;
+
+        $Socket = @fsockopen('udp://' . $Address, $Port, $ErrNo, $ErrStr, $Timeout);
+
+        if ($ErrNo || $Socket === false) {
+            throw new SocketException('Could not create socket: ' . $ErrStr, SocketException::COULD_NOT_CREATE_SOCKET);
+        }
+
+        $this->Socket = $Socket;
+        stream_set_timeout($this->Socket, $Timeout);
+        stream_set_blocking($this->Socket, true);
+    }
+
+    public function Write(int $Header, string $String = ''): bool
+    {
+        $Command = pack('ccccca*', 0xFF, 0xFF, 0xFF, 0xFF, $Header, $String);
+        $Length  = strlen($Command);
+
+        return $Length === fwrite($this->Socket, $Command, $Length);
+    }
+
+    /**
+     * Reads from socket and returns Buffer.
+     *
+     * @throws InvalidPacketException
+     *
+     * @return Buffer Buffer
+     */
+    public function Read(int $Length = 1400): Buffer
+    {
+        $Buffer = new Buffer();
+        $Buffer->Set(fread($this->Socket, $Length));
+
+        $this->ReadInternal($Buffer, $Length, [ $this, 'Sherlock' ]);
+
+        return $Buffer;
+    }
+
+    public function Sherlock(Buffer $Buffer, int $Length): bool
+    {
+        $Data = fread($this->Socket, $Length);
+
+        if (strlen($Data) < 4) {
+            return false;
+        }
+
+        $Buffer->Set($Data);
+
+        return $Buffer->GetLong() === -2;
+    }
+}
diff --git a/SourceQuery/SourceQuery.php b/SourceQuery/SourceQuery.php
index 74f7f57..f1a895b 100644
--- a/SourceQuery/SourceQuery.php
+++ b/SourceQuery/SourceQuery.php
@@ -1,570 +1,536 @@
 <?php
-	/**
-	 * This class provides the public interface to the PHP-Source-Query library.
-	 *
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 */
-
-	namespace xPaw\SourceQuery;
-
-	use xPaw\SourceQuery\Exception\AuthenticationException;
-	use xPaw\SourceQuery\Exception\InvalidArgumentException;
-	use xPaw\SourceQuery\Exception\InvalidPacketException;
-	use xPaw\SourceQuery\Exception\SocketException;
-
-	/**
-	 * Class SourceQuery
-	 *
-	 * @package xPaw\SourceQuery
-	 *
-	 * @uses xPaw\SourceQuery\Exception\AuthenticationException
-	 * @uses xPaw\SourceQuery\Exception\InvalidArgumentException
-	 * @uses xPaw\SourceQuery\Exception\InvalidPacketException
-	 * @uses xPaw\SourceQuery\Exception\SocketException
-	 */
-	class SourceQuery
-	{
-		/**
-		 * Engines
-		 */
-		const GOLDSOURCE = 0;
-		const SOURCE     = 1;
-		
-		/**
-		 * Packets sent
-		 */
-		const A2A_PING      = 0x69;
-		const A2S_INFO      = 0x54;
-		const A2S_PLAYER    = 0x55;
-		const A2S_RULES     = 0x56;
-		const A2S_SERVERQUERY_GETCHALLENGE = 0x57;
-		
-		/**
-		 * Packets received
-		 */
-		const A2A_ACK       = 0x6A;
-		const S2C_CHALLENGE = 0x41;
-		const S2A_INFO_SRC  = 0x49;
-		const S2A_INFO_OLD  = 0x6D; // Old GoldSource, HLTV uses it (actually called S2A_INFO_DETAILED)
-		const S2A_PLAYER    = 0x44;
-		const S2A_RULES     = 0x45;
-		const S2A_RCON      = 0x6C;
-		
-		/**
-		 * Source rcon sent
-		 */
-		const SERVERDATA_REQUESTVALUE   = 0;
-		const SERVERDATA_EXECCOMMAND    = 2;
-		const SERVERDATA_AUTH           = 3;
-		
-		/**
-		 * Source rcon received
-		 */
-		const SERVERDATA_RESPONSE_VALUE = 0;
-		const SERVERDATA_AUTH_RESPONSE  = 2;
-		
-		/**
-		 * Points to rcon class
-		 * 
-		 * @var SourceRcon|GoldSourceRcon|null
-		 */
-		private $Rcon;
-		
-		/**
-		 * Points to socket class
-		 */
-		private BaseSocket $Socket;
-		
-		/**
-		 * True if connection is open, false if not
-		 */
-		private bool $Connected = false;
-		
-		/**
-		 * Contains challenge
-		 */
-		private string $Challenge = '';
-		
-		/**
-		 * Use old method for getting challenge number
-		 */
-		private bool $UseOldGetChallengeMethod = false;
-		
-		public function __construct( BaseSocket $Socket = null )
-		{
-			$this->Socket = $Socket ?: new Socket( );
-		}
-		
-		public function __destruct( )
-		{
-			$this->Disconnect( );
-		}
-		
-		/**
-		 * Opens connection to server
-		 *
-		 * @param string $Address Server ip
-		 * @param int $Port Server port
-		 * @param int $Timeout Timeout period
-		 * @param int $Engine Engine the server runs on (goldsource, source)
-		 *
-		 * @throws InvalidArgumentException
-		 * @throws SocketException
-		 */
-		public function Connect( string $Address, int $Port, int $Timeout = 3, int $Engine = self::SOURCE ) : void
-		{
-			$this->Disconnect( );
-			
-			if( $Timeout < 0 )
-			{
-				throw new InvalidArgumentException( 'Timeout must be a positive integer.', InvalidArgumentException::TIMEOUT_NOT_INTEGER );
-			}
-			
-			$this->Socket->Open( $Address, $Port, $Timeout, $Engine );
-			
-			$this->Connected = true;
-		}
-		
-		/**
-		 * Forces GetChallenge to use old method for challenge retrieval because some games use outdated protocol (e.g Starbound)
-		 *
-		 * @param bool $Value Set to true to force old method
-		 *
-		 * @returns bool Previous value
-		 */
-		public function SetUseOldGetChallengeMethod( bool $Value ) : bool
-		{
-			$Previous = $this->UseOldGetChallengeMethod;
-			
-			$this->UseOldGetChallengeMethod = $Value === true;
-			
-			return $Previous;
-		}
-		
-		/**
-		 * Closes all open connections
-		 */
-		public function Disconnect( ) : void
-		{
-			$this->Connected = false;
-			$this->Challenge = '';
-			
-			$this->Socket->Close( );
-			
-			if( $this->Rcon )
-			{
-				$this->Rcon->Close( );
-				
-				$this->Rcon = null;
-			}
-		}
-		
-		/**
-		 * Sends ping packet to the server
-		 * NOTE: This may not work on some games (TF2 for example)
-		 *
-		 * @throws InvalidPacketException
-		 * @throws SocketException
-		 *
-		 * @return bool True on success, false on failure
-		 */
-		public function Ping( ) : bool
-		{
-			if( !$this->Connected )
-			{
-				throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
-			}
-			
-			$this->Socket->Write( self::A2A_PING );
-			$Buffer = $this->Socket->Read( );
-			
-			return $Buffer->GetByte( ) === self::A2A_ACK;
-		}
-		
-		/**
-		 * Get server information
-		 *
-		 * @throws InvalidPacketException
-		 * @throws SocketException
-		 *
-		 * @return array Returns an array with information on success
-		 */
-		public function GetInfo( ) : array
-		{
-			if( !$this->Connected )
-			{
-				throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
-			}
-			
-			if( $this->Challenge )
-			{
-				$this->Socket->Write( self::A2S_INFO, "Source Engine Query\0" . $this->Challenge );
-			}
-			else
-			{
-				$this->Socket->Write( self::A2S_INFO, "Source Engine Query\0" );
-			}
-
-			$Buffer = $this->Socket->Read( );
-			$Type = $Buffer->GetByte( );
-			$Server = [];
-			
-			if( $Type === self::S2C_CHALLENGE )
-			{
-				$this->Challenge = $Buffer->Get( 4 );
-
-				$this->Socket->Write( self::A2S_INFO, "Source Engine Query\0" . $this->Challenge );
-				$Buffer = $this->Socket->Read( );
-				$Type = $Buffer->GetByte( );
-			}
-
-			// Old GoldSource protocol, HLTV still uses it
-			if( $Type === self::S2A_INFO_OLD && $this->Socket->Engine === self::GOLDSOURCE )
-			{
-				/**
-				 * If we try to read data again, and we get the result with type S2A_INFO (0x49)
-				 * That means this server is running dproto,
-				 * Because it sends answer for both protocols
-				 */
-				
-				$Server[ 'Address' ]    = $Buffer->GetString( );
-				$Server[ 'HostName' ]   = $Buffer->GetString( );
-				$Server[ 'Map' ]        = $Buffer->GetString( );
-				$Server[ 'ModDir' ]     = $Buffer->GetString( );
-				$Server[ 'ModDesc' ]    = $Buffer->GetString( );
-				$Server[ 'Players' ]    = $Buffer->GetByte( );
-				$Server[ 'MaxPlayers' ] = $Buffer->GetByte( );
-				$Server[ 'Protocol' ]   = $Buffer->GetByte( );
-				$Server[ 'Dedicated' ]  = chr( $Buffer->GetByte( ) );
-				$Server[ 'Os' ]         = chr( $Buffer->GetByte( ) );
-				$Server[ 'Password' ]   = $Buffer->GetByte( ) === 1;
-				$Server[ 'IsMod' ]      = $Buffer->GetByte( ) === 1;
-				
-				if( $Server[ 'IsMod' ] )
-				{
-					$Mod = [];
-					$Mod[ 'Url' ]        = $Buffer->GetString( );
-					$Mod[ 'Download' ]   = $Buffer->GetString( );
-					$Buffer->Get( 1 ); // NULL byte
-					$Mod[ 'Version' ]    = $Buffer->GetLong( );
-					$Mod[ 'Size' ]       = $Buffer->GetLong( );
-					$Mod[ 'ServerSide' ] = $Buffer->GetByte( ) === 1;
-					$Mod[ 'CustomDLL' ]  = $Buffer->GetByte( ) === 1;
-					$Server[ 'Mod' ] = $Mod;
-				}
-				
-				$Server[ 'Secure' ]   = $Buffer->GetByte( ) === 1;
-				$Server[ 'Bots' ]     = $Buffer->GetByte( );
-				
-				return $Server;
-			}
-			
-			if( $Type !== self::S2A_INFO_SRC )
-			{
-				throw new InvalidPacketException( 'GetInfo: Packet header mismatch. (0x' . dechex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
-			}
-			
-			$Server[ 'Protocol' ]   = $Buffer->GetByte( );
-			$Server[ 'HostName' ]   = $Buffer->GetString( );
-			$Server[ 'Map' ]        = $Buffer->GetString( );
-			$Server[ 'ModDir' ]     = $Buffer->GetString( );
-			$Server[ 'ModDesc' ]    = $Buffer->GetString( );
-			$Server[ 'AppID' ]      = $Buffer->GetShort( );
-			$Server[ 'Players' ]    = $Buffer->GetByte( );
-			$Server[ 'MaxPlayers' ] = $Buffer->GetByte( );
-			$Server[ 'Bots' ]       = $Buffer->GetByte( );
-			$Server[ 'Dedicated' ]  = chr( $Buffer->GetByte( ) );
-			$Server[ 'Os' ]         = chr( $Buffer->GetByte( ) );
-			$Server[ 'Password' ]   = $Buffer->GetByte( ) === 1;
-			$Server[ 'Secure' ]     = $Buffer->GetByte( ) === 1;
-			
-			// The Ship (they violate query protocol spec by modifying the response)
-			if( $Server[ 'AppID' ] === 2400 )
-			{
-				$Server[ 'GameMode' ]     = $Buffer->GetByte( );
-				$Server[ 'WitnessCount' ] = $Buffer->GetByte( );
-				$Server[ 'WitnessTime' ]  = $Buffer->GetByte( );
-			}
-			
-			$Server[ 'Version' ] = $Buffer->GetString( );
-			
-			// Extra Data Flags
-			if( $Buffer->Remaining( ) > 0 )
-			{
-				$Server[ 'ExtraDataFlags' ] = $Flags = $Buffer->GetByte( );
-				
-				// S2A_EXTRA_DATA_HAS_GAME_PORT - Next 2 bytes include the game port.
-				if( $Flags & 0x80 )
-				{
-					$Server[ 'GamePort' ] = $Buffer->GetShort( );
-				}
-				
-				// S2A_EXTRA_DATA_HAS_STEAMID - Next 8 bytes are the steamID
-				// Want to play around with this?
-				// You can use https://github.com/xPaw/SteamID.php
-				if( $Flags & 0x10 )
-				{
-					$SteamIDLower    = $Buffer->GetUnsignedLong( );
-					$SteamIDInstance = $Buffer->GetUnsignedLong( ); // This gets shifted by 32 bits, which should be steamid instance
-					$SteamID = 0;
-					
-					if( PHP_INT_SIZE === 4 )
-					{
-						if( extension_loaded( 'gmp' ) )
-						{
-							$SteamIDLower    = gmp_abs( $SteamIDLower );
-							$SteamIDInstance = gmp_abs( $SteamIDInstance );
-							$SteamID         = gmp_strval( gmp_or( $SteamIDLower, gmp_mul( $SteamIDInstance, gmp_pow( 2, 32 ) ) ) );
-						}
-						else
-						{
-							throw new \RuntimeException( 'Either 64-bit PHP installation or "gmp" module is required to correctly parse server\'s steamid.' );
-						}
-					}
-					else
-					{
-						$SteamID = $SteamIDLower | ( $SteamIDInstance << 32 );
-					}
-					
-					$Server[ 'SteamID' ] = $SteamID;
-					
-					unset( $SteamIDLower, $SteamIDInstance, $SteamID );
-				}
-				
-				// S2A_EXTRA_DATA_HAS_SPECTATOR_DATA - Next 2 bytes include the spectator port, then the spectator server name.
-				if( $Flags & 0x40 )
-				{
-					$Server[ 'SpecPort' ] = $Buffer->GetShort( );
-					$Server[ 'SpecName' ] = $Buffer->GetString( );
-				}
-				
-				// S2A_EXTRA_DATA_HAS_GAMETAG_DATA - Next bytes are the game tag string
-				if( $Flags & 0x20 )
-				{
-					$Server[ 'GameTags' ] = $Buffer->GetString( );
-				}
-				
-				// S2A_EXTRA_DATA_GAMEID - Next 8 bytes are the gameID of the server
-				if( $Flags & 0x01 )
-				{
-					$Server[ 'GameID' ] = $Buffer->GetUnsignedLong( ) | ( $Buffer->GetUnsignedLong( ) << 32 ); 
-				}
-				
-				if( $Buffer->Remaining( ) > 0 )
-				{
-					throw new InvalidPacketException( 'GetInfo: unread data? ' . $Buffer->Remaining( ) . ' bytes remaining in the buffer. Please report it to the library developer.',
-						InvalidPacketException::BUFFER_NOT_EMPTY );
-				}
-			}
-			
-			return $Server;
-		}
-		
-		/**
-		 * Get players on the server
-		 *
-		 * @throws InvalidPacketException
-		 * @throws SocketException
-		 * 
-		 * @return array Returns an array with players on success
-		 */
-		public function GetPlayers( ) : array
-		{
-			if( !$this->Connected )
-			{
-				throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
-			}
-			
-			$this->GetChallenge( self::A2S_PLAYER, self::S2A_PLAYER );
-			
-			$this->Socket->Write( self::A2S_PLAYER, $this->Challenge );
-			$Buffer = $this->Socket->Read( 14000 ); // Moronic Arma 3 developers do not split their packets, so we have to read more data
-			// This violates the protocol spec, and they probably should fix it: https://developer.valvesoftware.com/wiki/Server_queries#Protocol
-			
-			$Type = $Buffer->GetByte( );
-			
-			if( $Type !== self::S2A_PLAYER )
-			{
-				throw new InvalidPacketException( 'GetPlayers: Packet header mismatch. (0x' . dechex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
-			}
-			
-			$Players = [];
-			$Count   = $Buffer->GetByte( );
-			
-			while( $Count-- > 0 && $Buffer->Remaining( ) > 0 )
-			{
-				$Player = [];
-				$Player[ 'Id' ]    = $Buffer->GetByte( ); // PlayerID, is it just always 0?
-				$Player[ 'Name' ]  = $Buffer->GetString( );
-				$Player[ 'Frags' ] = $Buffer->GetLong( );
-				$Player[ 'Time' ]  = (int)$Buffer->GetFloat( );
-				$Player[ 'TimeF' ] = gmdate( ( $Player[ 'Time' ] > 3600 ? 'H:i:s' : 'i:s' ), $Player[ 'Time' ] );
-				
-				$Players[ ] = $Player;
-			}
-			
-			return $Players;
-		}
-		
-		/**
-		 * Get rules (cvars) from the server
-		 *
-		 * @throws InvalidPacketException
-		 * @throws SocketException
-		 *
-		 * @return array Returns an array with rules on success
-		 */
-		public function GetRules( ) : array
-		{
-			if( !$this->Connected )
-			{
-				throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
-			}
-			
-			$this->GetChallenge( self::A2S_RULES, self::S2A_RULES );
-			
-			$this->Socket->Write( self::A2S_RULES, $this->Challenge );
-			$Buffer = $this->Socket->Read( );
-			
-			$Type = $Buffer->GetByte( );
-			
-			if( $Type !== self::S2A_RULES )
-			{
-				throw new InvalidPacketException( 'GetRules: Packet header mismatch. (0x' . dechex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
-			}
-			
-			$Rules = [];
-			$Count = $Buffer->GetShort( );
-			
-			while( $Count-- > 0 && $Buffer->Remaining( ) > 0 )
-			{
-				$Rule  = $Buffer->GetString( );
-				$Value = $Buffer->GetString( );
-				
-				if( !empty( $Rule ) )
-				{
-					$Rules[ $Rule ] = $Value;
-				}
-			}
-			
-			return $Rules;
-		}
-		
-		/**
-		 * Get challenge (used for players/rules packets)
-		 *
-		 * @throws InvalidPacketException
-		 */
-		private function GetChallenge( int $Header, int $ExpectedResult ) : void
-		{
-			if( $this->Challenge )
-			{
-				return;
-			}
-			
-			if( $this->UseOldGetChallengeMethod )
-			{
-				$Header = self::A2S_SERVERQUERY_GETCHALLENGE;
-			}
-			
-			$this->Socket->Write( $Header, "\xFF\xFF\xFF\xFF" );
-			$Buffer = $this->Socket->Read( );
-			
-			$Type = $Buffer->GetByte( );
-			
-			switch( $Type )
-			{
-				case self::S2C_CHALLENGE:
-				{
-					$this->Challenge = $Buffer->Get( 4 );
-					
-					return;
-				}
-				case $ExpectedResult:
-				{
-					// Goldsource (HLTV)
-					
-					return;
-				}
-				case 0:
-				{
-					throw new InvalidPacketException( 'GetChallenge: Failed to get challenge.' );
-				}
-				default:
-				{
-					throw new InvalidPacketException( 'GetChallenge: Packet header mismatch. (0x' . dechex( $Type ) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH );
-				}
-			}
-		}
-		
-		/**
-		 * Sets rcon password, for future use in Rcon()
-		 *
-		 * @param string $Password Rcon Password
-		 *
-		 * @throws AuthenticationException
-		 * @throws InvalidPacketException
-		 * @throws SocketException
-		 */
-		public function SetRconPassword( string $Password ) : void
-		{
-			if( !$this->Connected )
-			{
-				throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
-			}
-			
-			switch( $this->Socket->Engine )
-			{
-				case SourceQuery::GOLDSOURCE:
-				{
-					$this->Rcon = new GoldSourceRcon( $this->Socket );
-					
-					break;
-				}
-				case SourceQuery::SOURCE:
-				{
-					$this->Rcon = new SourceRcon( $this->Socket );
-					
-					break;
-				}
-				default:
-				{
-					throw new SocketException( 'Unknown engine.', SocketException::INVALID_ENGINE );
-				}
-			}
-			
-			$this->Rcon->Open( );
-			$this->Rcon->Authorize( $Password );
-		}
-		
-		/**
-		 * Sends a command to the server for execution.
-		 *
-		 * @param string $Command Command to execute
-		 *
-		 * @throws AuthenticationException
-		 * @throws InvalidPacketException
-		 * @throws SocketException
-		 *
-		 * @return string Answer from server in string
-		 */
-		public function Rcon( string $Command ) : string
-		{
-			if( !$this->Connected )
-			{
-				throw new SocketException( 'Not connected.', SocketException::NOT_CONNECTED );
-			}
-			
-			if( $this->Rcon === null )
-			{
-				throw new SocketException( 'You must set a RCON password before trying to execute a RCON command.', SocketException::NOT_CONNECTED );
-			}
-			
-			return $this->Rcon->Command( $Command );
-		}
-	}
+
+declare(strict_types=1);
+
+/**
+ * This class provides the public interface to the PHP-Source-Query library.
+ *
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ */
+
+namespace xPaw\SourceQuery;
+
+use xPaw\SourceQuery\Exception\AuthenticationException;
+use xPaw\SourceQuery\Exception\InvalidArgumentException;
+use xPaw\SourceQuery\Exception\InvalidPacketException;
+use xPaw\SourceQuery\Exception\SocketException;
+
+/**
+ * Class SourceQuery
+ *
+ * @package xPaw\SourceQuery
+ *
+ * @uses AuthenticationException
+ * @uses InvalidArgumentException
+ * @uses InvalidPacketException
+ * @uses SocketException
+ */
+final class SourceQuery
+{
+    /**
+     * Engines
+     */
+    public const GOLDSOURCE = 0;
+    public const SOURCE     = 1;
+
+    /**
+     * Packets sent
+     */
+    private const A2A_PING      = 0x69;
+    private const A2S_INFO      = 0x54;
+    private const A2S_PLAYER    = 0x55;
+    private const A2S_RULES     = 0x56;
+    private const A2S_SERVERQUERY_GETCHALLENGE = 0x57;
+
+    /**
+     * Packets received
+     */
+    private const A2A_ACK       = 0x6A;
+    private const S2C_CHALLENGE = 0x41;
+    private const S2A_INFO_SRC  = 0x49;
+    private const S2A_INFO_OLD  = 0x6D; // Old GoldSource, HLTV uses it (actually called S2A_INFO_DETAILED)
+    private const S2A_PLAYER    = 0x44;
+    private const S2A_RULES     = 0x45;
+    public const S2A_RCON      = 0x6C;
+
+    /**
+     * Source rcon sent
+     */
+    public const SERVERDATA_REQUESTVALUE   = 0;
+    public const SERVERDATA_EXECCOMMAND    = 2;
+    public const SERVERDATA_AUTH           = 3;
+
+    /**
+     * Source rcon received
+     */
+    public const SERVERDATA_RESPONSE_VALUE = 0;
+    public const SERVERDATA_AUTH_RESPONSE  = 2;
+
+    /**
+     * Points to rcon class
+     *
+     * @var SourceRcon|GoldSourceRcon|null
+     */
+    private $Rcon;
+
+    /**
+     * Points to socket class
+     */
+    private BaseSocket $Socket;
+
+    /**
+     * True if connection is open, false if not
+     */
+    private bool $Connected = false;
+
+    /**
+     * Contains challenge
+     */
+    private string $Challenge = '';
+
+    /**
+     * Use old method for getting challenge number
+     */
+    private bool $UseOldGetChallengeMethod = false;
+
+    public function __construct(BaseSocket $Socket = null)
+    {
+        $this->Socket = $Socket ?: new Socket();
+    }
+
+    public function __destruct()
+    {
+        $this->Disconnect();
+    }
+
+    /**
+     * Opens connection to server
+     *
+     * @param string $Address Server ip
+     * @param int $Port Server port
+     * @param int $Timeout Timeout period
+     * @param int $Engine Engine the server runs on (goldsource, source)
+     *
+     * @throws InvalidArgumentException
+     * @throws SocketException
+     */
+    public function Connect(string $Address, int $Port, int $Timeout = 3, int $Engine = self::SOURCE): void
+    {
+        $this->Disconnect();
+
+        if ($Timeout < 0) {
+            throw new InvalidArgumentException('Timeout must be a positive integer.', InvalidArgumentException::TIMEOUT_NOT_INTEGER);
+        }
+
+        $this->Socket->Open($Address, $Port, $Timeout, $Engine);
+
+        $this->Connected = true;
+    }
+
+    /**
+     * Forces GetChallenge to use old method for challenge retrieval because some games use outdated protocol (e.g Starbound)
+     *
+     * @param bool $Value Set to true to force old method
+     *
+     * @returns bool Previous value
+     */
+    public function SetUseOldGetChallengeMethod(bool $Value): bool
+    {
+        $Previous = $this->UseOldGetChallengeMethod;
+
+        $this->UseOldGetChallengeMethod = $Value === true;
+
+        return $Previous;
+    }
+
+    /**
+     * Closes all open connections
+     */
+    public function Disconnect(): void
+    {
+        $this->Connected = false;
+        $this->Challenge = '';
+
+        $this->Socket->Close();
+
+        if ($this->Rcon) {
+            $this->Rcon->Close();
+
+            $this->Rcon = null;
+        }
+    }
+
+    /**
+     * Sends ping packet to the server
+     * NOTE: This may not work on some games (TF2 for example)
+     *
+     * @throws InvalidPacketException
+     * @throws SocketException
+     *
+     * @return bool True on success, false on failure
+     */
+    public function Ping(): bool
+    {
+        if (!$this->Connected) {
+            throw new SocketException('Not connected.', SocketException::NOT_CONNECTED);
+        }
+
+        $this->Socket->Write(self::A2A_PING);
+        $Buffer = $this->Socket->Read();
+
+        return $Buffer->GetByte() === self::A2A_ACK;
+    }
+
+    /**
+     * Get server information
+     *
+     * @throws InvalidPacketException
+     * @throws SocketException
+     *
+     * @return array Returns an array with information on success
+     */
+    public function GetInfo(): array
+    {
+        if (!$this->Connected) {
+            throw new SocketException('Not connected.', SocketException::NOT_CONNECTED);
+        }
+
+        if ($this->Challenge) {
+            $this->Socket->Write(self::A2S_INFO, "Source Engine Query\0" . $this->Challenge);
+        } else {
+            $this->Socket->Write(self::A2S_INFO, "Source Engine Query\0");
+        }
+
+        $Buffer = $this->Socket->Read();
+        $Type = $Buffer->GetByte();
+        $Server = [];
+
+        if ($Type === self::S2C_CHALLENGE) {
+            $this->Challenge = $Buffer->Get(4);
+
+            $this->Socket->Write(self::A2S_INFO, "Source Engine Query\0" . $this->Challenge);
+            $Buffer = $this->Socket->Read();
+            $Type = $Buffer->GetByte();
+        }
+
+        // Old GoldSource protocol, HLTV still uses it
+        if ($Type === self::S2A_INFO_OLD && $this->Socket->Engine === self::GOLDSOURCE) {
+            /**
+             * If we try to read data again, and we get the result with type S2A_INFO (0x49)
+             * That means this server is running dproto,
+             * Because it sends answer for both protocols
+             */
+
+            $Server[ 'Address' ]    = $Buffer->GetString();
+            $Server[ 'HostName' ]   = $Buffer->GetString();
+            $Server[ 'Map' ]        = $Buffer->GetString();
+            $Server[ 'ModDir' ]     = $Buffer->GetString();
+            $Server[ 'ModDesc' ]    = $Buffer->GetString();
+            $Server[ 'Players' ]    = $Buffer->GetByte();
+            $Server[ 'MaxPlayers' ] = $Buffer->GetByte();
+            $Server[ 'Protocol' ]   = $Buffer->GetByte();
+            $Server[ 'Dedicated' ]  = chr($Buffer->GetByte());
+            $Server[ 'Os' ]         = chr($Buffer->GetByte());
+            $Server[ 'Password' ]   = $Buffer->GetByte() === 1;
+            $Server[ 'IsMod' ]      = $Buffer->GetByte() === 1;
+
+            if ($Server[ 'IsMod' ]) {
+                $Mod = [];
+                $Mod[ 'Url' ]        = $Buffer->GetString();
+                $Mod[ 'Download' ]   = $Buffer->GetString();
+                $Buffer->Get(1); // NULL byte
+                $Mod[ 'Version' ]    = $Buffer->GetLong();
+                $Mod[ 'Size' ]       = $Buffer->GetLong();
+                $Mod[ 'ServerSide' ] = $Buffer->GetByte() === 1;
+                $Mod[ 'CustomDLL' ]  = $Buffer->GetByte() === 1;
+                $Server[ 'Mod' ] = $Mod;
+            }
+
+            $Server[ 'Secure' ]   = $Buffer->GetByte() === 1;
+            $Server[ 'Bots' ]     = $Buffer->GetByte();
+
+            return $Server;
+        }
+
+        if ($Type !== self::S2A_INFO_SRC) {
+            throw new InvalidPacketException('GetInfo: Packet header mismatch. (0x' . dechex($Type) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH);
+        }
+
+        $Server[ 'Protocol' ]   = $Buffer->GetByte();
+        $Server[ 'HostName' ]   = $Buffer->GetString();
+        $Server[ 'Map' ]        = $Buffer->GetString();
+        $Server[ 'ModDir' ]     = $Buffer->GetString();
+        $Server[ 'ModDesc' ]    = $Buffer->GetString();
+        $Server[ 'AppID' ]      = $Buffer->GetShort();
+        $Server[ 'Players' ]    = $Buffer->GetByte();
+        $Server[ 'MaxPlayers' ] = $Buffer->GetByte();
+        $Server[ 'Bots' ]       = $Buffer->GetByte();
+        $Server[ 'Dedicated' ]  = chr($Buffer->GetByte());
+        $Server[ 'Os' ]         = chr($Buffer->GetByte());
+        $Server[ 'Password' ]   = $Buffer->GetByte() === 1;
+        $Server[ 'Secure' ]     = $Buffer->GetByte() === 1;
+
+        // The Ship (they violate query protocol spec by modifying the response)
+        if ($Server[ 'AppID' ] === 2400) {
+            $Server[ 'GameMode' ]     = $Buffer->GetByte();
+            $Server[ 'WitnessCount' ] = $Buffer->GetByte();
+            $Server[ 'WitnessTime' ]  = $Buffer->GetByte();
+        }
+
+        $Server[ 'Version' ] = $Buffer->GetString();
+
+        // Extra Data Flags
+        if ($Buffer->Remaining() > 0) {
+            $Server[ 'ExtraDataFlags' ] = $Flags = $Buffer->GetByte();
+
+            // S2A_EXTRA_DATA_HAS_GAME_PORT - Next 2 bytes include the game port.
+            if ($Flags & 0x80) {
+                $Server[ 'GamePort' ] = $Buffer->GetShort();
+            }
+
+            // S2A_EXTRA_DATA_HAS_STEAMID - Next 8 bytes are the steamID
+            // Want to play around with this?
+            // You can use https://github.com/xPaw/SteamID.php
+            if ($Flags & 0x10) {
+                $SteamIDLower    = $Buffer->GetUnsignedLong();
+                $SteamIDInstance = $Buffer->GetUnsignedLong(); // This gets shifted by 32 bits, which should be steamid instance
+                $SteamID = 0;
+
+                if (PHP_INT_SIZE === 4) {
+                    if (extension_loaded('gmp')) {
+                        $SteamIDLower    = gmp_abs($SteamIDLower);
+                        $SteamIDInstance = gmp_abs($SteamIDInstance);
+                        $SteamID         = gmp_strval(gmp_or($SteamIDLower, gmp_mul($SteamIDInstance, gmp_pow(2, 32))));
+                    } else {
+                        throw new \RuntimeException('Either 64-bit PHP installation or "gmp" module is required to correctly parse server\'s steamid.');
+                    }
+                } else {
+                    $SteamID = $SteamIDLower | ($SteamIDInstance << 32);
+                }
+
+                $Server[ 'SteamID' ] = $SteamID;
+
+                unset($SteamIDLower, $SteamIDInstance, $SteamID);
+            }
+
+            // S2A_EXTRA_DATA_HAS_SPECTATOR_DATA - Next 2 bytes include the spectator port, then the spectator server name.
+            if ($Flags & 0x40) {
+                $Server[ 'SpecPort' ] = $Buffer->GetShort();
+                $Server[ 'SpecName' ] = $Buffer->GetString();
+            }
+
+            // S2A_EXTRA_DATA_HAS_GAMETAG_DATA - Next bytes are the game tag string
+            if ($Flags & 0x20) {
+                $Server[ 'GameTags' ] = $Buffer->GetString();
+            }
+
+            // S2A_EXTRA_DATA_GAMEID - Next 8 bytes are the gameID of the server
+            if ($Flags & 0x01) {
+                $Server[ 'GameID' ] = $Buffer->GetUnsignedLong() | ($Buffer->GetUnsignedLong() << 32);
+            }
+
+            if ($Buffer->Remaining() > 0) {
+                throw new InvalidPacketException(
+                    'GetInfo: unread data? ' . $Buffer->Remaining() . ' bytes remaining in the buffer. Please report it to the library developer.',
+                    InvalidPacketException::BUFFER_NOT_EMPTY
+                );
+            }
+        }
+
+        return $Server;
+    }
+
+    /**
+     * Get players on the server
+     *
+     * @throws InvalidPacketException
+     * @throws SocketException
+     *
+     * @return array Returns an array with players on success
+     */
+    public function GetPlayers(): array
+    {
+        if (!$this->Connected) {
+            throw new SocketException('Not connected.', SocketException::NOT_CONNECTED);
+        }
+
+        $this->GetChallenge(self::A2S_PLAYER, self::S2A_PLAYER);
+
+        $this->Socket->Write(self::A2S_PLAYER, $this->Challenge);
+        $Buffer = $this->Socket->Read(14000); // Moronic Arma 3 developers do not split their packets, so we have to read more data
+        // This violates the protocol spec, and they probably should fix it: https://developer.valvesoftware.com/wiki/Server_queries#Protocol
+
+        $Type = $Buffer->GetByte();
+
+        if ($Type !== self::S2A_PLAYER) {
+            throw new InvalidPacketException('GetPlayers: Packet header mismatch. (0x' . dechex($Type) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH);
+        }
+
+        $Players = [];
+        $Count   = $Buffer->GetByte();
+
+        while ($Count-- > 0 && $Buffer->Remaining() > 0) {
+            $Player = [];
+            $Player[ 'Id' ]    = $Buffer->GetByte(); // PlayerID, is it just always 0?
+            $Player[ 'Name' ]  = $Buffer->GetString();
+            $Player[ 'Frags' ] = $Buffer->GetLong();
+            $Player[ 'Time' ]  = (int)$Buffer->GetFloat();
+            $Player[ 'TimeF' ] = gmdate(($Player[ 'Time' ] > 3600 ? 'H:i:s' : 'i:s'), $Player[ 'Time' ]);
+
+            $Players[ ] = $Player;
+        }
+
+        return $Players;
+    }
+
+    /**
+     * Get rules (cvars) from the server
+     *
+     * @throws InvalidPacketException
+     * @throws SocketException
+     *
+     * @return array Returns an array with rules on success
+     */
+    public function GetRules(): array
+    {
+        if (!$this->Connected) {
+            throw new SocketException('Not connected.', SocketException::NOT_CONNECTED);
+        }
+
+        $this->GetChallenge(self::A2S_RULES, self::S2A_RULES);
+
+        $this->Socket->Write(self::A2S_RULES, $this->Challenge);
+        $Buffer = $this->Socket->Read();
+
+        $Type = $Buffer->GetByte();
+
+        if ($Type !== self::S2A_RULES) {
+            throw new InvalidPacketException('GetRules: Packet header mismatch. (0x' . dechex($Type) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH);
+        }
+
+        $Rules = [];
+        $Count = $Buffer->GetShort();
+
+        while ($Count-- > 0 && $Buffer->Remaining() > 0) {
+            $Rule  = $Buffer->GetString();
+            $Value = $Buffer->GetString();
+
+            if (!empty($Rule)) {
+                $Rules[ $Rule ] = $Value;
+            }
+        }
+
+        return $Rules;
+    }
+
+    /**
+     * Get challenge (used for players/rules packets)
+     *
+     * @throws InvalidPacketException
+     */
+    private function GetChallenge(int $Header, int $ExpectedResult): void
+    {
+        if ($this->Challenge) {
+            return;
+        }
+
+        if ($this->UseOldGetChallengeMethod) {
+            $Header = self::A2S_SERVERQUERY_GETCHALLENGE;
+        }
+
+        $this->Socket->Write($Header, "\xFF\xFF\xFF\xFF");
+        $Buffer = $this->Socket->Read();
+
+        $Type = $Buffer->GetByte();
+
+        switch ($Type) {
+            case self::S2C_CHALLENGE:
+            {
+                $this->Challenge = $Buffer->Get(4);
+
+                return;
+            }
+            case $ExpectedResult:
+            {
+                // Goldsource (HLTV)
+
+                return;
+            }
+            case 0:
+            {
+                throw new InvalidPacketException('GetChallenge: Failed to get challenge.');
+            }
+            default:
+            {
+                throw new InvalidPacketException('GetChallenge: Packet header mismatch. (0x' . dechex($Type) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH);
+            }
+        }
+    }
+
+    /**
+     * Sets rcon password, for future use in Rcon()
+     *
+     * @param string $Password Rcon Password
+     *
+     * @throws AuthenticationException
+     * @throws InvalidPacketException
+     * @throws SocketException
+     */
+    public function SetRconPassword(string $Password): void
+    {
+        if (!$this->Connected) {
+            throw new SocketException('Not connected.', SocketException::NOT_CONNECTED);
+        }
+
+        switch ($this->Socket->Engine) {
+            case SourceQuery::GOLDSOURCE:
+            {
+                $this->Rcon = new GoldSourceRcon($this->Socket);
+
+                break;
+            }
+            case SourceQuery::SOURCE:
+            {
+                $this->Rcon = new SourceRcon($this->Socket);
+
+                break;
+            }
+            default:
+            {
+                throw new SocketException('Unknown engine.', SocketException::INVALID_ENGINE);
+            }
+        }
+
+        $this->Rcon->Open();
+        $this->Rcon->Authorize($Password);
+    }
+
+    /**
+     * Sends a command to the server for execution.
+     *
+     * @param string $Command Command to execute
+     *
+     * @throws AuthenticationException
+     * @throws InvalidPacketException
+     * @throws SocketException
+     *
+     * @return string Answer from server in string
+     */
+    public function Rcon(string $Command): string
+    {
+        if (!$this->Connected) {
+            throw new SocketException('Not connected.', SocketException::NOT_CONNECTED);
+        }
+
+        if ($this->Rcon === null) {
+            throw new SocketException('You must set a RCON password before trying to execute a RCON command.', SocketException::NOT_CONNECTED);
+        }
+
+        return $this->Rcon->Command($Command);
+    }
+}
diff --git a/SourceQuery/SourceRcon.php b/SourceQuery/SourceRcon.php
index 56919e0..88625b9 100644
--- a/SourceQuery/SourceRcon.php
+++ b/SourceQuery/SourceRcon.php
@@ -1,199 +1,186 @@
 <?php
-	/**
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 *
-	 * @internal
-	 */
-
-	namespace xPaw\SourceQuery;
-	
-	use xPaw\SourceQuery\Exception\AuthenticationException;
-	use xPaw\SourceQuery\Exception\InvalidPacketException;
-	use xPaw\SourceQuery\Exception\SocketException;
-
-	/**
-	 * Class SourceRcon
-	 *
-	 * @package xPaw\SourceQuery
-	 *
-	 * @uses xPaw\SourceQuery\Exception\AuthenticationException
-	 * @uses xPaw\SourceQuery\Exception\InvalidPacketException
-	 * @uses xPaw\SourceQuery\Exception\SocketException
-	 */
-	class SourceRcon
-	{
-		/**
-		 * Points to socket class
-		 */
-		private BaseSocket $Socket;
-		
-		/** @var ?resource */
-		private $RconSocket;
-		private int $RconRequestId = 0;
-		
-		public function __construct( BaseSocket $Socket )
-		{
-			$this->Socket = $Socket;
-		}
-		
-		public function Close( ) : void
-		{
-			if( $this->RconSocket )
-			{
-				fclose( $this->RconSocket );
-				
-				$this->RconSocket = null;
-			}
-			
-			$this->RconRequestId = 0;
-		}
-		
-		public function Open( ) : void
-		{
-			if( !$this->RconSocket )
-			{
-				$RconSocket = @fsockopen( $this->Socket->Address, $this->Socket->Port, $ErrNo, $ErrStr, $this->Socket->Timeout );
-				
-				if( $ErrNo || !$RconSocket )
-				{
-					throw new SocketException( 'Can\'t connect to RCON server: ' . $ErrStr, SocketException::CONNECTION_FAILED );
-				}
-				
-				$this->RconSocket = $RconSocket;
-				stream_set_timeout( $this->RconSocket, $this->Socket->Timeout );
-				stream_set_blocking( $this->RconSocket, true );
-			}
-		}
-		
-		public function Write( int $Header, string $String = '' ) : bool
-		{
-			// Pack the packet together
-			$Command = pack( 'VV', ++$this->RconRequestId, $Header ) . $String . "\x00\x00"; 
-			
-			// Prepend packet length
-			$Command = pack( 'V', strlen( $Command ) ) . $Command;
-			$Length  = strlen( $Command );
-			
-			return $Length === fwrite( $this->RconSocket, $Command, $Length );
-		}
-		
-		public function Read( ) : Buffer
-		{
-			$Buffer = new Buffer( );
-			$Buffer->Set( fread( $this->RconSocket, 4 ) );
-			
-			if( $Buffer->Remaining( ) < 4 )
-			{
-				throw new InvalidPacketException( 'Rcon read: Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY );
-			}
-			
-			$PacketSize = $Buffer->GetLong( );
-			
-			$Buffer->Set( fread( $this->RconSocket, $PacketSize ) );
-			
-			$Data = $Buffer->Get( );
-			
-			$Remaining = $PacketSize - strlen( $Data );
-			
-			while( $Remaining > 0 )
-			{
-				$Data2 = fread( $this->RconSocket, $Remaining );
-				
-				$PacketSize = strlen( $Data2 );
-				
-				if( $PacketSize === 0 )
-				{
-					throw new InvalidPacketException( 'Read ' . strlen( $Data ) . ' bytes from socket, ' . $Remaining . ' remaining', InvalidPacketException::BUFFER_EMPTY );
-				}
-				
-				$Data .= $Data2;
-				$Remaining -= $PacketSize;
-			}
-			
-			$Buffer->Set( $Data );
-			
-			return $Buffer;
-		}
-		
-		public function Command( string $Command ) : string
-		{
-			$this->Write( SourceQuery::SERVERDATA_EXECCOMMAND, $Command );
-			$Buffer = $this->Read( );
-			
-			$Buffer->GetLong( ); // RequestID
-			
-			$Type = $Buffer->GetLong( );
-			
-			if( $Type === SourceQuery::SERVERDATA_AUTH_RESPONSE )
-			{
-				throw new AuthenticationException( 'Bad rcon_password.', AuthenticationException::BAD_PASSWORD );
-			}
-			else if( $Type !== SourceQuery::SERVERDATA_RESPONSE_VALUE )
-			{
-				throw new InvalidPacketException( 'Invalid rcon response.', InvalidPacketException::PACKET_HEADER_MISMATCH );
-			}
-			
-			$Data = $Buffer->Get( );
-			
-			// We do this stupid hack to handle split packets
-			// See https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses
-			if( strlen( $Data ) >= 4000 )
-			{
-				$this->Write( SourceQuery::SERVERDATA_REQUESTVALUE );
-				
-				do
-				{	
-					$Buffer = $this->Read( );
-					
-					$Buffer->GetLong( ); // RequestID
-					
-					if( $Buffer->GetLong( ) !== SourceQuery::SERVERDATA_RESPONSE_VALUE )
-					{
-						break;
-					}
-					
-					$Data2 = $Buffer->Get( );
-					
-					if( $Data2 === "\x00\x01\x00\x00\x00\x00" )
-					{
-						break;
-					}
-					
-					$Data .= $Data2;
-				}
-				while( true );
-			}
-			
-			return rtrim( $Data, "\0" );
-		}
-		
-		public function Authorize( string $Password ) : void
-		{
-			$this->Write( SourceQuery::SERVERDATA_AUTH, $Password );
-			$Buffer = $this->Read( );
-			
-			$RequestID = $Buffer->GetLong( );
-			$Type      = $Buffer->GetLong( );
-			
-			// If we receive SERVERDATA_RESPONSE_VALUE, then we need to read again
-			// More info: https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Additional_Comments
-			
-			if( $Type === SourceQuery::SERVERDATA_RESPONSE_VALUE )
-			{
-				$Buffer = $this->Read( );
-				
-				$RequestID = $Buffer->GetLong( );
-				$Type      = $Buffer->GetLong( );
-			}
-			
-			if( $RequestID === -1 || $Type !== SourceQuery::SERVERDATA_AUTH_RESPONSE )
-			{
-				throw new AuthenticationException( 'RCON authorization failed.', AuthenticationException::BAD_PASSWORD );
-			}
-		}
-	}
+
+declare(strict_types=1);
+
+/**
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ *
+ * @internal
+ */
+
+namespace xPaw\SourceQuery;
+
+use xPaw\SourceQuery\Exception\AuthenticationException;
+use xPaw\SourceQuery\Exception\InvalidPacketException;
+use xPaw\SourceQuery\Exception\SocketException;
+
+/**
+ * Class SourceRcon
+ *
+ * @package xPaw\SourceQuery
+ *
+ * @uses AuthenticationException
+ * @uses InvalidPacketException
+ * @uses SocketException
+ */
+final class SourceRcon
+{
+    /**
+     * Points to socket class
+     */
+    private BaseSocket $Socket;
+
+    /** @var ?resource */
+    private $RconSocket;
+    private int $RconRequestId = 0;
+
+    public function __construct(BaseSocket $Socket)
+    {
+        $this->Socket = $Socket;
+    }
+
+    public function Close(): void
+    {
+        if ($this->RconSocket) {
+            fclose($this->RconSocket);
+
+            $this->RconSocket = null;
+        }
+
+        $this->RconRequestId = 0;
+    }
+
+    public function Open(): void
+    {
+        if (!$this->RconSocket) {
+            $RconSocket = @fsockopen($this->Socket->Address, $this->Socket->Port, $ErrNo, $ErrStr, $this->Socket->Timeout);
+
+            if ($ErrNo || !$RconSocket) {
+                throw new SocketException('Can\'t connect to RCON server: ' . $ErrStr, SocketException::CONNECTION_FAILED);
+            }
+
+            $this->RconSocket = $RconSocket;
+            stream_set_timeout($this->RconSocket, $this->Socket->Timeout);
+            stream_set_blocking($this->RconSocket, true);
+        }
+    }
+
+    public function Write(int $Header, string $String = ''): bool
+    {
+        // Pack the packet together
+        $Command = pack('VV', ++$this->RconRequestId, $Header) . $String . "\x00\x00";
+
+        // Prepend packet length
+        $Command = pack('V', strlen($Command)) . $Command;
+        $Length  = strlen($Command);
+
+        return $Length === fwrite($this->RconSocket, $Command, $Length);
+    }
+
+    public function Read(): Buffer
+    {
+        $Buffer = new Buffer();
+        $Buffer->Set(fread($this->RconSocket, 4));
+
+        if ($Buffer->Remaining() < 4) {
+            throw new InvalidPacketException('Rcon read: Failed to read any data from socket', InvalidPacketException::BUFFER_EMPTY);
+        }
+
+        $PacketSize = $Buffer->GetLong();
+
+        $Buffer->Set(fread($this->RconSocket, $PacketSize));
+
+        $Data = $Buffer->Get();
+
+        $Remaining = $PacketSize - strlen($Data);
+
+        while ($Remaining > 0) {
+            $Data2 = fread($this->RconSocket, $Remaining);
+
+            $PacketSize = strlen($Data2);
+
+            if ($PacketSize === 0) {
+                throw new InvalidPacketException('Read ' . strlen($Data) . ' bytes from socket, ' . $Remaining . ' remaining', InvalidPacketException::BUFFER_EMPTY);
+            }
+
+            $Data .= $Data2;
+            $Remaining -= $PacketSize;
+        }
+
+        $Buffer->Set($Data);
+
+        return $Buffer;
+    }
+
+    public function Command(string $Command): string
+    {
+        $this->Write(SourceQuery::SERVERDATA_EXECCOMMAND, $Command);
+        $Buffer = $this->Read();
+
+        $Buffer->GetLong(); // RequestID
+
+        $Type = $Buffer->GetLong();
+
+        if ($Type === SourceQuery::SERVERDATA_AUTH_RESPONSE) {
+            throw new AuthenticationException('Bad rcon_password.', AuthenticationException::BAD_PASSWORD);
+        } elseif ($Type !== SourceQuery::SERVERDATA_RESPONSE_VALUE) {
+            throw new InvalidPacketException('Invalid rcon response.', InvalidPacketException::PACKET_HEADER_MISMATCH);
+        }
+
+        $Data = $Buffer->Get();
+
+        // We do this stupid hack to handle split packets
+        // See https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses
+        if (strlen($Data) >= 4000) {
+            $this->Write(SourceQuery::SERVERDATA_REQUESTVALUE);
+
+            do {
+                $Buffer = $this->Read();
+
+                $Buffer->GetLong(); // RequestID
+
+                if ($Buffer->GetLong() !== SourceQuery::SERVERDATA_RESPONSE_VALUE) {
+                    break;
+                }
+
+                $Data2 = $Buffer->Get();
+
+                if ($Data2 === "\x00\x01\x00\x00\x00\x00") {
+                    break;
+                }
+
+                $Data .= $Data2;
+            } while (true);
+        }
+
+        return rtrim($Data, "\0");
+    }
+
+    public function Authorize(string $Password): void
+    {
+        $this->Write(SourceQuery::SERVERDATA_AUTH, $Password);
+        $Buffer = $this->Read();
+
+        $RequestID = $Buffer->GetLong();
+        $Type      = $Buffer->GetLong();
+
+        // If we receive SERVERDATA_RESPONSE_VALUE, then we need to read again
+        // More info: https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Additional_Comments
+
+        if ($Type === SourceQuery::SERVERDATA_RESPONSE_VALUE) {
+            $Buffer = $this->Read();
+
+            $RequestID = $Buffer->GetLong();
+            $Type      = $Buffer->GetLong();
+        }
+
+        if ($RequestID === -1 || $Type !== SourceQuery::SERVERDATA_AUTH_RESPONSE) {
+            throw new AuthenticationException('RCON authorization failed.', AuthenticationException::BAD_PASSWORD);
+        }
+    }
+}
diff --git a/SourceQuery/bootstrap.php b/SourceQuery/bootstrap.php
index 9cb6571..c11049e 100644
--- a/SourceQuery/bootstrap.php
+++ b/SourceQuery/bootstrap.php
@@ -1,27 +1,30 @@
 <?php
-	/**
-	 * Library to query servers that implement Source Engine Query protocol.
-	 *
-	 * Special thanks to koraktor for his awesome Steam Condenser class,
-	 * I used it as a reference at some points.
-	 *
-	 * @author Pavel Djundik
-	 *
-	 * @link https://xpaw.me
-	 * @link https://github.com/xPaw/PHP-Source-Query
-	 *
-	 * @license GNU Lesser General Public License, version 2.1
-	 */
 
-	require_once __DIR__ . '/Exception/SourceQueryException.php';
-	require_once __DIR__ . '/Exception/AuthenticationException.php';
-	require_once __DIR__ . '/Exception/InvalidArgumentException.php';
-	require_once __DIR__ . '/Exception/SocketException.php';
-	require_once __DIR__ . '/Exception/InvalidPacketException.php';
+declare(strict_types=1);
 
-	require_once __DIR__ . '/Buffer.php';
-	require_once __DIR__ . '/BaseSocket.php';
-	require_once __DIR__ . '/Socket.php';
-	require_once __DIR__ . '/SourceRcon.php';
-	require_once __DIR__ . '/GoldSourceRcon.php';
-	require_once __DIR__ . '/SourceQuery.php';
+/**
+ * Library to query servers that implement Source Engine Query protocol.
+ *
+ * Special thanks to koraktor for his awesome Steam Condenser class,
+ * I used it as a reference at some points.
+ *
+ * @author Pavel Djundik
+ *
+ * @link https://xpaw.me
+ * @link https://github.com/xPaw/PHP-Source-Query
+ *
+ * @license GNU Lesser General Public License, version 2.1
+ */
+
+require_once __DIR__ . '/Exception/SourceQueryException.php';
+require_once __DIR__ . '/Exception/AuthenticationException.php';
+require_once __DIR__ . '/Exception/InvalidArgumentException.php';
+require_once __DIR__ . '/Exception/SocketException.php';
+require_once __DIR__ . '/Exception/InvalidPacketException.php';
+
+require_once __DIR__ . '/Buffer.php';
+require_once __DIR__ . '/BaseSocket.php';
+require_once __DIR__ . '/Socket.php';
+require_once __DIR__ . '/SourceRcon.php';
+require_once __DIR__ . '/GoldSourceRcon.php';
+require_once __DIR__ . '/SourceQuery.php';
diff --git a/Tests/Tests.php b/Tests/Tests.php
index 1453554..556bd47 100644
--- a/Tests/Tests.php
+++ b/Tests/Tests.php
@@ -1,328 +1,322 @@
 <?php
-	use PHPUnit\Framework\TestCase;
-	use xPaw\SourceQuery\BaseSocket;
-	use xPaw\SourceQuery\SourceQuery;
-	use xPaw\SourceQuery\Buffer;
-	
-	class TestableSocket extends BaseSocket
-	{
-		/** @var \SplQueue<string> */
-		private \SplQueue $PacketQueue;
-		
-		public function __construct( )
-		{
-			/** @var \SplQueue<string> */
-			$this->PacketQueue = new \SplQueue();
-			$this->PacketQueue->setIteratorMode( \SplDoublyLinkedList::IT_MODE_DELETE );
-			
-		}
-		
-		public function Queue( string $Data ) : void
-		{
-			$this->PacketQueue->push( $Data );
-		}
-		
-		public function Close( ) : void
-		{
-			//
-		}
-		
-		public function Open( string $Address, int $Port, int $Timeout, int $Engine ) : void
-		{
-			$this->Timeout = $Timeout;
-			$this->Engine  = $Engine;
-			$this->Port    = $Port;
-			$this->Address = $Address;
-		}
-		
-		public function Write( int $Header, string $String = '' ) : bool
-		{
-			return true;
-		}
-		
-		public function Read( int $Length = 1400 ) : Buffer
-		{
-			$Buffer = new Buffer( );
-			$Buffer->Set( $this->PacketQueue->shift() );
-			
-			$this->ReadInternal( $Buffer, $Length, [ $this, 'Sherlock' ] );
-			
-			return $Buffer;
-		}
-		
-		public function Sherlock( Buffer $Buffer, int $Length ) : bool
-		{
-			if( $this->PacketQueue->isEmpty() )
-			{
-				return false;
-			}
-			
-			$Buffer->Set( $this->PacketQueue->shift() );
-			
-			return $Buffer->GetLong( ) === -2;
-		}
-	}
-	
-	class Tests extends \PHPUnit\Framework\TestCase
-	{
-		private TestableSocket $Socket;
-		private SourceQuery $SourceQuery;
-		
-		public function setUp() : void
-		{
-			$this->Socket = new TestableSocket();
-			$this->SourceQuery = new SourceQuery( $this->Socket );
-			$this->SourceQuery->Connect( '', 2 );
-		}
-		
-		public function tearDown() : void
-		{
-			$this->SourceQuery->Disconnect();
-			
-			unset( $this->Socket, $this->SourceQuery );
-		}
-		
-		public function testInvalidTimeout() : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\InvalidArgumentException::class );
-			$SourceQuery = new SourceQuery( );
-			$SourceQuery->Connect( '', 2, -1 );
-		}
-		
-		public function testNotConnectedGetInfo() : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
-			$this->SourceQuery->Disconnect();
-			$this->SourceQuery->GetInfo();
-		}
-		
-		public function testNotConnectedPing() : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
-			$this->SourceQuery->Disconnect();
-			$this->SourceQuery->Ping();
-		}
-		
-		public function testNotConnectedGetPlayers() : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
-			$this->SourceQuery->Disconnect();
-			$this->SourceQuery->GetPlayers();
-		}
-		
-		/**
-		 * @expectedException xPaw\SourceQuery\Exception\SocketException
-		 */
-		public function testNotConnectedGetRules() : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
-			$this->SourceQuery->Disconnect();
-			$this->SourceQuery->GetRules();
-		}
-		
-		public function testNotConnectedSetRconPassword() : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
-			$this->SourceQuery->Disconnect();
-			$this->SourceQuery->SetRconPassword('a');
-		}
-		
-		public function testNotConnectedRcon() : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
-			$this->SourceQuery->Disconnect();
-			$this->SourceQuery->Rcon('a');
-		}
-		
-		public function testRconWithoutPassword() : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\SocketException::class );
-			$this->SourceQuery->Rcon('a');
-		}
-		
-		/**
-		 * @dataProvider InfoProvider
-		 */
-		public function testGetInfo( string $RawInput, array $ExpectedOutput ) : void
-		{
-			if( isset( $ExpectedOutput[ 'IsMod' ] ) )
-			{
-				$this->Socket->Engine = SourceQuery::GOLDSOURCE;
-			}
-			
-			$this->Socket->Queue( $RawInput );
-			
-			$RealOutput = $this->SourceQuery->GetInfo();
-			
-			$this->assertEquals( $ExpectedOutput, $RealOutput );
-		}
-		
-		public function InfoProvider() : array
-		{
-			$DataProvider = [];
-			
-			$Files = glob( __DIR__ . '/Info/*.raw', GLOB_ERR );
-			
-			foreach( $Files as $File )
-			{
-				$DataProvider[] =
-				[
-					hex2bin( trim( file_get_contents( $File ) ) ),
-					json_decode( file_get_contents( str_replace( '.raw', '.json', $File ) ), true )
-				];
-			}
-			
-			return $DataProvider;
-		}
-		
-		/**
-		 * @dataProvider BadPacketProvider
-		 */
-		public function testBadGetInfo( string $Data ) : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
-			$this->Socket->Queue( $Data );
-			
-			$this->SourceQuery->GetInfo();
-		}
-		
-		/**
-		 * @dataProvider BadPacketProvider
-		 */
-		public function testBadGetChallengeViaPlayers( string $Data ) : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
-			$this->Socket->Queue( $Data );
-			
-			$this->SourceQuery->GetPlayers();
-		}
-		
-		/**
-		 * @dataProvider BadPacketProvider
-		 */
-		public function testBadGetPlayersAfterCorrectChallenge( string $Data ) : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
-			$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11" );
-			$this->Socket->Queue( $Data );
-			
-			$this->SourceQuery->GetPlayers();
-		}
-		
-		/**
-		 * @dataProvider BadPacketProvider
-		 */
-		public function testBadGetRulesAfterCorrectChallenge( string $Data ) : void
-		{
-			$this->expectException( xPaw\SourceQuery\Exception\InvalidPacketException::class );
-			$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11" );
-			$this->Socket->Queue( $Data );
-			
-			$this->SourceQuery->GetRules();
-		}
-		
-		public function BadPacketProvider( ) : array
-		{
-			return
-			[
-				[ "" ],
-				[ "\xff\xff\xff\xff" ], // No type
-				[ "\xff\xff\xff\xff\x49" ], // Correct type, but no data after
-				[ "\xff\xff\xff\xff\x6D" ], // Old info packet, but tests are done for source
-				[ "\xff\xff\xff\xff\x11" ], // Wrong type
-				[ "\x11\x11\x11\x11" ], // Wrong header
-				[ "\xff" ], // Should be 4 bytes, but it's 1
-			];
-		}
-		
-		public function testGetChallengeTwice( ) : void
-		{
-			$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11" );
-			$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x45\x01\x00ayy\x00lmao\x00" );
-			$this->assertEquals( [ 'ayy' => 'lmao' ], $this->SourceQuery->GetRules() );
-			
-			$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x45\x01\x00wow\x00much\x00" );
-			$this->assertEquals( [ 'wow' => 'much' ], $this->SourceQuery->GetRules() );
-		}
-		
-		/**
-		 * @dataProvider RulesProvider
-		 * @param array<string> $RawInput
-		 */
-		public function testGetRules( array $RawInput, array $ExpectedOutput ) : void
-		{
-			$this->Socket->Queue( hex2bin( "ffffffff4104fce20e" ) ); // Challenge
-			
-			foreach( $RawInput as $Packet )
-			{
-				$this->Socket->Queue( hex2bin( $Packet ) );
-			}
-			
-			$RealOutput = $this->SourceQuery->GetRules();
-			
-			$this->assertEquals( $ExpectedOutput, $RealOutput );
-		}
-		
-		public function RulesProvider() : array
-		{
-			$DataProvider = [];
-			
-			$Files = glob( __DIR__ . '/Rules/*.raw', GLOB_ERR );
-			
-			foreach( $Files as $File )
-			{
-				$DataProvider[] =
-				[
-					file( $File, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES ),
-					json_decode( file_get_contents( str_replace( '.raw', '.json', $File ) ), true )
-				];
-			}
-			
-			return $DataProvider;
-		}
-		
-		/**
-		 * @dataProvider PlayersProvider
-		 * @param array<string> $RawInput
-		 */
-		public function testGetPlayers( array $RawInput, array $ExpectedOutput ) : void
-		{
-			$this->Socket->Queue( hex2bin( "ffffffff4104fce20e" ) ); // Challenge
-			
-			foreach( $RawInput as $Packet )
-			{
-				$this->Socket->Queue( hex2bin( $Packet ) );
-			}
-			
-			$RealOutput = $this->SourceQuery->GetPlayers();
-			
-			$this->assertEquals( $ExpectedOutput, $RealOutput );
-		}
-		
-		public function PlayersProvider() : array
-		{
-			$DataProvider = [];
-			
-			$Files = glob( __DIR__ . '/Players/*.raw', GLOB_ERR );
-			
-			foreach( $Files as $File )
-			{
-				$DataProvider[] =
-				[
-					file( $File, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES ),
-					json_decode( file_get_contents( str_replace( '.raw', '.json', $File ) ), true )
-				];
-			}
-			
-			return $DataProvider;
-		}
-		
-		public function testPing() : void
-		{
-			$this->Socket->Queue( "\xFF\xFF\xFF\xFF\x6A\x00");
-			$this->assertTrue( $this->SourceQuery->Ping() );
-			
-			$this->Socket->Queue( "\xFF\xFF\xFF\xFF\xEE");
-			$this->assertFalse( $this->SourceQuery->Ping() );
-		}
-	}
+
+declare(strict_types=1);
+
+use PHPUnit\Framework\TestCase;
+use xPaw\SourceQuery\BaseSocket;
+use xPaw\SourceQuery\SourceQuery;
+use xPaw\SourceQuery\Buffer;
+
+final class TestableSocket extends BaseSocket
+{
+    /** @var \SplQueue<string> */
+    private \SplQueue $PacketQueue;
+
+    public function __construct()
+    {
+        $this->PacketQueue = new \SplQueue();
+        $this->PacketQueue->setIteratorMode(\SplDoublyLinkedList::IT_MODE_DELETE);
+    }
+
+    public function Queue(string $Data): void
+    {
+        $this->PacketQueue->push($Data);
+    }
+
+    public function Close(): void
+    {
+        //
+    }
+
+    public function Open(string $Address, int $Port, int $Timeout, int $Engine): void
+    {
+        $this->Timeout = $Timeout;
+        $this->Engine  = $Engine;
+        $this->Port    = $Port;
+        $this->Address = $Address;
+    }
+
+    public function Write(int $Header, string $String = ''): bool
+    {
+        return true;
+    }
+
+    public function Read(int $Length = 1400): Buffer
+    {
+        $Buffer = new Buffer();
+        $Buffer->Set($this->PacketQueue->shift());
+
+        $this->ReadInternal($Buffer, $Length, [ $this, 'Sherlock' ]);
+
+        return $Buffer;
+    }
+
+    public function Sherlock(Buffer $Buffer, int $Length): bool
+    {
+        if ($this->PacketQueue->isEmpty()) {
+            return false;
+        }
+
+        $Buffer->Set($this->PacketQueue->shift());
+
+        return $Buffer->GetLong() === -2;
+    }
+}
+
+final class Tests extends TestCase
+{
+    private TestableSocket $Socket;
+    private SourceQuery $SourceQuery;
+
+    public function setUp(): void
+    {
+        $this->Socket = new TestableSocket();
+        $this->SourceQuery = new SourceQuery($this->Socket);
+        $this->SourceQuery->Connect('', 2);
+    }
+
+    public function tearDown(): void
+    {
+        $this->SourceQuery->Disconnect();
+
+        unset($this->Socket, $this->SourceQuery);
+    }
+
+    public function testInvalidTimeout(): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\InvalidArgumentException::class);
+        $SourceQuery = new SourceQuery();
+        $SourceQuery->Connect('', 2, -1);
+    }
+
+    public function testNotConnectedGetInfo(): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\SocketException::class);
+        $this->SourceQuery->Disconnect();
+        $this->SourceQuery->GetInfo();
+    }
+
+    public function testNotConnectedPing(): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\SocketException::class);
+        $this->SourceQuery->Disconnect();
+        $this->SourceQuery->Ping();
+    }
+
+    public function testNotConnectedGetPlayers(): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\SocketException::class);
+        $this->SourceQuery->Disconnect();
+        $this->SourceQuery->GetPlayers();
+    }
+
+    /**
+     * @expectedException xPaw\SourceQuery\Exception\SocketException
+     */
+    public function testNotConnectedGetRules(): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\SocketException::class);
+        $this->SourceQuery->Disconnect();
+        $this->SourceQuery->GetRules();
+    }
+
+    public function testNotConnectedSetRconPassword(): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\SocketException::class);
+        $this->SourceQuery->Disconnect();
+        $this->SourceQuery->SetRconPassword('a');
+    }
+
+    public function testNotConnectedRcon(): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\SocketException::class);
+        $this->SourceQuery->Disconnect();
+        $this->SourceQuery->Rcon('a');
+    }
+
+    public function testRconWithoutPassword(): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\SocketException::class);
+        $this->SourceQuery->Rcon('a');
+    }
+
+    /**
+     * @dataProvider InfoProvider
+     */
+    public function testGetInfo(string $RawInput, array $ExpectedOutput): void
+    {
+        if (isset($ExpectedOutput[ 'IsMod' ])) {
+            $this->Socket->Engine = SourceQuery::GOLDSOURCE;
+        }
+
+        $this->Socket->Queue($RawInput);
+
+        $RealOutput = $this->SourceQuery->GetInfo();
+
+        self::assertEquals($ExpectedOutput, $RealOutput);
+    }
+
+    public function InfoProvider(): array
+    {
+        $DataProvider = [];
+
+        $Files = glob(__DIR__ . '/Info/*.raw', GLOB_ERR);
+
+        foreach ($Files as $File) {
+            $DataProvider[] =
+            [
+                hex2bin(trim(file_get_contents($File))),
+                json_decode(file_get_contents(str_replace('.raw', '.json', $File)), true)
+            ];
+        }
+
+        return $DataProvider;
+    }
+
+    /**
+     * @dataProvider BadPacketProvider
+     */
+    public function testBadGetInfo(string $Data): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\InvalidPacketException::class);
+        $this->Socket->Queue($Data);
+
+        $this->SourceQuery->GetInfo();
+    }
+
+    /**
+     * @dataProvider BadPacketProvider
+     */
+    public function testBadGetChallengeViaPlayers(string $Data): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\InvalidPacketException::class);
+        $this->Socket->Queue($Data);
+
+        $this->SourceQuery->GetPlayers();
+    }
+
+    /**
+     * @dataProvider BadPacketProvider
+     */
+    public function testBadGetPlayersAfterCorrectChallenge(string $Data): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\InvalidPacketException::class);
+        $this->Socket->Queue("\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11");
+        $this->Socket->Queue($Data);
+
+        $this->SourceQuery->GetPlayers();
+    }
+
+    /**
+     * @dataProvider BadPacketProvider
+     */
+    public function testBadGetRulesAfterCorrectChallenge(string $Data): void
+    {
+        $this->expectException(xPaw\SourceQuery\Exception\InvalidPacketException::class);
+        $this->Socket->Queue("\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11");
+        $this->Socket->Queue($Data);
+
+        $this->SourceQuery->GetRules();
+    }
+
+    public function BadPacketProvider(): array
+    {
+        return
+        [
+            [ "" ],
+            [ "\xff\xff\xff\xff" ], // No type
+            [ "\xff\xff\xff\xff\x49" ], // Correct type, but no data after
+            [ "\xff\xff\xff\xff\x6D" ], // Old info packet, but tests are done for source
+            [ "\xff\xff\xff\xff\x11" ], // Wrong type
+            [ "\x11\x11\x11\x11" ], // Wrong header
+            [ "\xff" ], // Should be 4 bytes, but it's 1
+        ];
+    }
+
+    public function testGetChallengeTwice(): void
+    {
+        $this->Socket->Queue("\xFF\xFF\xFF\xFF\x41\x11\x11\x11\x11");
+        $this->Socket->Queue("\xFF\xFF\xFF\xFF\x45\x01\x00ayy\x00lmao\x00");
+        self::assertEquals([ 'ayy' => 'lmao' ], $this->SourceQuery->GetRules());
+
+        $this->Socket->Queue("\xFF\xFF\xFF\xFF\x45\x01\x00wow\x00much\x00");
+        self::assertEquals([ 'wow' => 'much' ], $this->SourceQuery->GetRules());
+    }
+
+    /**
+     * @dataProvider RulesProvider
+     * @param array<string> $RawInput
+     */
+    public function testGetRules(array $RawInput, array $ExpectedOutput): void
+    {
+        $this->Socket->Queue(hex2bin("ffffffff4104fce20e")); // Challenge
+
+        foreach ($RawInput as $Packet) {
+            $this->Socket->Queue(hex2bin($Packet));
+        }
+
+        $RealOutput = $this->SourceQuery->GetRules();
+
+        self::assertEquals($ExpectedOutput, $RealOutput);
+    }
+
+    public function RulesProvider(): array
+    {
+        $DataProvider = [];
+
+        $Files = glob(__DIR__ . '/Rules/*.raw', GLOB_ERR);
+
+        foreach ($Files as $File) {
+            $DataProvider[] =
+            [
+                file($File, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES),
+                json_decode(file_get_contents(str_replace('.raw', '.json', $File)), true)
+            ];
+        }
+
+        return $DataProvider;
+    }
+
+    /**
+     * @dataProvider PlayersProvider
+     * @param array<string> $RawInput
+     */
+    public function testGetPlayers(array $RawInput, array $ExpectedOutput): void
+    {
+        $this->Socket->Queue(hex2bin("ffffffff4104fce20e")); // Challenge
+
+        foreach ($RawInput as $Packet) {
+            $this->Socket->Queue(hex2bin($Packet));
+        }
+
+        $RealOutput = $this->SourceQuery->GetPlayers();
+
+        self::assertEquals($ExpectedOutput, $RealOutput);
+    }
+
+    public function PlayersProvider(): array
+    {
+        $DataProvider = [];
+
+        $Files = glob(__DIR__ . '/Players/*.raw', GLOB_ERR);
+
+        foreach ($Files as $File) {
+            $DataProvider[] =
+            [
+                file($File, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES),
+                json_decode(file_get_contents(str_replace('.raw', '.json', $File)), true)
+            ];
+        }
+
+        return $DataProvider;
+    }
+
+    public function testPing(): void
+    {
+        $this->Socket->Queue("\xFF\xFF\xFF\xFF\x6A\x00");
+        self::assertTrue($this->SourceQuery->Ping());
+
+        $this->Socket->Queue("\xFF\xFF\xFF\xFF\xEE");
+        self::assertFalse($this->SourceQuery->Ping());
+    }
+}
diff --git a/composer.json b/composer.json
index 5a2d346..ed90e3a 100644
--- a/composer.json
+++ b/composer.json
@@ -24,7 +24,8 @@
 	{
 		"phpunit/phpunit": "^9.5",
 		"vimeo/psalm": "^4.7",
-		"phpstan/phpstan": "^0.12.83"
+		"phpstan/phpstan": "^0.12.83",
+		"friendsofphp/php-cs-fixer": "^3.0"
 	},
 	"autoload":
 	{