Browse Source

Updated GameQ

own3mall 5 năm trước cách đây
mục cha
commit
b8e77f65f8
100 tập tin đã thay đổi với 10158 bổ sung0 xóa
  1. 60 0
      OGP/php-query/gameq/Autoloader.php
  2. 501 0
      OGP/php-query/gameq/Buffer.php
  3. 30 0
      OGP/php-query/gameq/Exception/Protocol.php
  4. 30 0
      OGP/php-query/gameq/Exception/Query.php
  5. 30 0
      OGP/php-query/gameq/Exception/Server.php
  6. 58 0
      OGP/php-query/gameq/Filters/Base.php
  7. 133 0
      OGP/php-query/gameq/Filters/Normalize.php
  8. 121 0
      OGP/php-query/gameq/Filters/Secondstohuman.php
  9. 115 0
      OGP/php-query/gameq/Filters/Stripcolors.php
  10. 47 0
      OGP/php-query/gameq/Filters/Test.php
  11. 658 0
      OGP/php-query/gameq/GameQ.php
  12. 82 0
      OGP/php-query/gameq/GameQMonitor.php
  13. 500 0
      OGP/php-query/gameq/Protocol.php
  14. 53 0
      OGP/php-query/gameq/Protocols/Aa3.php
  15. 42 0
      OGP/php-query/gameq/Protocols/Aapg.php
  16. 51 0
      OGP/php-query/gameq/Protocols/Arkse.php
  17. 43 0
      OGP/php-query/gameq/Protocols/Arma.php
  18. 181 0
      OGP/php-query/gameq/Protocols/Arma3.php
  19. 50 0
      OGP/php-query/gameq/Protocols/Armedassault2oa.php
  20. 32 0
      OGP/php-query/gameq/Protocols/Armedassault3.php
  21. 208 0
      OGP/php-query/gameq/Protocols/Ase.php
  22. 55 0
      OGP/php-query/gameq/Protocols/Atlas.php
  23. 68 0
      OGP/php-query/gameq/Protocols/Batt1944.php
  24. 88 0
      OGP/php-query/gameq/Protocols/Bf1942.php
  25. 98 0
      OGP/php-query/gameq/Protocols/Bf2.php
  26. 348 0
      OGP/php-query/gameq/Protocols/Bf3.php
  27. 114 0
      OGP/php-query/gameq/Protocols/Bf4.php
  28. 326 0
      OGP/php-query/gameq/Protocols/Bfbc2.php
  29. 43 0
      OGP/php-query/gameq/Protocols/Bfh.php
  30. 50 0
      OGP/php-query/gameq/Protocols/Brink.php
  31. 43 0
      OGP/php-query/gameq/Protocols/Cod.php
  32. 42 0
      OGP/php-query/gameq/Protocols/Cod2.php
  33. 42 0
      OGP/php-query/gameq/Protocols/Cod4.php
  34. 50 0
      OGP/php-query/gameq/Protocols/Codmw3.php
  35. 43 0
      OGP/php-query/gameq/Protocols/Coduo.php
  36. 43 0
      OGP/php-query/gameq/Protocols/Codwaw.php
  37. 42 0
      OGP/php-query/gameq/Protocols/Conanexiles.php
  38. 42 0
      OGP/php-query/gameq/Protocols/Contagion.php
  39. 43 0
      OGP/php-query/gameq/Protocols/Crysis.php
  40. 43 0
      OGP/php-query/gameq/Protocols/Crysis2.php
  41. 43 0
      OGP/php-query/gameq/Protocols/Crysiswars.php
  42. 45 0
      OGP/php-query/gameq/Protocols/Cs15.php
  43. 69 0
      OGP/php-query/gameq/Protocols/Cs16.php
  44. 263 0
      OGP/php-query/gameq/Protocols/Cs2d.php
  45. 45 0
      OGP/php-query/gameq/Protocols/Cscz.php
  46. 43 0
      OGP/php-query/gameq/Protocols/Csgo.php
  47. 42 0
      OGP/php-query/gameq/Protocols/Css.php
  48. 43 0
      OGP/php-query/gameq/Protocols/Dal.php
  49. 66 0
      OGP/php-query/gameq/Protocols/Dayz.php
  50. 44 0
      OGP/php-query/gameq/Protocols/Dayzmod.php
  51. 45 0
      OGP/php-query/gameq/Protocols/Dod.php
  52. 42 0
      OGP/php-query/gameq/Protocols/Dods.php
  53. 69 0
      OGP/php-query/gameq/Protocols/Dow.php
  54. 123 0
      OGP/php-query/gameq/Protocols/Eco.php
  55. 51 0
      OGP/php-query/gameq/Protocols/Egs.php
  56. 43 0
      OGP/php-query/gameq/Protocols/Et.php
  57. 234 0
      OGP/php-query/gameq/Protocols/Etqw.php
  58. 43 0
      OGP/php-query/gameq/Protocols/Ffe.php
  59. 243 0
      OGP/php-query/gameq/Protocols/Ffow.php
  60. 181 0
      OGP/php-query/gameq/Protocols/Gamespy.php
  61. 269 0
      OGP/php-query/gameq/Protocols/Gamespy2.php
  62. 340 0
      OGP/php-query/gameq/Protocols/Gamespy3.php
  63. 34 0
      OGP/php-query/gameq/Protocols/Gamespy4.php
  64. 42 0
      OGP/php-query/gameq/Protocols/Gmod.php
  65. 42 0
      OGP/php-query/gameq/Protocols/Grav.php
  66. 173 0
      OGP/php-query/gameq/Protocols/Gta5m.php
  67. 163 0
      OGP/php-query/gameq/Protocols/Gtan.php
  68. 42 0
      OGP/php-query/gameq/Protocols/Hl2dm.php
  69. 67 0
      OGP/php-query/gameq/Protocols/Http.php
  70. 42 0
      OGP/php-query/gameq/Protocols/Hurtworld.php
  71. 42 0
      OGP/php-query/gameq/Protocols/Insurgency.php
  72. 49 0
      OGP/php-query/gameq/Protocols/Insurgencysand.php
  73. 42 0
      OGP/php-query/gameq/Protocols/Jediacademy.php
  74. 42 0
      OGP/php-query/gameq/Protocols/Jedioutcast.php
  75. 127 0
      OGP/php-query/gameq/Protocols/Justcause2.php
  76. 50 0
      OGP/php-query/gameq/Protocols/Justcause3.php
  77. 96 0
      OGP/php-query/gameq/Protocols/Killingfloor.php
  78. 51 0
      OGP/php-query/gameq/Protocols/Killingfloor2.php
  79. 42 0
      OGP/php-query/gameq/Protocols/L4d.php
  80. 42 0
      OGP/php-query/gameq/Protocols/L4d2.php
  81. 214 0
      OGP/php-query/gameq/Protocols/Lhmp.php
  82. 87 0
      OGP/php-query/gameq/Protocols/Minecraft.php
  83. 44 0
      OGP/php-query/gameq/Protocols/Minecraftpe.php
  84. 79 0
      OGP/php-query/gameq/Protocols/Mohaa.php
  85. 53 0
      OGP/php-query/gameq/Protocols/Mordhau.php
  86. 59 0
      OGP/php-query/gameq/Protocols/Mta.php
  87. 194 0
      OGP/php-query/gameq/Protocols/Mumble.php
  88. 49 0
      OGP/php-query/gameq/Protocols/Ns2.php
  89. 43 0
      OGP/php-query/gameq/Protocols/Pixark.php
  90. 45 0
      OGP/php-query/gameq/Protocols/Projectrealitybf2.php
  91. 219 0
      OGP/php-query/gameq/Protocols/Quake2.php
  92. 214 0
      OGP/php-query/gameq/Protocols/Quake3.php
  93. 42 0
      OGP/php-query/gameq/Protocols/Quakelive.php
  94. 50 0
      OGP/php-query/gameq/Protocols/Redorchestra2.php
  95. 43 0
      OGP/php-query/gameq/Protocols/Redorchestraostfront.php
  96. 55 0
      OGP/php-query/gameq/Protocols/Risingstorm2.php
  97. 43 0
      OGP/php-query/gameq/Protocols/Rust.php
  98. 279 0
      OGP/php-query/gameq/Protocols/Samp.php
  99. 75 0
      OGP/php-query/gameq/Protocols/Serioussam.php
  100. 49 0
      OGP/php-query/gameq/Protocols/Sevendaystodie.php

+ 60 - 0
OGP/php-query/gameq/Autoloader.php

@@ -0,0 +1,60 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ */
+
+/**
+ * A simple PSR-4 spec auto loader to allow GameQ to function the same as if it were loaded via Composer
+ *
+ * To use this just include this file in your script and the GameQ namespace will be made available
+ *
+ * i.e. require_once('/path/to/src/GameQ/Autoloader.php');
+ *
+ * See: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md
+ *
+ * @codeCoverageIgnore
+ */
+spl_autoload_register(function ($class) {
+
+    // project-specific namespace prefix
+    $prefix = 'GameQ\\';
+
+    // base directory for the namespace prefix
+    $base_dir = __DIR__ . DIRECTORY_SEPARATOR;
+
+    // does the class use the namespace prefix?
+    $len = strlen($prefix);
+
+    if (strncmp($prefix, $class, $len) !== 0) {
+        // no, move to the next registered autoloader
+        return;
+    }
+
+    // get the relative class name
+    $relative_class = substr($class, $len);
+
+    // replace the namespace prefix with the base directory, replace namespace
+    // separators with directory separators in the relative class name, append
+    // with .php
+    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
+
+    // if the file exists, require it
+    if (file_exists($file)) {
+        require $file;
+    }
+});

+ 501 - 0
OGP/php-query/gameq/Buffer.php

@@ -0,0 +1,501 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ */
+
+namespace GameQ;
+
+use GameQ\Exception\Protocol as Exception;
+
+/**
+ * Class Buffer
+ *
+ * Read specific byte sequences from a provided string or Buffer
+ *
+ * @package GameQ
+ *
+ * @author  Austin Bischoff <[email protected]>
+ * @author  Aidan Lister <[email protected]>
+ * @author  Tom Buskens <[email protected]>
+ */
+class Buffer
+{
+
+    /**
+     * Constants for the byte code types we need to read as
+     */
+    const NUMBER_TYPE_BIGENDIAN = 'be',
+        NUMBER_TYPE_LITTLEENDIAN = 'le',
+        NUMBER_TYPE_MACHINE = 'm';
+
+    /**
+     * The number type we use for reading integers.  Defaults to little endian
+     *
+     * @type string
+     */
+    private $number_type = self::NUMBER_TYPE_LITTLEENDIAN;
+
+    /**
+     * The original data
+     *
+     * @type string
+     */
+    private $data;
+
+    /**
+     * The original data
+     *
+     * @type int
+     */
+    private $length;
+
+    /**
+     * Position of pointer
+     *
+     * @type int
+     */
+    private $index = 0;
+
+    /**
+     * Constructor
+     *
+     * @param string $data
+     * @param string $number_type
+     */
+    public function __construct($data, $number_type = self::NUMBER_TYPE_LITTLEENDIAN)
+    {
+
+        $this->number_type = $number_type;
+        $this->data = $data;
+        $this->length = strlen($data);
+    }
+
+    /**
+     * Return all the data
+     *
+     * @return  string    The data
+     */
+    public function getData()
+    {
+
+        return $this->data;
+    }
+
+    /**
+     * Return data currently in the buffer
+     *
+     * @return  string    The data currently in the buffer
+     */
+    public function getBuffer()
+    {
+
+        return substr($this->data, $this->index);
+    }
+
+    /**
+     * Returns the number of bytes in the buffer
+     *
+     * @return  int  Length of the buffer
+     */
+    public function getLength()
+    {
+
+        return max($this->length - $this->index, 0);
+    }
+
+    /**
+     * Read from the buffer
+     *
+     * @param int $length
+     *
+     * @return string
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function read($length = 1)
+    {
+
+        if (($length + $this->index) > $this->length) {
+            throw new Exception("Unable to read length={$length} from buffer.  Bad protocol format or return?");
+        }
+
+        $string = substr($this->data, $this->index, $length);
+        $this->index += $length;
+
+        return $string;
+    }
+
+    /**
+     * Read the last character from the buffer
+     *
+     * Unlike the other read functions, this function actually removes
+     * the character from the buffer.
+     *
+     * @return string
+     */
+    public function readLast()
+    {
+
+        $len = strlen($this->data);
+        $string = $this->data[strlen($this->data) - 1];
+        $this->data = substr($this->data, 0, $len - 1);
+        $this->length -= 1;
+
+        return $string;
+    }
+
+    /**
+     * Look at the buffer, but don't remove
+     *
+     * @param int $length
+     *
+     * @return string
+     */
+    public function lookAhead($length = 1)
+    {
+
+        return substr($this->data, $this->index, $length);
+    }
+
+    /**
+     * Skip forward in the buffer
+     *
+     * @param int $length
+     */
+    public function skip($length = 1)
+    {
+
+        $this->index += $length;
+    }
+
+    /**
+     * Jump to a specific position in the buffer,
+     * will not jump past end of buffer
+     *
+     * @param $index
+     */
+    public function jumpto($index)
+    {
+
+        $this->index = min($index, $this->length - 1);
+    }
+
+    /**
+     * Get the current pointer position
+     *
+     * @return int
+     */
+    public function getPosition()
+    {
+
+        return $this->index;
+    }
+
+    /**
+     * Read from buffer until delimiter is reached
+     *
+     * If not found, return everything
+     *
+     * @param string $delim
+     *
+     * @return string
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function readString($delim = "\x00")
+    {
+
+        // Get position of delimiter
+        $len = strpos($this->data, $delim, min($this->index, $this->length));
+
+        // If it is not found then return whole buffer
+        if ($len === false) {
+            return $this->read(strlen($this->data) - $this->index);
+        }
+
+        // Read the string and remove the delimiter
+        $string = $this->read($len - $this->index);
+        ++$this->index;
+
+        return $string;
+    }
+
+    /**
+     * Reads a pascal string from the buffer
+     *
+     * @param int  $offset      Number of bits to cut off the end
+     * @param bool $read_offset True if the data after the offset is to be read
+     *
+     * @return string
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function readPascalString($offset = 0, $read_offset = false)
+    {
+
+        // Get the proper offset
+        $len = $this->readInt8();
+        $offset = max($len - $offset, 0);
+
+        // Read the data
+        if ($read_offset) {
+            return $this->read($offset);
+        } else {
+            return substr($this->read($len), 0, $offset);
+        }
+    }
+
+    /**
+     * Read from buffer until any of the delimiters is reached
+     *
+     * If not found, return everything
+     *
+     * @param      $delims
+     * @param null $delimfound
+     *
+     * @return string
+     * @throws \GameQ\Exception\Protocol
+     *
+     * @todo: Check to see if this is even used anymore
+     */
+    public function readStringMulti($delims, &$delimfound = null)
+    {
+
+        // Get position of delimiters
+        $pos = [];
+        foreach ($delims as $delim) {
+            if ($p = strpos($this->data, $delim, min($this->index, $this->length))) {
+                $pos[] = $p;
+            }
+        }
+
+        // If none are found then return whole buffer
+        if (empty($pos)) {
+            return $this->read(strlen($this->data) - $this->index);
+        }
+
+        // Read the string and remove the delimiter
+        sort($pos);
+        $string = $this->read($pos[0] - $this->index);
+        $delimfound = $this->read();
+
+        return $string;
+    }
+
+    /**
+     * Read an 8-bit unsigned integer
+     *
+     * @return int
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function readInt8()
+    {
+
+        $int = unpack('Cint', $this->read(1));
+
+        return $int['int'];
+    }
+
+    /**
+     * Read and 8-bit signed integer
+     *
+     * @return int
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function readInt8Signed()
+    {
+
+        $int = unpack('cint', $this->read(1));
+
+        return $int['int'];
+    }
+
+    /**
+     * Read a 16-bit unsigned integer
+     *
+     * @return int
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function readInt16()
+    {
+
+        // Change the integer type we are looking up
+        switch ($this->number_type) {
+            case self::NUMBER_TYPE_BIGENDIAN:
+                $type = 'nint';
+                break;
+
+            case self::NUMBER_TYPE_LITTLEENDIAN:
+                $type = 'vint';
+                break;
+
+            default:
+                $type = 'Sint';
+        }
+
+        $int = unpack($type, $this->read(2));
+
+        return $int['int'];
+    }
+
+    /**
+     * Read a 16-bit signed integer
+     *
+     * @return int
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function readInt16Signed()
+    {
+
+        // Read the data into a string
+        $string = $this->read(2);
+
+        // For big endian we need to reverse the bytes
+        if ($this->number_type == self::NUMBER_TYPE_BIGENDIAN) {
+            $string = strrev($string);
+        }
+
+        $int = unpack('sint', $string);
+
+        unset($string);
+
+        return $int['int'];
+    }
+
+    /**
+     * Read a 32-bit unsigned integer
+     *
+     * @return int
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function readInt32()
+    {
+
+        // Change the integer type we are looking up
+        switch ($this->number_type) {
+            case self::NUMBER_TYPE_BIGENDIAN:
+                $type = 'Nint';
+                break;
+
+            case self::NUMBER_TYPE_LITTLEENDIAN:
+                $type = 'Vint';
+                break;
+
+            default:
+                $type = 'Lint';
+        }
+
+        // Unpack the number
+        $int = unpack($type, $this->read(4));
+
+        return $int['int'];
+    }
+
+    /**
+     * Read a 32-bit signed integer
+     *
+     * @return int
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function readInt32Signed()
+    {
+
+        // Read the data into a string
+        $string = $this->read(4);
+
+        // For big endian we need to reverse the bytes
+        if ($this->number_type == self::NUMBER_TYPE_BIGENDIAN) {
+            $string = strrev($string);
+        }
+
+        $int = unpack('lint', $string);
+
+        unset($string);
+
+        return $int['int'];
+    }
+
+    /**
+     * Read a 64-bit unsigned integer
+     *
+     * @return int
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function readInt64()
+    {
+
+        // We have the pack 64-bit codes available. See: http://php.net/manual/en/function.pack.php
+        if (version_compare(PHP_VERSION, '5.6.3') >= 0 && PHP_INT_SIZE == 8) {
+            // Change the integer type we are looking up
+            switch ($this->number_type) {
+                case self::NUMBER_TYPE_BIGENDIAN:
+                    $type = 'Jint';
+                    break;
+
+                case self::NUMBER_TYPE_LITTLEENDIAN:
+                    $type = 'Pint';
+                    break;
+
+                default:
+                    $type = 'Qint';
+            }
+
+            $int64 = unpack($type, $this->read(8));
+
+            $int = $int64['int'];
+
+            unset($int64);
+        } else {
+            if ($this->number_type == self::NUMBER_TYPE_BIGENDIAN) {
+                $high = $this->readInt32();
+                $low = $this->readInt32();
+            } else {
+                $low = $this->readInt32();
+                $high = $this->readInt32();
+            }
+
+            // We have to determine the number via bitwise
+            $int = ($high << 32) | $low;
+
+            unset($low, $high);
+        }
+
+        return $int;
+    }
+
+    /**
+     * Read a 32-bit float
+     *
+     * @return float
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function readFloat32()
+    {
+
+        // Read the data into a string
+        $string = $this->read(4);
+
+        // For big endian we need to reverse the bytes
+        if ($this->number_type == self::NUMBER_TYPE_BIGENDIAN) {
+            $string = strrev($string);
+        }
+
+        $float = unpack('ffloat', $string);
+
+        unset($string);
+
+        return $float['float'];
+    }
+}

+ 30 - 0
OGP/php-query/gameq/Exception/Protocol.php

@@ -0,0 +1,30 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ */
+
+namespace GameQ\Exception;
+
+/**
+ * Exception
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Protocol extends \Exception
+{
+}

+ 30 - 0
OGP/php-query/gameq/Exception/Query.php

@@ -0,0 +1,30 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ */
+
+namespace GameQ\Exception;
+
+/**
+ * Exception
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Query extends \Exception
+{
+}

+ 30 - 0
OGP/php-query/gameq/Exception/Server.php

@@ -0,0 +1,30 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ */
+
+namespace GameQ\Exception;
+
+/**
+ * Exception
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Server extends \Exception
+{
+}

+ 58 - 0
OGP/php-query/gameq/Filters/Base.php

@@ -0,0 +1,58 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Filters;
+
+use GameQ\Server;
+
+/**
+ * Abstract base class which all filters must inherit
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+abstract class Base
+{
+
+    /**
+     * Holds the options for this instance of the filter
+     *
+     * @type array
+     */
+    protected $options = [];
+
+    /**
+     * Construct
+     *
+     * @param array $options
+     */
+    public function __construct(array $options = [])
+    {
+
+        $this->options = $options;
+    }
+
+    /**
+     * Apply the filter to the data
+     *
+     * @param array         $result
+     * @param \GameQ\Server $server
+     *
+     * @return mixed
+     */
+    abstract public function apply(array $result, Server $server);
+}

+ 133 - 0
OGP/php-query/gameq/Filters/Normalize.php

@@ -0,0 +1,133 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Filters;
+
+use GameQ\Server;
+
+/**
+ * Class Normalize
+ *
+ * @package GameQ\Filters
+ */
+class Normalize extends Base
+{
+
+    /**
+     * Holds the protocol specific normalize information
+     *
+     * @type array
+     */
+    protected $normalize = [];
+
+    /**
+     * Apply this filter
+     *
+     * @param array         $result
+     * @param \GameQ\Server $server
+     *
+     * @return array
+     */
+    public function apply(array $result, Server $server)
+    {
+
+        // No result passed so just return
+        if (empty($result)) {
+            return $result;
+        }
+
+        //$data = [ ];
+        //$data['raw'][$server->id()] = $result;
+
+        // Grab the normalize for this protocol for the specific server
+        $this->normalize = $server->protocol()->getNormalize();
+
+        // Do general information
+        $result = array_merge($result, $this->check('general', $result));
+
+        // Do player information
+        if (isset($result['players']) && count($result['players']) > 0) {
+            // Iterate
+            foreach ($result['players'] as $key => $player) {
+                $result['players'][$key] = array_merge($player, $this->check('player', $player));
+            }
+        } else {
+            $result['players'] = [];
+        }
+
+        // Do team information
+        if (isset($result['teams']) && count($result['teams']) > 0) {
+            // Iterate
+            foreach ($result['teams'] as $key => $team) {
+                $result['teams'][$key] = array_merge($team, $this->check('team', $team));
+            }
+        } else {
+            $result['teams'] = [];
+        }
+
+        //$data['filtered'][$server->id()] = $result;
+        /*file_put_contents(
+            sprintf('%s/../../../tests/Filters/Providers/Normalize/%s_1.json', __DIR__, $server->protocol()->getProtocol()),
+            json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR)
+        );*/
+
+        // Return the normalized result
+        return $result;
+    }
+
+    /**
+     * Check a section for normalization
+     *
+     * @param $section
+     * @param $data
+     *
+     * @return array
+     */
+    protected function check($section, $data)
+    {
+
+        // Normalized return array
+        $normalized = [];
+
+        if (isset($this->normalize[$section]) && !empty($this->normalize[$section])) {
+            foreach ($this->normalize[$section] as $property => $raw) {
+                // Default the value for the new key as null
+                $value = null;
+
+                if (is_array($raw)) {
+                    // Iterate over the raw property we want to use
+                    foreach ($raw as $check) {
+                        if (array_key_exists($check, $data)) {
+                            $value = $data[$check];
+                            break;
+                        }
+                    }
+                } else {
+                    // String
+                    if (array_key_exists($raw, $data)) {
+                        $value = $data[$raw];
+                    }
+                }
+
+                $normalized['gq_' . $property] = $value;
+            }
+        }
+
+        return $normalized;
+    }
+}

+ 121 - 0
OGP/php-query/gameq/Filters/Secondstohuman.php

@@ -0,0 +1,121 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Filters;
+
+use GameQ\Server;
+
+/**
+ * Class Secondstohuman
+ *
+ * This class converts seconds into a human readable time string 'hh:mm:ss'. This is mainly for converting
+ * a player's connected time into a readable string. Note that most game servers DO NOT return a player's connected
+ * time. Source (A2S) based games generally do but not always. This class can also be used to convert other time
+ * responses into readable time
+ *
+ * @package GameQ\Filters
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Secondstohuman extends Base
+{
+
+    /**
+     * The options key for setting the data key(s) to look for to convert
+     */
+    const OPTION_TIMEKEYS = 'timekeys';
+
+    /**
+     * The result key added when applying this filter to a result
+     */
+    const RESULT_KEY = 'gq_%s_human';
+
+    /**
+     * Holds the default 'time' keys from the response array.  This is key is usually 'time' from A2S responses
+     *
+     * @var array
+     */
+    protected $timeKeysDefault = ['time'];
+
+    /**
+     * Secondstohuman constructor.
+     *
+     * @param array $options
+     */
+    public function __construct(array $options = [])
+    {
+        // Check for passed keys
+        if (!array_key_exists(self::OPTION_TIMEKEYS, $options)) {
+            // Use default
+            $options[self::OPTION_TIMEKEYS] = $this->timeKeysDefault;
+        } else {
+            // Used passed key(s) and make sure it is an array
+            $options[self::OPTION_TIMEKEYS] = (!is_array($options[self::OPTION_TIMEKEYS])) ?
+                [$options[self::OPTION_TIMEKEYS]] : $options[self::OPTION_TIMEKEYS];
+        }
+
+        parent::__construct($options);
+    }
+
+    /**
+     * Apply this filter to the result data
+     *
+     * @param array  $result
+     * @param Server $server
+     *
+     * @return array
+     */
+    public function apply(array $result, Server $server)
+    {
+        // Send the results off to be iterated and return the updated result
+        return $this->iterate($result);
+    }
+
+    /**
+     * Home grown iterate function.  Would like to replace this with an internal PHP method(s) but could not find a way
+     * to make the iterate classes add new keys to the response.  They all seemed to be read-only.
+     *
+     * @todo: See if there is a more internal way of handling this instead of foreach looping and recursive calling
+     *
+     * @param array $result
+     *
+     * @return array
+     */
+    protected function iterate(array &$result)
+    {
+        // Iterate over the results
+        foreach ($result as $key => $value) {
+            // Offload to itself if we have another array
+            if (is_array($value)) {
+                // Iterate and update the result
+                $result[$key] = $this->iterate($value);
+            } elseif (in_array($key, $this->options[self::OPTION_TIMEKEYS])) {
+                // Make sure the value is a float (throws E_WARNING in PHP 7.1+)
+                $value = floatval($value);
+                // We match one of the keys we are wanting to convert so add it and move on
+                $result[sprintf(self::RESULT_KEY, $key)] = sprintf(
+                    "%02d:%02d:%02d",
+                    floor($value / 3600),
+                    ($value / 60) % 60,
+                    $value % 60
+                );
+            }
+        }
+
+        return $result;
+    }
+}

+ 115 - 0
OGP/php-query/gameq/Filters/Stripcolors.php

@@ -0,0 +1,115 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Filters;
+
+use GameQ\Server;
+
+/**
+ * Class Strip Colors
+ *
+ * Strip color codes from UT and Quake based games
+ *
+ * @package GameQ\Filters
+ */
+class Stripcolors extends Base
+{
+
+    /**
+     * Apply this filter
+     *
+     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+     *
+     * @param array         $result
+     * @param \GameQ\Server $server
+     *
+     * @return array
+     */
+    public function apply(array $result, Server $server)
+    {
+
+        // No result passed so just return
+        if (empty($result)) {
+            return $result;
+        }
+
+        //$data = [];
+        //$data['raw'][ $server->id() ] = $result;
+
+        // Switch based on the base (not game) protocol
+        switch ($server->protocol()->getProtocol()) {
+            case 'quake2':
+            case 'quake3':
+            case 'doom3':
+                array_walk_recursive($result, [$this, 'stripQuake']);
+                break;
+            case 'unreal2':
+            case 'ut3':
+            case 'gamespy3':  //not sure if gamespy3 supports ut colors but won't hurt
+            case 'gamespy2':
+                array_walk_recursive($result, [$this, 'stripUnreal']);
+                break;
+            case 'source':
+                array_walk_recursive($result, [$this, 'stripSource']);
+                break;
+        }
+
+        /*$data['filtered'][ $server->id() ] = $result;
+        file_put_contents(
+            sprintf(
+                '%s/../../../tests/Filters/Providers/Stripcolors\%s_1.json',
+                __DIR__,
+                $server->protocol()->getProtocol()
+            ),
+            json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR)
+        );*/
+
+        // Return the stripped result
+        return $result;
+    }
+
+    /**
+     * Strip color codes from quake based games
+     *
+     * @param string $string
+     */
+    protected function stripQuake(&$string)
+    {
+        $string = preg_replace('#(\^.)#', '', $string);
+    }
+
+    /**
+     * Strip color codes from Unreal based games
+     *
+     * @param string $string
+     */
+    protected function stripUnreal(&$string)
+    {
+        $string = preg_replace('/\x1b.../', '', $string);
+    }
+
+    /**
+     * Strip color codes from Source based games
+     *
+     * @param string $string
+     */
+    protected function stripSource(&$string)
+    {
+        $string = strip_tags($string);
+    }
+}

+ 47 - 0
OGP/php-query/gameq/Filters/Test.php

@@ -0,0 +1,47 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Filters;
+
+use GameQ\Server;
+
+/**
+ * Class Test
+ *
+ * This is a test filter to be used for testing purposes only.
+ *
+ * @package GameQ\Filters
+ */
+class Test extends Base
+{
+    /**
+     * Apply the filter.  For this we just return whatever is sent
+     *
+     * @SuppressWarnings(PHPMD)
+     *
+     * @param array         $result
+     * @param \GameQ\Server $server
+     *
+     * @return array
+     */
+    public function apply(array $result, Server $server)
+    {
+
+        return $result;
+    }
+}

+ 658 - 0
OGP/php-query/gameq/GameQ.php

@@ -0,0 +1,658 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ;
+
+use GameQ\Exception\Protocol as ProtocolException;
+use GameQ\Exception\Query as QueryException;
+
+/**
+ * Base GameQ Class
+ *
+ * This class should be the only one that is included when you use GameQ to query
+ * any games servers.
+ *
+ * Requirements: See wiki or README for more information on the requirements
+ *  - PHP 5.4.14+
+ *    * Bzip2 - http://www.php.net/manual/en/book.bzip2.php
+ *
+ * @author Austin Bischoff <[email protected]>
+ *
+ * @property bool   $debug
+ * @property string $capture_packets_file
+ * @property int    $stream_timeout
+ * @property int    $timeout
+ * @property int    $write_wait
+ */
+class GameQ
+{
+    /*
+     * Constants
+     */
+    const PROTOCOLS_DIRECTORY = __DIR__ . '/Protocols';
+
+    /* Static Section */
+
+    /**
+     * Holds the instance of itself
+     *
+     * @type self
+     */
+    protected static $instance = null;
+
+    /**
+     * Create a new instance of this class
+     *
+     * @return \GameQ\GameQ
+     */
+    public static function factory()
+    {
+
+        // Create a new instance
+        self::$instance = new self();
+
+        // Return this new instance
+        return self::$instance;
+    }
+
+    /* Dynamic Section */
+
+    /**
+     * Default options
+     *
+     * @type array
+     */
+    protected $options = [
+        'debug'                => false,
+        'timeout'              => 3, // Seconds
+        'filters'              => [
+            // Default normalize
+            'normalize_d751713988987e9331980363e24189ce' => [
+                'filter'  => 'normalize',
+                'options' => [],
+            ],
+        ],
+        // Advanced settings
+        'stream_timeout'       => 200000, // See http://www.php.net/manual/en/function.stream-select.php for more info
+        'write_wait'           => 500,
+        // How long (in micro-seconds) to pause between writing to server sockets, helps cpu usage
+
+        // Used for generating protocol test data
+        'capture_packets_file' => null,
+    ];
+
+    /**
+     * Array of servers being queried
+     *
+     * @type array
+     */
+    protected $servers = [];
+
+    /**
+     * The query library to use.  Default is Native
+     *
+     * @type string
+     */
+    protected $queryLibrary = 'GameQ\\Query\\Native';
+
+    /**
+     * Holds the instance of the queryLibrary
+     *
+     * @type \GameQ\Query\Core|null
+     */
+    protected $query = null;
+
+    /**
+     * GameQ constructor.
+     *
+     * Do some checks as needed so this will operate
+     */
+    public function __construct()
+    {
+        // Check for missing utf8_encode function
+        if (!function_exists('utf8_encode')) {
+            throw new \Exception("PHP's utf8_encode() function is required - "
+                . "http://php.net/manual/en/function.utf8-encode.php.  Check your php installation.");
+        }
+    }
+
+    /**
+     * Get an option's value
+     *
+     * @param mixed $option
+     *
+     * @return mixed|null
+     */
+    public function __get($option)
+    {
+
+        return isset($this->options[$option]) ? $this->options[$option] : null;
+    }
+
+    /**
+     * Set an option's value
+     *
+     * @param mixed $option
+     * @param mixed $value
+     *
+     * @return bool
+     */
+    public function __set($option, $value)
+    {
+
+        $this->options[$option] = $value;
+
+        return true;
+    }
+
+    /**
+     * Chainable call to __set, uses set as the actual setter
+     *
+     * @param mixed $var
+     * @param mixed $value
+     *
+     * @return $this
+     */
+    public function setOption($var, $value)
+    {
+
+        // Use magic
+        $this->{$var} = $value;
+
+        return $this; // Make chainable
+    }
+
+    /**
+     * Add a single server
+     *
+     * @param array $server_info
+     *
+     * @return $this
+     */
+    public function addServer(array $server_info = [])
+    {
+
+        // Add and validate the server
+        $this->servers[uniqid()] = new Server($server_info);
+
+        return $this; // Make calls chainable
+    }
+
+    /**
+     * Add multiple servers in a single call
+     *
+     * @param array $servers
+     *
+     * @return $this
+     */
+    public function addServers(array $servers = [])
+    {
+
+        // Loop through all the servers and add them
+        foreach ($servers as $server_info) {
+            $this->addServer($server_info);
+        }
+
+        return $this; // Make calls chainable
+    }
+
+    /**
+     * Add a set of servers from a file or an array of files.
+     * Supported formats:
+     * JSON
+     *
+     * @param array $files
+     *
+     * @return $this
+     * @throws \Exception
+     */
+    public function addServersFromFiles($files = [])
+    {
+
+        // Since we expect an array let us turn a string (i.e. single file) into an array
+        if (!is_array($files)) {
+            $files = [$files];
+        }
+
+        // Iterate over the file(s) and add them
+        foreach ($files as $file) {
+            // Check to make sure the file exists and we can read it
+            if (!file_exists($file) || !is_readable($file)) {
+                continue;
+            }
+
+            // See if this file is JSON
+            if (($servers = json_decode(file_get_contents($file), true)) === null
+                && json_last_error() !== JSON_ERROR_NONE
+            ) {
+                // Type not supported
+                continue;
+            }
+
+            // Add this list of servers
+            $this->addServers($servers);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Clear all of the defined servers
+     *
+     * @return $this
+     */
+    public function clearServers()
+    {
+
+        // Reset all the servers
+        $this->servers = [];
+
+        return $this; // Make Chainable
+    }
+
+    /**
+     * Add a filter to the processing list
+     *
+     * @param string $filterName
+     * @param array  $options
+     *
+     * @return $this
+     */
+    public function setFilter($filterName, $options = []){
+		return $this->addFilter($filterName, $options);
+	} 
+     
+    public function addFilter($filterName, $options = [])
+    {
+        // Create the filter hash so we can run multiple versions of the same filter
+        $filterHash = sprintf('%s_%s', strtolower($filterName), md5(json_encode($options)));
+
+        // Add the filter
+        $this->options['filters'][$filterHash] = [
+            'filter'  => strtolower($filterName),
+            'options' => $options,
+        ];
+
+        unset($filterHash);
+
+        return $this;
+    }
+
+    /**
+     * Remove an added filter
+     *
+     * @param string $filterHash
+     *
+     * @return $this
+     */
+    public function removeFilter($filterHash)
+    {
+        // Make lower case
+        $filterHash = strtolower($filterHash);
+
+        // Remove this filter if it has been defined
+        if (array_key_exists($filterHash, $this->options['filters'])) {
+            unset($this->options['filters'][$filterHash]);
+        }
+
+        unset($filterHash);
+
+        return $this;
+    }
+
+    /**
+     * Return the list of applied filters
+     *
+     * @return array
+     */
+    public function listFilters()
+    {
+        return $this->options['filters'];
+    }
+
+    /**
+     * Main method used to actually process all of the added servers and return the information
+     *
+     * @return array
+     * @throws \Exception
+     */
+     
+    public function requestData(){
+		return $this->process();
+	}
+     
+    public function process()
+    {
+
+        // Initialize the query library we are using
+        $class = new \ReflectionClass($this->queryLibrary);
+
+        // Set the query pointer to the new instance of the library
+        $this->query = $class->newInstance();
+
+        unset($class);
+
+        // Define the return
+        $results = [];
+
+        // @todo: Add break up into loop to split large arrays into smaller chunks
+
+        // Do server challenge(s) first, if any
+        $this->doChallenges();
+
+        // Do packets for server(s) and get query responses
+        $this->doQueries();
+
+        // Now we should have some information to process for each server
+        foreach ($this->servers as $server) {
+            /* @var $server \GameQ\Server */
+
+            // Parse the responses for this server
+            $result = $this->doParseResponse($server);
+
+            // Apply the filters
+            $result = array_merge($result, $this->doApplyFilters($result, $server));
+
+            // Sort the keys so they are alphabetical and nicer to look at
+            ksort($result);
+
+            // Add the result to the results array
+            $results[$server->id()] = $result;
+        }
+
+        return $results;
+    }
+
+    /**
+     * Do server challenges, where required
+     */
+    protected function doChallenges()
+    {
+
+        // Initialize the sockets for reading
+        $sockets = [];
+
+        // By default we don't have any challenges to process
+        $server_challenge = false;
+
+        // Do challenge packets
+        foreach ($this->servers as $server_id => $server) {
+            /* @var $server \GameQ\Server */
+
+            // This protocol has a challenge packet that needs to be sent
+            if ($server->protocol()->hasChallenge()) {
+                // We have a challenge, set the flag
+                $server_challenge = true;
+
+                // Let's make a clone of the query class
+                $socket = clone $this->query;
+
+                // Set the information for this query socket
+                $socket->set(
+                    $server->protocol()->transport(),
+                    $server->ip,
+                    $server->port_query,
+                    $this->timeout
+                );
+
+                try {
+                    // Now write the challenge packet to the socket.
+                    $socket->write($server->protocol()->getPacket(Protocol::PACKET_CHALLENGE));
+
+                    // Add the socket information so we can reference it easily
+                    $sockets[(int)$socket->get()] = [
+                        'server_id' => $server_id,
+                        'socket'    => $socket,
+                    ];
+                } catch (QueryException $e) {
+                    // Check to see if we are in debug, if so bubble up the exception
+                    if ($this->debug) {
+                        throw new \Exception($e->getMessage(), $e->getCode(), $e);
+                    }
+                }
+
+                unset($socket);
+
+                // Let's sleep shortly so we are not hammering out calls rapid fire style hogging cpu
+                usleep($this->write_wait);
+            }
+        }
+
+        // We have at least one server with a challenge, we need to listen for responses
+        if ($server_challenge) {
+            // Now we need to listen for and grab challenge response(s)
+            $responses = call_user_func_array(
+                [$this->query, 'getResponses'],
+                [$sockets, $this->timeout, $this->stream_timeout]
+            );
+
+            // Iterate over the challenge responses
+            foreach ($responses as $socket_id => $response) {
+                // Back out the server_id we need to update the challenge response for
+                $server_id = $sockets[$socket_id]['server_id'];
+
+                // Make this into a buffer so it is easier to manipulate
+                $challenge = new Buffer(implode('', $response));
+
+                // Grab the server instance
+                /* @var $server \GameQ\Server */
+                $server = $this->servers[$server_id];
+
+                // Apply the challenge
+                $server->protocol()->challengeParseAndApply($challenge);
+
+                // Add this socket to be reused, has to be reused in GameSpy3 for example
+                $server->socketAdd($sockets[$socket_id]['socket']);
+
+                // Clear
+                unset($server);
+            }
+        }
+    }
+
+    /**
+     * Run the actual queries and get the response(s)
+     */
+    protected function doQueries()
+    {
+
+        // Initialize the array of sockets
+        $sockets = [];
+
+        // Iterate over the server list
+        foreach ($this->servers as $server_id => $server) {
+            /* @var $server \GameQ\Server */
+
+            // Invoke the beforeSend method
+            $server->protocol()->beforeSend($server);
+
+            // Get all the non-challenge packets we need to send
+            $packets = $server->protocol()->getPacket('!' . Protocol::PACKET_CHALLENGE);
+
+            if (count($packets) == 0) {
+                // Skip nothing else to do for some reason.
+                continue;
+            }
+
+            // Try to use an existing socket
+            if (($socket = $server->socketGet()) === null) {
+                // Let's make a clone of the query class
+                $socket = clone $this->query;
+
+                // Set the information for this query socket
+                $socket->set(
+                    $server->protocol()->transport(),
+                    $server->ip,
+                    $server->port_query,
+                    $this->timeout
+                );
+            }
+
+            try {
+                // Iterate over all the packets we need to send
+                foreach ($packets as $packet_data) {
+                    // Now write the packet to the socket.
+                    $socket->write($packet_data);
+
+                    // Let's sleep shortly so we are not hammering out calls rapid fire style
+                    usleep($this->write_wait);
+                }
+
+                unset($packets);
+
+                // Add the socket information so we can reference it easily
+                $sockets[(int)$socket->get()] = [
+                    'server_id' => $server_id,
+                    'socket'    => $socket,
+                ];
+            } catch (QueryException $e) {
+                // Check to see if we are in debug, if so bubble up the exception
+                if ($this->debug) {
+                    throw new \Exception($e->getMessage(), $e->getCode(), $e);
+                }
+
+                break;
+            }
+
+            // Clean up the sockets, if any left over
+            $server->socketCleanse();
+        }
+
+        // Now we need to listen for and grab response(s)
+        $responses = call_user_func_array(
+            [$this->query, 'getResponses'],
+            [$sockets, $this->timeout, $this->stream_timeout]
+        );
+
+        // Iterate over the responses
+        foreach ($responses as $socket_id => $response) {
+            // Back out the server_id
+            $server_id = $sockets[$socket_id]['server_id'];
+
+            // Grab the server instance
+            /* @var $server \GameQ\Server */
+            $server = $this->servers[$server_id];
+
+            // Save the response from this packet
+            $server->protocol()->packetResponse($response);
+
+            unset($server);
+        }
+
+        // Now we need to close all of the sockets
+        foreach ($sockets as $socketInfo) {
+            /* @var $socket \GameQ\Query\Core */
+            $socket = $socketInfo['socket'];
+
+            // Close the socket
+            $socket->close();
+
+            unset($socket);
+        }
+
+        unset($sockets);
+    }
+
+    /**
+     * Parse the response for a specific server
+     *
+     * @param \GameQ\Server $server
+     *
+     * @return array
+     * @throws \Exception
+     */
+    protected function doParseResponse(Server $server)
+    {
+
+        try {
+            // @codeCoverageIgnoreStart
+            // We want to save this server's response to a file (useful for unit testing)
+            if (!is_null($this->capture_packets_file)) {
+                file_put_contents(
+                    $this->capture_packets_file,
+                    implode(PHP_EOL . '||' . PHP_EOL, $server->protocol()->packetResponse())
+                );
+            }
+            // @codeCoverageIgnoreEnd
+
+            // Get the server response
+            $results = $server->protocol()->processResponse();
+
+            // Check for online before we do anything else
+            $results['gq_online'] = (count($results) > 0);
+        } catch (ProtocolException $e) {
+            // Check to see if we are in debug, if so bubble up the exception
+            if ($this->debug) {
+                throw new \Exception($e->getMessage(), $e->getCode(), $e);
+            }
+
+            // We ignore this server
+            $results = [
+                'gq_online' => false,
+            ];
+        }
+
+        // Now add some default stuff
+        $results['gq_address'] = (isset($results['gq_address'])) ? $results['gq_address'] : $server->ip();
+        $results['gq_port_client'] = $server->portClient();
+        $results['gq_port_query'] = (isset($results['gq_port_query'])) ? $results['gq_port_query'] : $server->portQuery();
+        $results['gq_protocol'] = $server->protocol()->getProtocol();
+        $results['gq_type'] = (string)$server->protocol();
+        $results['gq_name'] = $server->protocol()->nameLong();
+        $results['gq_transport'] = $server->protocol()->transport();
+
+        // Process the join link
+        if (!isset($results['gq_joinlink']) || empty($results['gq_joinlink'])) {
+            $results['gq_joinlink'] = $server->getJoinLink();
+        }
+
+        return $results;
+    }
+
+    /**
+     * Apply any filters to the results
+     *
+     * @param array         $results
+     * @param \GameQ\Server $server
+     *
+     * @return array
+     */
+    protected function doApplyFilters(array $results, Server $server)
+    {
+
+        // Loop over the filters
+        foreach ($this->options['filters'] as $filterOptions) {
+            // Try to do this filter
+            try {
+                // Make a new reflection class
+                $class = new \ReflectionClass(sprintf('GameQ\\Filters\\%s', ucfirst($filterOptions['filter'])));
+
+                // Create a new instance of the filter class specified
+                $filter = $class->newInstanceArgs([$filterOptions['options']]);
+
+                // Apply the filter to the data
+                $results = $filter->apply($results, $server);
+            } catch (\ReflectionException $e) {
+                // Invalid, skip it
+                continue;
+            }
+        }
+
+        return $results;
+    }
+}

+ 82 - 0
OGP/php-query/gameq/GameQMonitor.php

@@ -0,0 +1,82 @@
+<?php
+global $settings;
+// Skip server queries if there are too many total servers
+if(isset($_SESSION))
+	$num_of_servers = $db->getNumberOfOwnedServersPerUser( $_SESSION['user_id'] );
+else
+	$num_of_servers = 0;
+	
+if(isset($settings['query_num_servers_stop']) && is_numeric($settings['query_num_servers_stop']))
+	$numberservers_to_skip_query = $settings['query_num_servers_stop'];
+else
+	$numberservers_to_skip_query = 15;
+
+if($num_of_servers < $numberservers_to_skip_query)
+{
+	if ( $server_home['use_nat'] == 1 )
+		$internal_query_ip = $server_home['agent_ip'];
+	else
+		$internal_query_ip = $server_home['ip'];
+	
+	$query_cache_life = ( isset($settings['query_cache_life']) and is_numeric($settings['query_cache_life']) )? $settings['query_cache_life'] : 30;
+	$ip_id = $db->getIpIdByIp($server_home['ip']);
+	$statusCache = $db->getServerStatusCache($ip_id,$port);
+	if( !empty($statusCache) AND date('YmdHis',$statusCache['date_timestamp'] + $query_cache_life) >= date('YmdHis') )
+	{
+		$results = $statusCache;
+	}
+	else
+	{
+		require_once 'protocol/GameQ/Autoloader.php';
+		$port = $server_home['port'];
+		$query_port = get_query_port($server_xml, $port);
+		$gq = new \GameQ\GameQ();
+		$server = array(
+							'id' => 'server',
+							'type' => $server_xml->gameq_query_name,
+							'host' => $internal_query_ip . ":" . $query_port,
+						);
+		$gq->addServer($server);
+		$gq->setOption('timeout', 4);
+		$gq->setOption('debug', FALSE);
+		$gq->addFilter('normalise');
+		$results = $gq->process();
+		$db->saveServerStatusCache($ip_id,$port,$results);
+	}
+
+	if($results['server']['gq_online'] == 1)
+	{
+		$status = "online";
+		// Some functions to print the results
+		$players = $results['server']['gq_numplayers'];
+		$playersmax = $results['server']['gq_maxplayers'];
+		$name = $results['server']['gq_hostname'];
+		$map  = preg_replace("/[^a-z0-9_]/", "_", strtolower($results['server']['gq_mapname']));
+		
+		//----------+ patches for voice servers (ts2, ts3, ventrilo)
+		if(!$map)$map = $results['server']['gq_type'];
+		if(!$players)$players = 0;
+
+		@$stats_players += $players;       // COUNT VISIBLE NUMBER OF PLAYERS
+		@$stats_maxplayers += $playersmax;    // COUNT VISIBLE NUMBER OF SLOTS
+
+		if ( $results['server']['gq_numplayers'] > 0 )
+			$player_list = print_player_list_gameq($results['server']['players'],$players,$playersmax);
+		if(isset($results['gq_joinlink']) and $results['gq_joinlink'] != "")
+			$address = "<a href='$results[gq_joinlink]'>$ip:$port</a>";
+		elseif($server_xml->installer == 'steamcmd')
+			$address = "<a href='steam://connect/$internal_query_ip:$port'>$ip:$port</a>";
+		else
+			$address = "$ip:$port";
+		$playersList = $results['server']['players'];
+		$maplocation = get_map_path($query_name,$mod,$map);
+	}
+	else 
+		$status = "half";
+}
+else
+{
+	$status = "half";
+	$notifications = get_lang_f('queries_disabled_by_setting_disable_queries_after',$numberservers_to_skip_query,$num_of_servers);
+}
+?>

+ 500 - 0
OGP/php-query/gameq/Protocol.php

@@ -0,0 +1,500 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ */
+
+namespace GameQ;
+
+/**
+ * Handles the core functionality for the protocols
+ *
+ * @SuppressWarnings(PHPMD.NumberOfChildren)
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+abstract class Protocol
+{
+
+    /**
+     * Constants for class states
+     */
+    const STATE_TESTING = 1;
+
+    const STATE_BETA = 2;
+
+    const STATE_STABLE = 3;
+
+    const STATE_DEPRECATED = 4;
+
+    /**
+     * Constants for packet keys
+     */
+    const PACKET_ALL = 'all'; // Some protocols allow all data to be sent back in one call.
+
+    const PACKET_BASIC = 'basic';
+
+    const PACKET_CHALLENGE = 'challenge';
+
+    const PACKET_CHANNELS = 'channels'; // Voice servers
+
+    const PACKET_DETAILS = 'details';
+
+    const PACKET_INFO = 'info';
+
+    const PACKET_PLAYERS = 'players';
+
+    const PACKET_STATUS = 'status';
+
+    const PACKET_RULES = 'rules';
+
+    const PACKET_VERSION = 'version';
+
+    /**
+     * Transport constants
+     */
+    const TRANSPORT_UDP = 'udp';
+
+    const TRANSPORT_TCP = 'tcp';
+
+    const TRANSPORT_SSL = 'ssl';
+
+    const TRANSPORT_TLS = 'tls';
+
+    /**
+     * Short name of the protocol
+     *
+     * @type string
+     */
+    protected $name = 'unknown';
+
+    /**
+     * The longer, fancier name for the protocol
+     *
+     * @type string
+     */
+    protected $name_long = 'unknown';
+
+    /**
+     * The difference between the client port and query port
+     *
+     * @type int
+     */
+    protected $port_diff = 0;
+
+    /**
+     * The transport method to use to actually send the data
+     * Default is UDP
+     *
+     * @type string
+     */
+    protected $transport = self::TRANSPORT_UDP;
+
+    /**
+     * The protocol type used when querying the server
+     *
+     * @type string
+     */
+    protected $protocol = 'unknown';
+
+    /**
+     * Holds the valid packet types this protocol has available.
+     *
+     * @type array
+     */
+    protected $packets = [];
+
+    /**
+     * Holds the response headers and the method to use to process them.
+     *
+     * @type array
+     */
+    protected $responses = [];
+
+    /**
+     * Holds the list of methods to run when parsing the packet response(s) data. These
+     * methods should provide all the return information.
+     *
+     * @type array
+     */
+    protected $process_methods = [];
+
+    /**
+     * The packet responses received
+     *
+     * @type array
+     */
+    protected $packets_response = [];
+
+    /**
+     * Holds the instance of the result class
+     *
+     * @type null
+     */
+    protected $result = null;
+
+    /**
+     * Options for this protocol
+     *
+     * @type array
+     */
+    protected $options = [];
+
+    /**
+     * Define the state of this class
+     *
+     * @type int
+     */
+    protected $state = self::STATE_STABLE;
+
+    /**
+     * Holds specific normalize settings
+     *
+     * @todo: Remove this ugly bulk by moving specific ones to their specific game(s)
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => [
+                'listenserver',
+                'dedic',
+                'bf2dedicated',
+                'netserverdedicated',
+                'bf2142dedicated',
+                'dedicated',
+            ],
+            'gametype'   => ['ggametype', 'sigametype', 'matchtype'],
+            'hostname'   => ['svhostname', 'servername', 'siname', 'name'],
+            'mapname'    => ['map', 'simap'],
+            'maxplayers' => ['svmaxclients', 'simaxplayers', 'maxclients', 'max_players'],
+            'mod'        => ['game', 'gamedir', 'gamevariant'],
+            'numplayers' => ['clients', 'sinumplayers', 'num_players'],
+            'password'   => ['protected', 'siusepass', 'sineedpass', 'pswrd', 'gneedpass', 'auth', 'passsord'],
+        ],
+        // Indvidual
+        'player'  => [
+            'name'   => ['nick', 'player', 'playername', 'name'],
+            'kills'  => ['kills'],
+            'deaths' => ['deaths'],
+            'score'  => ['kills', 'frags', 'skill', 'score'],
+            'ping'   => ['ping'],
+        ],
+        // Team
+        'team'    => [
+            'name'  => ['name', 'teamname', 'team_t'],
+            'score' => ['score', 'score_t'],
+        ],
+    ];
+
+    /**
+     * Quick join link
+     *
+     * @type string
+     */
+    protected $join_link = '';
+
+    /**
+     * @param array $options
+     */
+    public function __construct(array $options = [])
+    {
+
+        // Set the options for this specific instance of the class
+        $this->options = $options;
+    }
+
+    /**
+     * String name of this class
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+
+        return $this->name;
+    }
+
+    /**
+     * Get the port difference between the server's client (game) and query ports
+     *
+     * @return int
+     */
+    public function portDiff()
+    {
+
+        return $this->port_diff;
+    }
+
+    /**
+     * "Find" the query port based off of the client port and port_diff
+     *
+     * This method is meant to be overloaded for more complex maths or lookup tables
+     *
+     * @param int $clientPort
+     *
+     * @return int
+     */
+    public function findQueryPort($clientPort)
+    {
+
+        return $clientPort + $this->port_diff;
+    }
+
+    /**
+     * Return the join_link as defined by the protocol class
+     *
+     * @return string
+     */
+    public function joinLink()
+    {
+
+        return $this->join_link;
+    }
+
+    /**
+     * Short (callable) name of this class
+     *
+     * @return string
+     */
+    public function name()
+    {
+
+        return $this->name;
+    }
+
+    /**
+     * Long name of this class
+     *
+     * @return string
+     */
+    public function nameLong()
+    {
+
+        return $this->name_long;
+    }
+
+    /**
+     * Return the status of this Protocol Class
+     *
+     * @return int
+     */
+    public function state()
+    {
+
+        return $this->state;
+    }
+
+    /**
+     * Return the protocol property
+     *
+     * @return string
+     */
+    public function getProtocol()
+    {
+
+        return $this->protocol;
+    }
+
+    /**
+     * Get/set the transport type for this protocol
+     *
+     * @param string|null $type
+     *
+     * @return string
+     */
+    public function transport($type = null)
+    {
+
+        // Act as setter
+        if (!is_null($type)) {
+            $this->transport = $type;
+        }
+
+        return $this->transport;
+    }
+
+    /**
+     * Set the options for the protocol call
+     *
+     * @param array $options
+     *
+     * @return array
+     */
+    public function options($options = [])
+    {
+
+        // Act as setter
+        if (!empty($options)) {
+            $this->options = $options;
+        }
+
+        return $this->options;
+    }
+
+
+    /*
+     * Packet Section
+     */
+
+    /**
+     * Return specific packet(s)
+     *
+     * @param array $type
+     *
+     * @return array
+     */
+    public function getPacket($type = [])
+    {
+
+        $packets = [];
+
+
+        // We want an array of packets back
+        if (is_array($type) && !empty($type)) {
+            // Loop the packets
+            foreach ($this->packets as $packet_type => $packet_data) {
+                // We want this packet
+                if (in_array($packet_type, $type)) {
+                    $packets[$packet_type] = $packet_data;
+                }
+            }
+        } elseif ($type == '!challenge') {
+            // Loop the packets
+            foreach ($this->packets as $packet_type => $packet_data) {
+                // Dont want challenge packets
+                if ($packet_type != self::PACKET_CHALLENGE) {
+                    $packets[$packet_type] = $packet_data;
+                }
+            }
+        } elseif (is_string($type)) {
+            // Return specific packet type
+            $packets = $this->packets[$type];
+        } else {
+            // Return all packets
+            $packets = $this->packets;
+        }
+
+        // Return the packets
+        return $packets;
+    }
+
+    /**
+     * Get/set the packet response
+     *
+     * @param array|null $response
+     *
+     * @return array
+     */
+    public function packetResponse(array $response = null)
+    {
+
+        // Act as setter
+        if (!empty($response)) {
+            $this->packets_response = $response;
+        }
+
+        return $this->packets_response;
+    }
+
+
+    /*
+     * Challenge section
+     */
+
+    /**
+     * Determine whether or not this protocol has a challenge needed before querying
+     *
+     * @return bool
+     */
+    public function hasChallenge()
+    {
+
+        return (isset($this->packets[self::PACKET_CHALLENGE]) && !empty($this->packets[self::PACKET_CHALLENGE]));
+    }
+
+    /**
+     * Parse the challenge response and add it to the buffer items that need it.
+     * This should be overloaded by extending class
+     *
+     * @codeCoverageIgnore
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     *
+     * @param \GameQ\Buffer $challenge_buffer
+     *
+     * @return bool
+     */
+    public function challengeParseAndApply(Buffer $challenge_buffer)
+    {
+
+        return true;
+    }
+
+    /**
+     * Apply the challenge string to all the packets that need it.
+     *
+     * @param string $challenge_string
+     *
+     * @return bool
+     */
+    protected function challengeApply($challenge_string)
+    {
+
+        // Let's loop through all the packets and append the challenge where it is needed
+        foreach ($this->packets as $packet_type => $packet) {
+            $this->packets[$packet_type] = sprintf($packet, $challenge_string);
+        }
+
+        return true;
+    }
+
+    /**
+     * Get the normalize settings for the protocol
+     *
+     * @return array
+     */
+    public function getNormalize()
+    {
+
+        return $this->normalize;
+    }
+
+    /*
+     * General
+     */
+
+    /**
+     * Generic method to allow protocol classes to do work right before the query is sent
+     *
+     * @codeCoverageIgnore
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     *
+     * @param \GameQ\Server $server
+     */
+    public function beforeSend(Server $server)
+    {
+    }
+
+    /**
+     * Method called to process query response data.  Each extending class has to have one of these functions.
+     *
+     * @return mixed
+     */
+    abstract public function processResponse();
+}

+ 53 - 0
OGP/php-query/gameq/Protocols/Aa3.php

@@ -0,0 +1,53 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Aa3
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Aa3 extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'aa3';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "America's Army 3";
+
+    /**
+     * Query port = client_port + 18243
+     *
+     * client_port default 8777
+     * query_port default 27020
+     *
+     * @type int
+     */
+    protected $port_diff = 18243;
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Aapg.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Aapg
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Aapg extends Aa3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'aapg';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "America's Army: Proving Grounds";
+}

+ 51 - 0
OGP/php-query/gameq/Protocols/Arkse.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class ARK: Survival Evolved
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Arkse extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'arkse';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "ARK: Survival Evolved";
+
+    /**
+     * query_port = client_port + 19238
+     * 27015 = 7777 + 19238
+     *
+     * @type int
+     */
+    protected $port_diff = 19238;
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Arma.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Arma
+ *
+ * @package GameQ\Protocols
+ *
+ * @author  Wilson Jesus <>
+ */
+class Arma extends Gamespy2
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'arma';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "ArmA Armed Assault";
+}

+ 181 - 0
OGP/php-query/gameq/Protocols/Arma3.php

@@ -0,0 +1,181 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Buffer;
+use GameQ\Result;
+
+/**
+ * Class Armed Assault 3
+ *
+ * Rules protocol reference: https://community.bistudio.com/wiki/Arma_3_ServerBrowserProtocol2
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ * @author  Memphis017 <https://github.com/Memphis017>
+ */
+class Arma3 extends Source
+{
+    /**
+     * Defines the names for the specific game DLCs
+     *
+     * @var array
+     */
+    protected $dlcNames = [
+        'af82811b' => 'Karts',
+        '94f76a1a' => 'Marksmen',
+        'd0356eec' => 'Helicopters',
+        '19984a71' => 'Zeus',
+        '7fb4b1f3' => 'Apex',
+        '49c2c12b' => 'Jets',
+        '7e766e18' => 'Laws of War',
+        '99d71f90' => 'Malden',
+        'a8b10cdf' => 'Tac-Ops',
+        '37680ce8' => 'Tanks',
+        '43f9c377' => 'Contact',
+        'c4979557' => 'Enoch',
+    ];
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'arma3';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Arma3";
+
+    /**
+     * Query port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+
+    /**
+     * Process the rules since Arma3 changed their response for rules
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    protected function processRules(Buffer $buffer)
+    {
+        // Total number of packets, burn it
+        $buffer->readInt16();
+
+        // Will hold the data string
+        $data = '';
+
+        // Loop until we run out of strings
+        while ($buffer->getLength()) {
+            // Burn the delimiters (i.e. \x01\x04\x00)
+            $buffer->readString();
+
+            // Add the data to the string, we are reassembling it
+            $data .= $buffer->readString();
+        }
+
+        // Restore escaped sequences
+        $data = str_replace(["\x01\x01", "\x01\x02", "\x01\x03"], ["\x01", "\x00", "\xFF"], $data);
+
+        // Make a new buffer with the reassembled data
+        $responseBuffer = new Buffer($data);
+
+        // Kill the old buffer, should be empty
+        unset($buffer, $data);
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Get results
+        $result->add('rules_protocol_version', $responseBuffer->readInt8());
+        $result->add('overflow', $responseBuffer->readInt8());
+        $dlcBit = decbin($responseBuffer->readInt8()); // Grab DLC bit 1 and use it later
+        $dlcBit2 = decbin($responseBuffer->readInt8()); // Grab DLC bit 2 and use it later
+        $dlcCount = substr_count($dlcBit, '1') + substr_count($dlcBit2, '1'); // Count the DLCs
+
+        // Grab difficulty so we can man handle it...
+        $difficulty = $responseBuffer->readInt8();
+
+        // Process difficulty
+        $result->add('3rd_person', $difficulty >> 7);
+        $result->add('advanced_flight_mode', ($difficulty >> 6) & 1);
+        $result->add('difficulty_ai', ($difficulty >> 3) & 3);
+        $result->add('difficulty_level', $difficulty & 3);
+
+        unset($difficulty);
+
+        // Crosshair
+        $result->add('crosshair', $responseBuffer->readInt8());
+
+        // Loop over the DLC bit so we can pull in the info for the DLC (if enabled)
+        for ($x = 0; $x < $dlcCount; $x++) {
+            $dlcHash = dechex($responseBuffer->readInt32());
+            isset($this->dlcNames[$dlcHash]) ?
+                $result->addSub('dlcs', 'name', $this->dlcNames[$dlcHash])
+                : $result->addSub('dlcs', 'name', 'Unknown');
+            $result->addSub('dlcs', 'hash', $dlcHash);
+        }
+
+        // No longer needed
+        unset($dlcBit, $dlcBit2, $dlcCount, $dlcHash);
+
+        // Grab the mod count
+        $modCount = $responseBuffer->readInt8();
+
+        // Add mod count
+        $result->add('mod_count', $modCount);
+
+        // Loop the mod count and add them
+        for ($x = 0; $x < $modCount; $x++) {
+            // Add the mod to the list
+            $result->addSub('mods', 'hash', dechex($responseBuffer->readInt32()));
+            $result->addSub('mods', 'steam_id', hexdec($responseBuffer->readPascalString(0, true)));
+            $result->addSub('mods', 'name', $responseBuffer->readPascalString(0, true));
+        }
+
+        unset($modCount, $x);
+
+        // Get the signatures count
+        $signatureCount = $responseBuffer->readInt8();
+        $result->add('signature_count', $signatureCount);
+
+        // Make signatures array
+        $signatures = [];
+
+        // Loop until we run out of signatures
+        for ($x = 0; $x < $signatureCount; $x++) {
+            $signatures[] = $responseBuffer->readPascalString(0, true);
+        }
+
+        // Add as a simple array
+        $result->add('signatures', $signatures);
+
+        unset($responseBuffer, $signatureCount, $signatures, $x);
+
+        return $result->fetch();
+    }
+}

+ 50 - 0
OGP/php-query/gameq/Protocols/Armedassault2oa.php

@@ -0,0 +1,50 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Armedassault2oa
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Armedassault2oa extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = "armedassault2oa";
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Armed Assault 2: Operation Arrowhead";
+
+    /**
+     * Query port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+}

+ 32 - 0
OGP/php-query/gameq/Protocols/Armedassault3.php

@@ -0,0 +1,32 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Armed assault 3 dummy Protocol Class
+ *
+ * Added for backward compatibility, please update to class arma3
+ *
+ * @deprecated v3.0.10
+ * @package    GameQ\Protocols
+ * @author     Austin Bischoff <[email protected]>
+ */
+class Armedassault3 extends Arma3
+{
+}

+ 208 - 0
OGP/php-query/gameq/Protocols/Ase.php

@@ -0,0 +1,208 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+
+/**
+ * All-Seeing Eye Protocol class
+ *
+ * @author Marcel Bößendörfer <[email protected]>
+ * @author Austin Bischoff <[email protected]>
+ */
+class Ase extends Protocol
+{
+
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_ALL => "s",
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'ase';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'ase';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "All-Seeing Eye";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = null;
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'gametype'   => 'gametype',
+            'hostname'   => 'servername',
+            'mapname'    => 'map',
+            'maxplayers' => 'max_players',
+            'mod'        => 'game_dir',
+            'numplayers' => 'num_players',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name'  => 'name',
+            'score' => 'score',
+            'team'  => 'team',
+            'ping'  => 'ping',
+            'time'  => 'time',
+        ],
+    ];
+
+    /**
+     * Process the response
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function processResponse()
+    {
+
+        // Create a new buffer
+        $buffer = new Buffer(implode('', $this->packets_response));
+
+        // Burn the header
+        $buffer->skip(4);
+
+        // Create a new result
+        $result = new Result();
+
+        // Variables
+        $result->add('gamename', $buffer->readPascalString(1, true));
+        $result->add('port', $buffer->readPascalString(1, true));
+        $result->add('servername', $buffer->readPascalString(1, true));
+        $result->add('gametype', $buffer->readPascalString(1, true));
+        $result->add('map', $buffer->readPascalString(1, true));
+        $result->add('version', $buffer->readPascalString(1, true));
+        $result->add('password', $buffer->readPascalString(1, true));
+        $result->add('num_players', $buffer->readPascalString(1, true));
+        $result->add('max_players', $buffer->readPascalString(1, true));
+        $result->add('dedicated', 1);
+
+        // Offload the key/value pair processing
+        $this->processKeyValuePairs($buffer, $result);
+
+        // Offload processing player and team info
+        $this->processPlayersAndTeams($buffer, $result);
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /*
+     * Internal methods
+     */
+
+    /**
+     * Handles processing the extra key/value pairs for server settings
+     *
+     * @param \GameQ\Buffer $buffer
+     * @param \GameQ\Result $result
+     */
+    protected function processKeyValuePairs(Buffer &$buffer, Result &$result)
+    {
+
+        // Key / value pairs
+        while ($buffer->getLength()) {
+            $key = $buffer->readPascalString(1, true);
+
+            // If we have an empty key, we've reached the end
+            if (empty($key)) {
+                break;
+            }
+
+            // Otherwise, add the pair
+            $result->add(
+                $key,
+                $buffer->readPascalString(1, true)
+            );
+        }
+
+        unset($key);
+    }
+
+    /**
+     * Handles processing the player and team data into a usable format
+     *
+     * @param \GameQ\Buffer $buffer
+     * @param \GameQ\Result $result
+     */
+    protected function processPlayersAndTeams(Buffer &$buffer, Result &$result)
+    {
+
+        // Players and team info
+        while ($buffer->getLength()) {
+            // Get the flags
+            $flags = $buffer->readInt8();
+
+            // Get data according to the flags
+            if ($flags & 1) {
+                $result->addPlayer('name', $buffer->readPascalString(1, true));
+            }
+            if ($flags & 2) {
+                $result->addPlayer('team', $buffer->readPascalString(1, true));
+            }
+            if ($flags & 4) {
+                $result->addPlayer('skin', $buffer->readPascalString(1, true));
+            }
+            if ($flags & 8) {
+                $result->addPlayer('score', $buffer->readPascalString(1, true));
+            }
+            if ($flags & 16) {
+                $result->addPlayer('ping', $buffer->readPascalString(1, true));
+            }
+            if ($flags & 32) {
+                $result->addPlayer('time', $buffer->readPascalString(1, true));
+            }
+        }
+    }
+}

+ 55 - 0
OGP/php-query/gameq/Protocols/Atlas.php

@@ -0,0 +1,55 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Atlas
+ *
+ * @package GameQ\Protocols
+ * @author  Wilson Jesus <>
+ */
+class Atlas extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'atlas';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Atlas";
+
+    /**
+     * query_port = client_port + 51800
+     * 57561 = 5761 + 51800
+     *
+     * this is the default value for the stock game server, both ports
+     * can be independently changed from the stock ones,
+     * making the port_diff logic useless.
+     *
+     * @type int
+     */
+    protected $port_diff = 51800;
+}

+ 68 - 0
OGP/php-query/gameq/Protocols/Batt1944.php

@@ -0,0 +1,68 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Battalion 1944
+ *
+ * @package GameQ\Protocols
+ * @author  TacTicToe66 <https://github.com/TacTicToe66>
+ */
+class Batt1944 extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'batt1944';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Battalion 1944";
+
+    /**
+     * query_port = client_port + 3
+     *
+     * @type int
+     */
+    protected $port_diff = 3;
+
+    /**
+     * Normalize main fields
+     *
+     * @var array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target    => source
+            'gametype'   => 'bat_gamemode_s',
+            'hostname'   => 'bat_name_s',
+            'mapname'    => 'bat_map_s',
+            'maxplayers' => 'bat_max_players_i',
+            'numplayers' => 'bat_player_count_s',
+            'password'   => 'bat_has_password_s',
+        ],
+    ];
+}

+ 88 - 0
OGP/php-query/gameq/Protocols/Bf1942.php

@@ -0,0 +1,88 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Battlefield 1942
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Bf1942 extends Gamespy
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'bf1942';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Battlefield 1942";
+
+    /**
+     * query_port = client_port + 8433
+     * 23000 = 14567 + 8433
+     *
+     * @type int
+     */
+    protected $port_diff = 8433;
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = "bf1942://%s:%d";
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'gametype'   => 'gametype',
+            'hostname'   => 'hostname',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'maxplayers',
+            'numplayers' => 'numplayers',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name'   => 'playername',
+            'kills'  => 'kills',
+            'deaths' => 'deaths',
+            'ping'   => 'ping',
+            'score'  => 'score',
+        ],
+        'team'    => [
+            'name' => 'teamname',
+        ],
+    ];
+}

+ 98 - 0
OGP/php-query/gameq/Protocols/Bf2.php

@@ -0,0 +1,98 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Battlefield 2
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Bf2 extends Gamespy3
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'bf2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Battlefield 2";
+
+    /**
+     * query_port = client_port + 8433
+     * 29900 = 16567 + 13333
+     *
+     * @type int
+     */
+    protected $port_diff = 13333;
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = "bf2://%s:%d";
+
+    /**
+     * BF2 has a different query packet to send than "normal" Gamespy 3
+     *
+     * @var array
+     */
+    protected $packets = [
+        self::PACKET_ALL => "\xFE\xFD\x00\x10\x20\x30\x40\xFF\xFF\xFF\x01",
+    ];
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'gametype'   => 'gametype',
+            'hostname'   => 'hostname',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'maxplayers',
+            'numplayers' => 'numplayers',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name'   => 'player',
+            'kills'  => 'score',
+            'deaths' => 'deaths',
+            'ping'   => 'ping',
+            'score'  => 'score',
+        ],
+        'team'    => [
+            'name'  => 'team',
+            'score' => 'score',
+        ],
+    ];
+}

+ 348 - 0
OGP/php-query/gameq/Protocols/Bf3.php

@@ -0,0 +1,348 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+use GameQ\Exception\Protocol as Exception;
+
+/**
+ * Battlefield 3 Protocol Class
+ *
+ * Good place for doc status and info is http://www.fpsadmin.com/forum/showthread.php?t=24134
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Bf3 extends Protocol
+{
+
+    /**
+     * Array of packets we want to query.
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_STATUS  => "\x00\x00\x00\x21\x1b\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x00\x00serverInfo\x00",
+        self::PACKET_VERSION => "\x00\x00\x00\x22\x18\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00version\x00",
+        self::PACKET_PLAYERS =>
+            "\x00\x00\x00\x23\x24\x00\x00\x00\x02\x00\x00\x00\x0b\x00\x00\x00listPlayers\x00\x03\x00\x00\x00\x61ll\x00",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        1627389952 => "processDetails", // a
+        1644167168 => "processVersion", // b
+        1660944384 => "processPlayers", // c
+    ];
+
+    /**
+     * The transport mode for this protocol is TCP
+     *
+     * @type string
+     */
+    protected $transport = self::TRANSPORT_TCP;
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'bf3';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'bf3';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Battlefield 3";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = null;
+
+    /**
+     * query_port = client_port + 22000
+     * 47200 = 25200 + 22000
+     *
+     * @type int
+     */
+    protected $port_diff = 22000;
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'hostname'   => 'hostname',
+            'mapname'    => 'map',
+            'maxplayers' => 'max_players',
+            'numplayers' => 'num_players',
+            'password'   => 'password',
+        ],
+        'player'  => [
+            'name'  => 'name',
+            'score' => 'score',
+            'ping'  => 'ping',
+        ],
+        'team'    => [
+            'score' => 'tickets',
+        ],
+    ];
+
+    /**
+     * Process the response for the StarMade server
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function processResponse()
+    {
+
+        // Holds the results sent back
+        $results = [];
+
+        // Holds the processed packets after having been reassembled
+        $processed = [];
+
+        // Start up the index for the processed
+        $sequence_id_last = 0;
+
+        foreach ($this->packets_response as $packet) {
+            // Create a new buffer
+            $buffer = new Buffer($packet);
+
+            // Each "good" packet begins with sequence_id (32-bit)
+            $sequence_id = $buffer->readInt32();
+
+            // Sequence id is a response
+            if (array_key_exists($sequence_id, $this->responses)) {
+                $processed[$sequence_id] = $buffer->getBuffer();
+                $sequence_id_last = $sequence_id;
+            } else {
+                // This is a continuation of the previous packet, reset the buffer and append
+                $buffer->jumpto(0);
+
+                // Append
+                $processed[$sequence_id_last] .= $buffer->getBuffer();
+            }
+        }
+
+        unset($buffer, $sequence_id_last, $sequence_id);
+
+        // Iterate over the combined packets and do some work
+        foreach ($processed as $sequence_id => $data) {
+            // Create a new buffer
+            $buffer = new Buffer($data);
+
+            // Get the length of the packet
+            $packetLength = $buffer->getLength();
+
+            // Check to make sure the expected length matches the real length
+            // Subtract 4 for the sequence_id pulled out earlier
+            if ($packetLength != ($buffer->readInt32() - 4)) {
+                throw new Exception(__METHOD__ . " packet length does not match expected length!");
+            }
+
+            // Now we need to call the proper method
+            $results = array_merge(
+                $results,
+                call_user_func_array([$this, $this->responses[$sequence_id]], [$buffer])
+            );
+        }
+
+        return $results;
+    }
+
+    /*
+     * Internal Methods
+     */
+
+    /**
+     * Decode the buffer into a usable format
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function decode(Buffer $buffer)
+    {
+
+        $items = [];
+
+        // Get the number of words in this buffer
+        $itemCount = $buffer->readInt32();
+
+        // Loop over the number of items
+        for ($i = 0; $i < $itemCount; $i++) {
+            // Length of the string
+            $buffer->readInt32();
+
+            // Just read the string
+            $items[$i] = $buffer->readString();
+        }
+
+        return $items;
+    }
+
+    /**
+     * Process the server details
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processDetails(Buffer $buffer)
+    {
+
+        // Decode into items
+        $items = $this->decode($buffer);
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Server is always dedicated
+        $result->add('dedicated', 1);
+
+        // These are the same no matter what mode the server is in
+        $result->add('hostname', $items[1]);
+        $result->add('num_players', (int)$items[2]);
+        $result->add('max_players', (int)$items[3]);
+        $result->add('gametype', $items[4]);
+        $result->add('map', $items[5]);
+        $result->add('roundsplayed', (int)$items[6]);
+        $result->add('roundstotal', (int)$items[7]);
+        $result->add('num_teams', (int)$items[8]);
+
+        // Set the current index
+        $index_current = 9;
+
+        // Pull the team count
+        $teamCount = $result->get('num_teams');
+
+        // Loop for the number of teams found, increment along the way
+        for ($id = 1; $id <= $teamCount; $id++, $index_current++) {
+            // Shows the tickets
+            $result->addTeam('tickets', $items[$index_current]);
+            // We add an id so we know which team this is
+            $result->addTeam('id', $id);
+        }
+
+        // Get and set the rest of the data points.
+        $result->add('targetscore', (int)$items[$index_current]);
+        $result->add('online', 1); // Forced true, it seems $words[$index_current + 1] is always empty
+        $result->add('ranked', (int)$items[$index_current + 2]);
+        $result->add('punkbuster', (int)$items[$index_current + 3]);
+        $result->add('password', (int)$items[$index_current + 4]);
+        $result->add('uptime', (int)$items[$index_current + 5]);
+        $result->add('roundtime', (int)$items[$index_current + 6]);
+        // Added in R9
+        $result->add('ip_port', $items[$index_current + 7]);
+        $result->add('punkbuster_version', $items[$index_current + 8]);
+        $result->add('join_queue', (int)$items[$index_current + 9]);
+        $result->add('region', $items[$index_current + 10]);
+        $result->add('pingsite', $items[$index_current + 11]);
+        $result->add('country', $items[$index_current + 12]);
+        // Added in R29, No docs as of yet
+        $result->add('quickmatch', (int)$items[$index_current + 13]); // Guessed from research
+
+        unset($items, $index_current, $teamCount, $buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Process the server version
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processVersion(Buffer $buffer)
+    {
+
+        // Decode into items
+        $items = $this->decode($buffer);
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        $result->add('version', $items[2]);
+
+        unset($buffer, $items);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Process the players
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processPlayers(Buffer $buffer)
+    {
+
+        // Decode into items
+        $items = $this->decode($buffer);
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Number of data points per player
+        $numTags = $items[1];
+
+        // Grab the tags for each player
+        $tags = array_slice($items, 2, $numTags);
+
+        // Get the player count
+        $playerCount = $items[$numTags + 2];
+
+        // Iterate over the index until we run out of players
+        for ($i = 0, $x = $numTags + 3; $i < $playerCount; $i++, $x += $numTags) {
+            // Loop over the player tags and extract the info for that tag
+            foreach ($tags as $index => $tag) {
+                $result->addPlayer($tag, $items[($x + $index)]);
+            }
+        }
+
+        return $result->fetch();
+    }
+}

+ 114 - 0
OGP/php-query/gameq/Protocols/Bf4.php

@@ -0,0 +1,114 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Buffer;
+use GameQ\Result;
+
+/**
+ * Battlefield 4 Protocol class
+ *
+ * Good place for doc status and info is http://battlelog.battlefield.com/bf4/forum/view/2955064768683911198/
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Bf4 extends Bf3
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'bf4';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Battlefield 4";
+
+    /**
+     * Handle processing details since they are different than BF3
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processDetails(Buffer $buffer)
+    {
+
+        // Decode into items
+        $items = $this->decode($buffer);
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Server is always dedicated
+        $result->add('dedicated', 1);
+
+        // These are the same no matter what mode the server is in
+        $result->add('hostname', $items[1]);
+        $result->add('num_players', (int) $items[2]);
+        $result->add('max_players', (int) $items[3]);
+        $result->add('gametype', $items[4]);
+        $result->add('map', $items[5]);
+        $result->add('roundsplayed', (int) $items[6]);
+        $result->add('roundstotal', (int) $items[7]);
+        $result->add('num_teams', (int) $items[8]);
+
+        // Set the current index
+        $index_current = 9;
+
+        // Pull the team count
+        $teamCount = $result->get('num_teams');
+
+        // Loop for the number of teams found, increment along the way
+        for ($id = 1; $id <= $teamCount; $id++, $index_current++) {
+            // Shows the tickets
+            $result->addTeam('tickets', $items[$index_current]);
+            // We add an id so we know which team this is
+            $result->addTeam('id', $id);
+        }
+
+        // Get and set the rest of the data points.
+        $result->add('targetscore', (int) $items[$index_current]);
+        $result->add('online', 1); // Forced true, it seems $words[$index_current + 1] is always empty
+        $result->add('ranked', (int) $items[$index_current + 2]);
+        $result->add('punkbuster', (int) $items[$index_current + 3]);
+        $result->add('password', (int) $items[$index_current + 4]);
+        $result->add('uptime', (int) $items[$index_current + 5]);
+        $result->add('roundtime', (int) $items[$index_current + 6]);
+        $result->add('ip_port', $items[$index_current + 7]);
+        $result->add('punkbuster_version', $items[$index_current + 8]);
+        $result->add('join_queue', (int) $items[$index_current + 9]);
+        $result->add('region', $items[$index_current + 10]);
+        $result->add('pingsite', $items[$index_current + 11]);
+        $result->add('country', $items[$index_current + 12]);
+        //$result->add('quickmatch', (int) $items[$index_current + 13]); Supposed to be here according to R42 but is not
+        $result->add('blaze_player_count', (int) $items[$index_current + 13]);
+        $result->add('blaze_game_state', (int) $items[$index_current + 14]);
+
+        unset($items, $index_current, $teamCount, $buffer);
+
+        return $result->fetch();
+    }
+}

+ 326 - 0
OGP/php-query/gameq/Protocols/Bfbc2.php

@@ -0,0 +1,326 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+use GameQ\Exception\Protocol as Exception;
+
+/**
+ * Battlefield Bad Company 2 Protocol Class
+ *
+ * NOTE:  There are no qualifiers to the response packets sent back from the server as to which response packet
+ * belongs to which query request.  For now this class assumes the responses are in the same order as the order in
+ * which the packets were sent to the server.  If this assumption turns out to be wrong there is easy way to tell which
+ * response belongs to which query.  Hopefully this assumption will hold true as it has in my testing.
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Bfbc2 extends Protocol
+{
+
+    /**
+     * Array of packets we want to query.
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_VERSION => "\x00\x00\x00\x00\x18\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00version\x00",
+        self::PACKET_STATUS  => "\x00\x00\x00\x00\x1b\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x00\x00serverInfo\x00",
+        self::PACKET_PLAYERS => "\x00\x00\x00\x00\x24\x00\x00\x00\x02\x00\x00\x00\x0b\x00\x00\x00listPlayers\x00\x03\x00\x00\x00\x61ll\x00",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        "processVersion",
+        "processDetails",
+        "processPlayers",
+    ];
+
+    /**
+     * The transport mode for this protocol is TCP
+     *
+     * @type string
+     */
+    protected $transport = self::TRANSPORT_TCP;
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'bfbc2';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'bfbc2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Battlefield Bad Company 2";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = null;
+
+    /**
+     * query_port = client_port + 29321
+     * 48888 = 19567 + 29321
+     *
+     * @type int
+     */
+    protected $port_diff = 29321;
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'hostname'   => 'hostname',
+            'mapname'    => 'map',
+            'maxplayers' => 'max_players',
+            'numplayers' => 'num_players',
+            'password'   => 'password',
+        ],
+        'player'  => [
+            'name'  => 'name',
+            'score' => 'score',
+            'ping'  => 'ping',
+        ],
+        'team'    => [
+            'score' => 'tickets',
+        ],
+    ];
+
+    /**
+     * Process the response for the StarMade server
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function processResponse()
+    {
+
+        //print_r($this->packets_response);
+
+        // Holds the results sent back
+        $results = [];
+
+        // Iterate over the response packets
+        // @todo: This protocol has no packet ordering, ids or anyway to identify which packet coming back belongs to which initial call.
+        foreach ($this->packets_response as $i => $packet) {
+            // Create a new buffer
+            $buffer = new Buffer($packet);
+
+            // Burn first 4 bytes, same across all packets
+            $buffer->skip(4);
+
+            // Get the packet length
+            $packetLength = $buffer->getLength();
+
+            // Check to make sure the expected length matches the real length
+            // Subtract 4 for the header burn
+            if ($packetLength != ($buffer->readInt32() - 4)) {
+                throw new Exception(__METHOD__ . " packet length does not match expected length!");
+            }
+
+            // We assume the packets are coming back in the same order as sent, this maybe incorrect...
+            $results = array_merge(
+                $results,
+                call_user_func_array([$this, $this->responses[$i]], [$buffer])
+            );
+        }
+
+        unset($buffer, $packetLength);
+
+        return $results;
+    }
+
+    /*
+     * Internal Methods
+     */
+
+    /**
+     * Decode the buffer into a usable format
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function decode(Buffer $buffer)
+    {
+
+        $items = [];
+
+        // Get the number of words in this buffer
+        $itemCount = $buffer->readInt32();
+
+        // Loop over the number of items
+        for ($i = 0; $i < $itemCount; $i++) {
+            // Length of the string
+            $buffer->readInt32();
+
+            // Just read the string
+            $items[$i] = $buffer->readString();
+        }
+
+        return $items;
+    }
+
+    /**
+     * Process the server details
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processDetails(Buffer $buffer)
+    {
+
+        // Decode into items
+        $items = $this->decode($buffer);
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Server is always dedicated
+        $result->add('dedicated', 1);
+
+        // These are the same no matter what mode the server is in
+        $result->add('hostname', $items[1]);
+        $result->add('num_players', (int)$items[2]);
+        $result->add('max_players', (int)$items[3]);
+        $result->add('gametype', $items[4]);
+        $result->add('map', $items[5]);
+        $result->add('roundsplayed', (int)$items[6]);
+        $result->add('roundstotal', (int)$items[7]);
+        $result->add('num_teams', (int)$items[8]);
+
+        // Set the current index
+        $index_current = 9;
+
+        // Pull the team count
+        $teamCount = $result->get('num_teams');
+
+        // Loop for the number of teams found, increment along the way
+        for ($id = 1; $id <= $teamCount; $id++, $index_current++) {
+            // Shows the tickets
+            $result->addTeam('tickets', $items[$index_current]);
+            // We add an id so we know which team this is
+            $result->addTeam('id', $id);
+        }
+
+        // Get and set the rest of the data points.
+        $result->add('targetscore', (int)$items[$index_current]);
+        $result->add('online', 1); // Forced true, shows accepting players
+        $result->add('ranked', (($items[$index_current + 2] == 'true') ? 1 : 0));
+        $result->add('punkbuster', (($items[$index_current + 3] == 'true') ? 1 : 0));
+        $result->add('password', (($items[$index_current + 4] == 'true') ? 1 : 0));
+        $result->add('uptime', (int)$items[$index_current + 5]);
+        $result->add('roundtime', (int)$items[$index_current + 6]);
+        $result->add('mod', $items[$index_current + 7]);
+
+        $result->add('ip_port', $items[$index_current + 9]);
+        $result->add('punkbuster_version', $items[$index_current + 10]);
+        $result->add('join_queue', (($items[$index_current + 11] == 'true') ? 1 : 0));
+        $result->add('region', $items[$index_current + 12]);
+
+        unset($items, $index_current, $teamCount, $buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Process the server version
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processVersion(Buffer $buffer)
+    {
+        // Decode into items
+        $items = $this->decode($buffer);
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        $result->add('version', $items[2]);
+
+        unset($buffer, $items);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Process the players
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processPlayers(Buffer $buffer)
+    {
+
+        // Decode into items
+        $items = $this->decode($buffer);
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Number of data points per player
+        $numTags = $items[1];
+
+        // Grab the tags for each player
+        $tags = array_slice($items, 2, $numTags);
+
+        // Get the player count
+        $playerCount = $items[$numTags + 2];
+
+        // Iterate over the index until we run out of players
+        for ($i = 0, $x = $numTags + 3; $i < $playerCount; $i++, $x += $numTags) {
+            // Loop over the player tags and extract the info for that tag
+            foreach ($tags as $index => $tag) {
+                $result->addPlayer($tag, $items[($x + $index)]);
+            }
+        }
+
+        return $result->fetch();
+    }
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Bfh.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Battlefield Hardline Protocol class
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Bfh extends Bf4
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'bfh';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Battlefield Hardline";
+}

+ 50 - 0
OGP/php-query/gameq/Protocols/Brink.php

@@ -0,0 +1,50 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Brink
+ *
+ * @package GameQ\Protocols
+ *
+ * @author Wilson Jesus <>
+ */
+class Brink extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'brink';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Brink";
+
+    /**
+     * query_port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Cod.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Call of Duty Protocol Class
+ *
+ * @package GameQ\Protocols
+ *
+ * @author  Wilson Jesus <>
+ */
+class Cod extends Quake3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'cod';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Call of Duty";
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Cod2.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Call of Duty 2 Protocol Class
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Cod2 extends Quake3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'cod2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Call of Duty 2";
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Cod4.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Call of Duty 4 Protocol Class
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Cod4 extends Quake3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'cod4';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Call of Duty 4";
+}

+ 50 - 0
OGP/php-query/gameq/Protocols/Codmw3.php

@@ -0,0 +1,50 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Codmw3
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Codmw3 extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'codmw3';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Call of Duty: Modern Warfare 3";
+
+    /**
+     * query_port = client_port + 2
+     *
+     * @type int
+     */
+    protected $port_diff = 2;
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Coduo.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Call of Duty United Offensive Class
+ *
+ * @package GameQ\Protocols
+ *
+ * @author  Wilson Jesus <>
+ */
+class Coduo extends Quake3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'coduo';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Call of Duty: United Offensive";
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Codwaw.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Call of Duty World at War Class
+ *
+ * @package GameQ\Protocols
+ * @author  naXe <[email protected]>
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Codwaw extends Quake3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'codwaw';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Call of Duty: World at War";
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Conanexiles.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Conanexiles
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Conanexiles extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'conanexiles';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Conan Exiles";
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Contagion.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Contagion
+ *
+ * @package GameQ\Protocols
+ * @author  Nikolay Ipanyuk <[email protected]>
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Contagion extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'contagion';
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Contagion";
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Crysis.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Crysis
+ *
+ * @package GameQ\Protocols
+ *
+ * @author  Wilson Jesus <>
+ */
+class Crysis extends Gamespy3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'crysis';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Crysis";
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Crysis2.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Crysis2
+ *
+ * @package GameQ\Protocols
+ *
+ * @author  Wilson Jesus <>
+ */
+class Crysis2 extends Gamespy3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'crysis2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Crysis 2";
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Crysiswars.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Crysiswars
+ *
+ * @package GameQ\Protocols
+ *
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Crysiswars extends Gamespy3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'crysiswars';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Crysis Wars";
+}

+ 45 - 0
OGP/php-query/gameq/Protocols/Cs15.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Counter-Strike 1.5 Protocol Class
+ *
+ * @author  Nikolay Ipanyuk <[email protected]>
+ * @author  Austin Bischoff <[email protected]>
+ *
+ * @package GameQ\Protocols
+ */
+class Cs15 extends Won
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'cs15';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Counter-Strike 1.5";
+}

+ 69 - 0
OGP/php-query/gameq/Protocols/Cs16.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Cs16
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Cs16 extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'cs16';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Counter-Strike 1.6";
+
+    /**
+     * In the case of cs 1.6 we offload split packets here because the split packet response for rules is in
+     * the old gold source format
+     *
+     * @param       $packet_id
+     * @param array $packets
+     *
+     * @return string
+     * @throws \GameQ\Exception\Protocol
+     */
+    protected function processPackets($packet_id, array $packets = [])
+    {
+
+        // The response is gold source if the packets are split
+        $this->source_engine = self::GOLDSOURCE_ENGINE;
+
+        // Offload to the parent
+        $packs = parent::processPackets($packet_id, $packets);
+
+        // Reset the engine
+        $this->source_engine = self::SOURCE_ENGINE;
+
+        // Return the result
+        return $packs;
+    }
+}

+ 263 - 0
OGP/php-query/gameq/Protocols/Cs2d.php

@@ -0,0 +1,263 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+use GameQ\Exception\Protocol as Exception;
+
+/**
+ * Counter-Strike 2d Protocol Class
+ *
+ * Note:
+ * Unable to make player information calls work as the protocol does not like parallel requests
+ *
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Cs2d extends Protocol
+{
+
+    /**
+     * Array of packets we want to query.
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_STATUS  => "\x01\x00\xFB\x01",
+        //self::PACKET_STATUS => "\x01\x00\x03\x10\x21\xFB\x01\x75\x00",
+        self::PACKET_PLAYERS => "\x01\x00\xFB\x05",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        "\x01\x00\xFB\x01" => "processDetails",
+        "\x01\x00\xFB\x05" => "processPlayers",
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'cs2d';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'cs2d';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Counter-Strike 2d";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = "cs2d://%s:%d/";
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'gametype'   => 'game_mode',
+            'hostname'   => 'hostname',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'max_players',
+            'mod'        => 'game_dir',
+            'numplayers' => 'num_players',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name'   => 'name',
+            'deaths' => 'deaths',
+            'score'  => 'score',
+        ],
+    ];
+
+    /**
+     * Process the response for the Tibia server
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function processResponse()
+    {
+
+        // We have a merged packet, try to split it back up
+        if (count($this->packets_response) == 1) {
+            // Temp buffer to make string manipulation easier
+            $buffer = new Buffer($this->packets_response[0]);
+
+            // Grab the header and set the packet we need to split with
+            $packet = (($buffer->lookAhead(4) === $this->packets[self::PACKET_PLAYERS]) ?
+                self::PACKET_STATUS : self::PACKET_PLAYERS);
+
+            // Explode the merged packet as the response
+            $responses = explode(substr($this->packets[$packet], 2), $buffer->getData());
+
+            // Try to rebuild the second packet to the same as if it was sent as two separate responses
+            $responses[1] = $this->packets[$packet] . ((count($responses) === 2) ? $responses[1] : "");
+
+            unset($buffer);
+        } else {
+            $responses = $this->packets_response;
+        }
+
+        // Will hold the packets after sorting
+        $packets = [];
+
+        // We need to pre-sort these for split packets so we can do extra work where needed
+        foreach ($responses as $response) {
+            $buffer = new Buffer($response);
+
+            // Pull out the header
+            $header = $buffer->read(4);
+
+            // Add the packet to the proper section, we will combine later
+            $packets[$header][] = $buffer->getBuffer();
+        }
+
+        unset($buffer);
+
+        $results = [];
+
+        // Now let's iterate and process
+        foreach ($packets as $header => $packetGroup) {
+            // Figure out which packet response this is
+            if (!array_key_exists($header, $this->responses)) {
+                throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
+            }
+
+            // Now we need to call the proper method
+            $results = array_merge(
+                $results,
+                call_user_func_array([$this, $this->responses[$header]], [new Buffer(implode($packetGroup))])
+            );
+        }
+
+        unset($packets);
+
+        return $results;
+    }
+
+    /**
+     * Handles processing the details data into a usable format
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     * @throws Exception
+     */
+    protected function processDetails(Buffer $buffer)
+    {
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // First int is the server flags
+        $serverFlags = $buffer->readInt8();
+
+        // Read server flags
+        $result->add('password', (int)$this->readFlag($serverFlags, 0));
+        $result->add('registered_only', (int)$this->readFlag($serverFlags, 1));
+        $result->add('fog_of_war', (int)$this->readFlag($serverFlags, 2));
+        $result->add('friendly_fire', (int)$this->readFlag($serverFlags, 3));
+        $result->add('bots_enabled', (int)$this->readFlag($serverFlags, 5));
+        $result->add('lua_scripts', (int)$this->readFlag($serverFlags, 6));
+
+        // Read the rest of the buffer data
+        $result->add('servername', utf8_encode($buffer->readPascalString(0)));
+        $result->add('mapname', utf8_encode($buffer->readPascalString(0)));
+        $result->add('num_players', $buffer->readInt8());
+        $result->add('max_players', $buffer->readInt8());
+        $result->add('game_mode', $buffer->readInt8());
+        $result->add('num_bots', (($this->readFlag($serverFlags, 5)) ? $buffer->readInt8() : 0));
+        $result->add('dedicated', 1);
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Handles processing the player data into a usable format
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     * @throws Exception
+     */
+    protected function processPlayers(Buffer $buffer)
+    {
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // First entry is the number of players in this list.  Don't care
+        $buffer->read();
+
+        // Parse players
+        while ($buffer->getLength()) {
+            // Player id
+            if (($id = $buffer->readInt8()) !== 0) {
+                // Add the results
+                $result->addPlayer('id', $id);
+                $result->addPlayer('name', utf8_encode($buffer->readPascalString(0)));
+                $result->addPlayer('team', $buffer->readInt8());
+                $result->addPlayer('score', $buffer->readInt32());
+                $result->addPlayer('deaths', $buffer->readInt32());
+            }
+        }
+
+        unset($buffer, $id);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Read flags from stored value
+     *
+     * @param $flags
+     * @param $offset
+     *
+     * @return bool
+     */
+    protected function readFlag($flags, $offset)
+    {
+        return !!($flags & (1 << $offset));
+    }
+}

+ 45 - 0
OGP/php-query/gameq/Protocols/Cscz.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Cscz
+ *
+ * Based off of CS 1.6
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Cscz extends Cs16
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'cscz';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Counter-Strike: Condition Zero";
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Csgo.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Csgo
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Csgo extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'csgo';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Counter-Strike: Global Offensive";
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Css.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Css
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Css extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'css';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Counter-Strike: Source";
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Dal.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Dark and Light
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Dal extends Arkse
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'dal';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Dark and Light";
+}

+ 66 - 0
OGP/php-query/gameq/Protocols/Dayz.php

@@ -0,0 +1,66 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Dayz
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Dayz extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'dayz';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "DayZ Standalone";
+
+    /**
+     * Overload the math used to guess at the Query Port
+     *
+     * @param int $clientPort
+     *
+     * @return int
+     */
+    public function findQueryPort($clientPort)
+    {
+
+        /*
+         * Port layout:
+         * 2302 - 27016
+         * 2402 - 27017
+         * 2502 - 27018
+         * 2602 - 27019
+         * 2702 - 27020
+         * ...
+         */
+
+        return 27016 + (($clientPort - 2302) / 100);
+    }
+}

+ 44 - 0
OGP/php-query/gameq/Protocols/Dayzmod.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Dayzmod
+ *
+ * @package GameQ\Protocols
+ * @author  Marcel Bößendörfer <[email protected]>
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Dayzmod extends Armedassault2oa
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'dayzmod';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "DayZ Mod";
+}

+ 45 - 0
OGP/php-query/gameq/Protocols/Dod.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Dod
+ *
+ * Based off of CS 1.6
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Dod extends Cs16
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'dod';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Day of Defeat";
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Dods.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Dods
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Dods extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'dods';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Day of Defeat: Source";
+}

+ 69 - 0
OGP/php-query/gameq/Protocols/Dow.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Buffer;
+
+/**
+ * Class Dow
+ *
+ * Apparently the player response is incomplete as there is no information being returned for that packet
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Dow extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'dow';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Days of War";
+
+    /**
+     * Normalize main fields
+     *
+     * @var array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'gametype'   => 'G_s',
+            'hostname'   => 'ONM_s',
+            'mapname'    => 'MPN_s',
+            'maxplayers' => 'P_i',
+            'numplayers' => 'N_i',
+        ],
+        // Individual
+        'player'  => [
+            'name'  => 'name',
+            'score' => 'score',
+            'time'  => 'time',
+        ],
+    ];
+}

+ 123 - 0
OGP/php-query/gameq/Protocols/Eco.php

@@ -0,0 +1,123 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Exception\Protocol as Exception;
+use GameQ\Result;
+
+/**
+ * ECO Global Survival Protocol Class
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Eco extends Http
+{
+    /**
+     * Packets to send
+     *
+     * @var array
+     */
+    protected $packets = [
+        self::PACKET_STATUS => "GET /frontpage HTTP/1.0\r\nAccept: */*\r\n\r\n",
+    ];
+
+    /**
+     * Http protocol is SSL
+     *
+     * @var string
+     */
+    protected $transport = self::TRANSPORT_TCP;
+
+    /**
+     * The protocol being used
+     *
+     * @var string
+     */
+    protected $protocol = 'eco';
+
+    /**
+     * String name of this protocol class
+     *
+     * @var string
+     */
+    protected $name = 'eco';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @var string
+     */
+    protected $name_long = "ECO Global Survival";
+
+    /**
+     * query_port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+
+    /**
+     * Normalize some items
+     *
+     * @var array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'hostname'   => 'description',
+            'maxplayers' => 'totalplayers',
+            'numplayers' => 'onlineplayers',
+            'password'   => 'haspassword',
+        ],
+    ];
+
+    /**
+     * Process the response
+     *
+     * @return array
+     * @throws Exception
+     */
+    public function processResponse()
+    {
+        if (empty($this->packets_response)) {
+            return [];
+        }
+
+        // Implode and rip out the JSON
+        preg_match('/\{(.*)\}/ms', implode('', $this->packets_response), $matches);
+
+        // Return should be JSON, let's validate
+        if (!isset($matches[0]) || ($json = json_decode($matches[0])) === null) {
+            throw new Exception("JSON response from Eco server is invalid.");
+        }
+
+        $result = new Result();
+
+        // Server is always dedicated
+        $result->add('dedicated', 1);
+
+        foreach ($json->Info as $info => $setting) {
+            $result->add(strtolower($info), $setting);
+        }
+
+        return $result->fetch();
+    }
+}

+ 51 - 0
OGP/php-query/gameq/Protocols/Egs.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Empyrion - Galactic Survival
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ * @author  TacTicToe66 <https://github.com/TacTicToe66>
+ */
+class EgS extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'egs';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Empyrion - Galactic Survival";
+
+    /**
+     * query_port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Et.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Wolfenstein Enemy Territory Protocol Class
+ *
+ * @package GameQ\Protocols
+ *
+ * @author  Wilson Jesus <>
+ */
+class Et extends Quake3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'et';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Wolfenstein Enemy Territory";
+}

+ 234 - 0
OGP/php-query/gameq/Protocols/Etqw.php

@@ -0,0 +1,234 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Buffer;
+use GameQ\Exception\Protocol as Exception;
+use GameQ\Protocol;
+use GameQ\Result;
+
+/**
+ * Enemy Territory Quake Wars Protocol Class
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Etqw extends Protocol
+{
+
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_STATUS => "\xFF\xFFgetInfoEx\x00\x00\x00\x00",
+        //self::PACKET_STATUS => "\xFF\xFFgetInfo\x00\x00\x00\x00\x00",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        "\xFF\xFFinfoExResponse" => "processStatus",
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'etqw';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'etqw';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Enemy Territory Quake Wars";
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'gametype'   => 'campaign',
+            'hostname'   => 'name',
+            'mapname'    => 'map',
+            'maxplayers' => 'maxPlayers',
+            'mod'        => 'gamename',
+            'numplayers' => 'numplayers',
+            'password'   => 'privateClients',
+        ],
+        // Individual
+        'player'  => [
+            'name'  => 'name',
+            'score' => 'score',
+            'time'  => 'time',
+        ],
+    ];
+
+    /**
+     * Process the response
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function processResponse()
+    {
+        // In case it comes back as multiple packets (it shouldn't)
+        $buffer = new Buffer(implode('', $this->packets_response));
+
+        // Figure out what packet response this is for
+        $response_type = $buffer->readString();
+
+        // Figure out which packet response this is
+        if (!array_key_exists($response_type, $this->responses)) {
+            throw new Exception(__METHOD__ . " response type '{$response_type}' is not valid");
+        }
+
+        // Offload the call
+        $results = call_user_func_array([$this, $this->responses[$response_type]], [$buffer]);
+
+        return $results;
+    }
+
+    /*
+     * Internal methods
+     */
+
+    /**
+     * Handle processing the status response
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processStatus(Buffer $buffer)
+    {
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Defaults
+        $result->add('dedicated', 1);
+
+        // Now burn the challenge, version and size
+        $buffer->skip(16);
+
+        // Key / value pairs
+        while ($buffer->getLength()) {
+            $var = str_replace('si_', '', $buffer->readString());
+            $val = $buffer->readString();
+            if (empty($var) && empty($val)) {
+                break;
+            }
+            // Add the server prop
+            $result->add($var, $val);
+        }
+        // Now let's do the basic player info
+        $this->parsePlayers($buffer, $result);
+
+        // Now grab the rest of the server info
+        $result->add('osmask', $buffer->readInt32());
+        $result->add('ranked', $buffer->readInt8());
+        $result->add('timeleft', $buffer->readInt32());
+        $result->add('gamestate', $buffer->readInt8());
+        $result->add('servertype', $buffer->readInt8());
+
+        // 0: regular server
+        if ($result->get('servertype') == 0) {
+            $result->add('interested_clients', $buffer->readInt8());
+        } else {
+            // 1: tv server
+            $result->add('connected_clients', $buffer->readInt32());
+            $result->add('max_clients', $buffer->readInt32());
+        }
+
+        // Now let's parse the extended player info
+        $this->parsePlayersExtra($buffer, $result);
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Parse players out of the status ex response
+     *
+     * @param Buffer $buffer
+     * @param Result $result
+     */
+    protected function parsePlayers(Buffer &$buffer, Result &$result)
+    {
+        // By default there are 0 players
+        $players = 0;
+
+        // Iterate over the players until we run out
+        while (($id = $buffer->readInt8()) != 32) {
+            $result->addPlayer('id', $id);
+            $result->addPlayer('ping', $buffer->readInt16());
+            $result->addPlayer('name', $buffer->readString());
+            $result->addPlayer('clantag_pos', $buffer->readInt8());
+            $result->addPlayer('clantag', $buffer->readString());
+            $result->addPlayer('bot', $buffer->readInt8());
+            $players++;
+        }
+
+        // Let's add in the current players as a result
+        $result->add('numplayers', $players);
+
+        // Free some memory
+        unset($id);
+    }
+
+    /**
+     * Handle parsing extra player data
+     *
+     * @param Buffer $buffer
+     * @param Result $result
+     */
+    protected function parsePlayersExtra(Buffer &$buffer, Result &$result)
+    {
+        // Iterate over the extra player info
+        while (($id = $buffer->readInt8()) != 32) {
+            $result->addPlayer('total_xp', $buffer->readFloat32());
+            $result->addPlayer('teamname', $buffer->readString());
+            $result->addPlayer('total_kills', $buffer->readInt32());
+            $result->addPlayer('total_deaths', $buffer->readInt32());
+        }
+
+        // @todo: Add team stuff
+
+        // Free some memory
+        unset($id);
+    }
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Ffe.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Ffe - Fortress Forever
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Ffe extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'ffe';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Fortress Forever";
+}

+ 243 - 0
OGP/php-query/gameq/Protocols/Ffow.php

@@ -0,0 +1,243 @@
+<?php
+
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+use GameQ\Exception\Protocol as Exception;
+
+/**
+ * Frontlines Fuel of War Protocol Class
+ *
+ * Handles processing ffow servers
+ *
+ * Class is incomplete due to lack of players to test against.
+ * http://wiki.hlsw.net/index.php/FFOW_Protocol
+ *
+ * @package GameQ\Protocols
+ */
+class Ffow extends Protocol
+{
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_CHALLENGE => "\xFF\xFF\xFF\xFF\x57",
+        self::PACKET_RULES     => "\xFF\xFF\xFF\xFF\x56%s",
+        self::PACKET_PLAYERS   => "\xFF\xFF\xFF\xFF\x55%s",
+        self::PACKET_INFO      => "\xFF\xFF\xFF\xFF\x46\x4C\x53\x51",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        "\xFF\xFF\xFF\xFF\x49\x02" => 'processInfo', // I
+        "\xFF\xFF\xFF\xFF\x45\x00" => 'processRules', // E
+        "\xFF\xFF\xFF\xFF\x44\x00" => 'processPlayers', // D
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'ffow';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'ffow';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Frontlines Fuel of War";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = null;
+
+    /**
+     * query_port = client_port + 2
+     *
+     * @type int
+     */
+    protected $port_diff = 2;
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'gametype'   => 'gamemode',
+            'hostname'   => 'servername',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'max_players',
+            'mod'        => 'modname',
+            'numplayers' => 'num_players',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name'  => 'name',
+            'ping'  => 'ping',
+            'score' => 'frags',
+        ],
+    ];
+
+    /**
+     * Parse the challenge response and apply it to all the packet types
+     *
+     * @param \GameQ\Buffer $challenge_buffer
+     *
+     * @return bool
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function challengeParseAndApply(Buffer $challenge_buffer)
+    {
+        // Burn padding
+        $challenge_buffer->skip(5);
+
+        // Apply the challenge and return
+        return $this->challengeApply($challenge_buffer->read(4));
+    }
+
+    /**
+     * Handle response from the server
+     *
+     * @return mixed
+     * @throws Exception
+     */
+    public function processResponse()
+    {
+        // Init results
+        $results = [];
+
+        foreach ($this->packets_response as $response) {
+            $buffer = new Buffer($response);
+
+            // Figure out what packet response this is for
+            $response_type = $buffer->read(6);
+
+            // Figure out which packet response this is
+            if (!array_key_exists($response_type, $this->responses)) {
+                throw new Exception(__METHOD__ . " response type '" . bin2hex($response_type) . "' is not valid");
+            }
+
+            // Now we need to call the proper method
+            $results = array_merge(
+                $results,
+                call_user_func_array([$this, $this->responses[$response_type]], [$buffer])
+            );
+
+            unset($buffer);
+        }
+
+        return $results;
+    }
+
+    /**
+     * Handle processing the server information
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processInfo(Buffer $buffer)
+    {
+        // Set the result to a new result instance
+        $result = new Result();
+
+        $result->add('servername', $buffer->readString());
+        $result->add('mapname', $buffer->readString());
+        $result->add('modname', $buffer->readString());
+        $result->add('gamemode', $buffer->readString());
+        $result->add('description', $buffer->readString());
+        $result->add('version', $buffer->readString());
+        $result->add('port', $buffer->readInt16());
+        $result->add('num_players', $buffer->readInt8());
+        $result->add('max_players', $buffer->readInt8());
+        $result->add('dedicated', $buffer->readInt8());
+        $result->add('os', $buffer->readInt8());
+        $result->add('password', $buffer->readInt8());
+        $result->add('anticheat', $buffer->readInt8());
+        $result->add('average_fps', $buffer->readInt8());
+        $result->add('round', $buffer->readInt8());
+        $result->add('max_rounds', $buffer->readInt8());
+        $result->add('time_left', $buffer->readInt16());
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Handle processing the server rules
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processRules(Buffer $buffer)
+    {
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Burn extra header
+        $buffer->skip(1);
+
+        // Read rules until we run out of buffer
+        while ($buffer->getLength()) {
+            $key = $buffer->readString();
+            // Check for map
+            if (strstr($key, "Map:")) {
+                $result->addSub("maplist", "name", $buffer->readString());
+            } else // Regular rule
+            {
+                $result->add($key, $buffer->readString());
+            }
+        }
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Handle processing of player data
+     *
+     * @todo: Build this out when there is a server with players to test against
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processPlayers(Buffer $buffer)
+    {
+        // Set the result to a new result instance
+        $result = new Result();
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+}

+ 181 - 0
OGP/php-query/gameq/Protocols/Gamespy.php

@@ -0,0 +1,181 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+use \GameQ\Exception\Protocol as Exception;
+
+/**
+ * GameSpy Protocol class
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Gamespy extends Protocol
+{
+
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_STATUS => "\x5C\x73\x74\x61\x74\x75\x73\x5C",
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'gamespy';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'gamespy';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "GameSpy Server";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = null;
+
+    /**
+     * Process the response for this protocol
+     *
+     * @return array
+     * @throws Exception
+     */
+    public function processResponse()
+    {
+        // Holds the processed packets so we can sort them in case they come in an unordered
+        $processed = [];
+
+        // Iterate over the packets
+        foreach ($this->packets_response as $response) {
+            // Check to see if we had a preg_match error
+            if (($match = preg_match("#^(.*)\\\\queryid\\\\([^\\\\]+)(\\\\|$)#", $response, $matches)) === false
+                || $match != 1
+            ) {
+                throw new Exception(__METHOD__ . " An error occurred while parsing the packets for 'queryid'");
+            }
+
+            // Multiply so we move the decimal point out of the way, if there is one
+            $key = (int)(floatval($matches[2]) * 1000);
+
+            // Add this packet to the processed
+            $processed[$key] = $matches[1];
+        }
+
+        // Sort the new array to make sure the keys (query ids) are in the proper order
+        ksort($processed, SORT_NUMERIC);
+
+        // Create buffer and offload processing
+        return $this->processStatus(new Buffer(implode('', $processed)));
+    }
+
+    /*
+     * Internal methods
+     */
+
+    /**
+     * Handle processing the status buffer
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processStatus(Buffer $buffer)
+    {
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // By default dedicted
+        $result->add('dedicated', 1);
+
+        // Lets peek and see if the data starts with a \
+        if ($buffer->lookAhead(1) == '\\') {
+            // Burn the first one
+            $buffer->skip(1);
+        }
+
+        // Explode the data
+        $data = explode('\\', $buffer->getBuffer());
+
+        // No longer needed
+        unset($buffer);
+
+        // Init some vars
+        $numPlayers = 0;
+        $numTeams = 0;
+
+        $itemCount = count($data);
+
+        // Check to make sure we have more than 1 item in the array before trying to loop
+        if (count($data) > 1) {
+            // Now lets loop the array since we have items
+            for ($x = 0; $x < $itemCount; $x += 2) {
+                // Set some local vars
+                $key = $data[$x];
+                $val = $data[$x + 1];
+
+                // Check for <variable>_<count> variable (i.e players)
+                if (($suffix = strrpos($key, '_')) !== false && is_numeric(substr($key, $suffix + 1))) {
+                    // See if this is a team designation
+                    if (substr($key, 0, $suffix) == 'teamname') {
+                        $result->addTeam('teamname', $val);
+                        $numTeams++;
+                    } else {
+                        // Its a player
+                        if (substr($key, 0, $suffix) == 'playername') {
+                            $numPlayers++;
+                        }
+                        $result->addPlayer(substr($key, 0, $suffix), utf8_encode($val));
+                    }
+                } else {
+                    // Regular variable so just add the value.
+                    $result->add($key, $val);
+                }
+            }
+        }
+
+        // Add the player and team count
+        $result->add('num_players', $numPlayers);
+        $result->add('num_teams', $numTeams);
+
+        // Unset some stuff to free up memory
+        unset($data, $key, $val, $suffix, $x, $itemCount);
+
+        // Return the result
+        return $result->fetch();
+    }
+}

+ 269 - 0
OGP/php-query/gameq/Protocols/Gamespy2.php

@@ -0,0 +1,269 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Exception\Protocol as Exception;
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+
+/**
+ * GameSpy2 Protocol class
+ *
+ * Given the ability for non utf-8 characters to be used as hostnames, player names, etc... this
+ * version returns all strings utf-8 encoded (utf8_encode).  To access the proper version of a
+ * string response you must use utf8_decode() on the specific response.
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Gamespy2 extends Protocol
+{
+
+    /**
+     * Define the state of this class
+     *
+     * @type int
+     */
+    protected $state = self::STATE_BETA;
+
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_DETAILS => "\xFE\xFD\x00\x43\x4F\x52\x59\xFF\x00\x00",
+        self::PACKET_PLAYERS => "\xFE\xFD\x00\x43\x4F\x52\x58\x00\xFF\xFF",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        "\x00\x43\x4F\x52\x59" => "processDetails",
+        "\x00\x43\x4F\x52\x58" => "processPlayers",
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'gamespy2';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'gamespy2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "GameSpy2 Server";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = null;
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'gametype'   => 'gametype',
+            'hostname'   => 'hostname',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'maxplayers',
+            'mod'        => 'mod',
+            'numplayers' => 'numplayers',
+            'password'   => 'password',
+        ],
+    ];
+
+
+    /**
+     * Process the response
+     *
+     * @return array
+     * @throws Exception
+     */
+    public function processResponse()
+    {
+
+        // Will hold the packets after sorting
+        $packets = [];
+
+        // We need to pre-sort these for split packets so we can do extra work where needed
+        foreach ($this->packets_response as $response) {
+            $buffer = new Buffer($response);
+
+            // Pull out the header
+            $header = $buffer->read(5);
+
+            // Add the packet to the proper section, we will combine later
+            $packets[$header][] = $buffer->getBuffer();
+        }
+
+        unset($buffer);
+
+        $results = [];
+
+        // Now let's iterate and process
+        foreach ($packets as $header => $packetGroup) {
+            // Figure out which packet response this is
+            if (!array_key_exists($header, $this->responses)) {
+                throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
+            }
+
+            // Now we need to call the proper method
+            $results = array_merge(
+                $results,
+                call_user_func_array([$this, $this->responses[$header]], [new Buffer(implode($packetGroup))])
+            );
+        }
+
+        unset($packets);
+
+        return $results;
+    }
+
+    /*
+     * Internal methods
+     */
+
+    /**
+     * Handles processing the details data into a usable format
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     * @throws Exception
+     */
+    protected function processDetails(Buffer $buffer)
+    {
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // We go until we hit an empty key
+        while ($buffer->getLength()) {
+            $key = $buffer->readString();
+            if (strlen($key) == 0) {
+                break;
+            }
+            $result->add($key, utf8_encode($buffer->readString()));
+        }
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Handles processing the players data into a usable format
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     * @throws Exception
+     */
+    protected function processPlayers(Buffer $buffer)
+    {
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Skip the header
+        $buffer->skip(1);
+
+        // Players are first
+        $this->parsePlayerTeam('players', $buffer, $result);
+
+        // Teams are next
+        $this->parsePlayerTeam('teams', $buffer, $result);
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Parse the player/team info returned from the player call
+     *
+     * @param string        $dataType
+     * @param \GameQ\Buffer $buffer
+     * @param \GameQ\Result $result
+     *
+     * @throws Exception
+     */
+    protected function parsePlayerTeam($dataType, Buffer &$buffer, Result &$result)
+    {
+
+        // Do count
+        $result->add('num_' . $dataType, $buffer->readInt8());
+
+        // Variable names
+        $varNames = [];
+
+        // Loop until we run out of length
+        while ($buffer->getLength()) {
+            $varNames[] = str_replace('_', '', $buffer->readString());
+
+            if ($buffer->lookAhead() === "\x00") {
+                $buffer->skip();
+                break;
+            }
+        }
+
+        // Check if there are any value entries
+        if ($buffer->lookAhead() == "\x00") {
+            $buffer->skip();
+
+            return;
+        }
+
+        // Get the values
+        while ($buffer->getLength() > 4) {
+            foreach ($varNames as $varName) {
+                $result->addSub($dataType, utf8_encode($varName), utf8_encode($buffer->readString()));
+            }
+            if ($buffer->lookAhead() === "\x00") {
+                $buffer->skip();
+                break;
+            }
+        }
+
+        return;
+    }
+}

+ 340 - 0
OGP/php-query/gameq/Protocols/Gamespy3.php

@@ -0,0 +1,340 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+
+/**
+ * GameSpy3 Protocol class
+ *
+ * Given the ability for non utf-8 characters to be used as hostnames, player names, etc... this
+ * version returns all strings utf-8 encoded (utf8_encode).  To access the proper version of a
+ * string response you must use utf8_decode() on the specific response.
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Gamespy3 extends Protocol
+{
+
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_CHALLENGE => "\xFE\xFD\x09\x10\x20\x30\x40",
+        self::PACKET_ALL       => "\xFE\xFD\x00\x10\x20\x30\x40%s\xFF\xFF\xFF\x01",
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'gamespy3';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'gamespy3';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "GameSpy3 Server";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = null;
+
+    /**
+     * This defines the split between the server info and player/team info.
+     * This value can vary by game. This value is the default split.
+     *
+     * @var string
+     */
+    protected $packetSplit = "/\\x00\\x00\\x01/m";
+
+    /**
+     * Parse the challenge response and apply it to all the packet types
+     *
+     * @param \GameQ\Buffer $challenge_buffer
+     *
+     * @return bool
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function challengeParseAndApply(Buffer $challenge_buffer)
+    {
+        // Pull out the challenge
+        $challenge = substr(preg_replace("/[^0-9\-]/si", "", $challenge_buffer->getBuffer()), 1);
+
+        // By default, no challenge result (see #197)
+        $challenge_result = '';
+
+        // Check for valid challenge (see #197)
+        if ($challenge) {
+            // Encode chellenge result
+            $challenge_result = sprintf(
+                "%c%c%c%c",
+                ($challenge >> 24),
+                ($challenge >> 16),
+                ($challenge >> 8),
+                ($challenge >> 0)
+            );
+        }
+
+        // Apply the challenge and return
+        return $this->challengeApply($challenge_result);
+    }
+
+    /**
+     * Process the response
+     *
+     * @return array
+     */
+    public function processResponse()
+    {
+
+        // Holds the processed packets
+        $processed = [];
+
+        // Iterate over the packets
+        foreach ($this->packets_response as $response) {
+            // Make a buffer
+            $buffer = new Buffer($response, Buffer::NUMBER_TYPE_BIGENDIAN);
+
+            // Packet type = 0
+            $buffer->readInt8();
+
+            // Session Id
+            $buffer->readInt32();
+
+            // We need to burn the splitnum\0 because it is not used
+            $buffer->skip(9);
+
+            // Get the id
+            $id = $buffer->readInt8();
+
+            // Burn next byte not sure what it is used for
+            $buffer->skip(1);
+
+            // Add this packet to the processed
+            $processed[$id] = $buffer->getBuffer();
+
+            unset($buffer, $id);
+        }
+
+        // Sort packets, reset index
+        ksort($processed);
+
+        // Offload cleaning up the packets if they happen to be split
+        $packets = $this->cleanPackets(array_values($processed));
+
+        // Split the packets by type general and the rest (i.e. players & teams)
+        $split = preg_split($this->packetSplit, implode('', $packets));
+
+        // Create a new result
+        $result = new Result();
+
+        // Assign variable due to pass by reference in PHP 7+
+        $buffer = new Buffer($split[0], Buffer::NUMBER_TYPE_BIGENDIAN);
+
+        // First key should be server details and rules
+        $this->processDetails($buffer, $result);
+
+        // The rest should be the player and team information, if it exists
+        if (array_key_exists(1, $split)) {
+            $buffer = new Buffer($split[1], Buffer::NUMBER_TYPE_BIGENDIAN);
+            $this->processPlayersAndTeams($buffer, $result);
+        }
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /*
+     * Internal methods
+     */
+
+    /**
+     * Handles cleaning up packets since the responses can be a bit "dirty"
+     *
+     * @param array $packets
+     *
+     * @return array
+     */
+    protected function cleanPackets(array $packets = [])
+    {
+
+        // Get the number of packets
+        $packetCount = count($packets);
+
+        // Compare last var of current packet with first var of next packet
+        // On a partial match, remove last var from current packet,
+        // variable header from next packet
+        for ($i = 0, $x = $packetCount; $i < $x - 1; $i++) {
+            // First packet
+            $fst = substr($packets[$i], 0, -1);
+            // Second packet
+            $snd = $packets[$i + 1];
+            // Get last variable from first packet
+            $fstvar = substr($fst, strrpos($fst, "\x00") + 1);
+            // Get first variable from last packet
+            $snd = substr($snd, strpos($snd, "\x00") + 2);
+            $sndvar = substr($snd, 0, strpos($snd, "\x00"));
+            // Check if fstvar is a substring of sndvar
+            // If so, remove it from the first string
+            if (!empty($fstvar) && strpos($sndvar, $fstvar) !== false) {
+                $packets[$i] = preg_replace("#(\\x00[^\\x00]+\\x00)$#", "\x00", $packets[$i]);
+            }
+        }
+
+        // Now let's loop the return and remove any dupe prefixes
+        for ($x = 1; $x < $packetCount; $x++) {
+            $buffer = new Buffer($packets[$x], Buffer::NUMBER_TYPE_BIGENDIAN);
+
+            $prefix = $buffer->readString();
+
+            // Check to see if the return before has the same prefix present
+            if ($prefix != null && strstr($packets[($x - 1)], $prefix)) {
+                // Update the return by removing the prefix plus 2 chars
+                $packets[$x] = substr(str_replace($prefix, '', $packets[$x]), 2);
+            }
+
+            unset($buffer);
+        }
+
+        unset($x, $i, $snd, $sndvar, $fst, $fstvar);
+
+        // Return cleaned packets
+        return $packets;
+    }
+
+    /**
+     * Handles processing the details data into a usable format
+     *
+     * @param \GameQ\Buffer $buffer
+     * @param \GameQ\Result $result
+     */
+    protected function processDetails(Buffer &$buffer, Result &$result)
+    {
+
+        // We go until we hit an empty key
+        while ($buffer->getLength()) {
+            $key = $buffer->readString();
+            if (strlen($key) == 0) {
+                break;
+            }
+            $result->add($key, utf8_encode($buffer->readString()));
+        }
+    }
+
+    /**
+     * Handles processing the player and team data into a usable format
+     *
+     * @param \GameQ\Buffer $buffer
+     * @param \GameQ\Result $result
+     */
+    protected function processPlayersAndTeams(Buffer &$buffer, Result &$result)
+    {
+
+        /*
+         * Explode the data into groups. First is player, next is team (item_t)
+         * Each group should be as follows:
+         *
+         * [0] => item_
+         * [1] => information for item_
+         * ...
+         */
+        $data = explode("\x00\x00", $buffer->getBuffer());
+
+        // By default item_group is blank, this will be set for each loop thru the data
+        $item_group = '';
+
+        // By default the item_type is blank, this will be set on each loop
+        $item_type = '';
+
+        // Save count as variable
+        $count = count($data);
+
+        // Loop through all of the $data for information and pull it out into the result
+        for ($x = 0; $x < $count - 1; $x++) {
+            // Pull out the item
+            $item = $data[$x];
+            // If this is an empty item, move on
+            if ($item == '' || $item == "\x00") {
+                continue;
+            }
+            /*
+            * Left as reference:
+            *
+            * Each block of player_ and team_t have preceding junk chars
+            *
+            * player_ is actually \x01player_
+            * team_t is actually \x00\x02team_t
+            *
+            * Probably a by-product of the change to exploding the data from the original.
+            *
+            * For now we just strip out these characters
+            */
+            // Check to see if $item has a _ at the end, this is player info
+            if (substr($item, -1) == '_') {
+                // Set the item group
+                $item_group = 'players';
+                // Set the item type, rip off any trailing stuff and bad chars
+                $item_type = rtrim(str_replace("\x01", '', $item), '_');
+            } elseif (substr($item, -2) == '_t') {
+                // Check to see if $item has a _t at the end, this is team info
+                // Set the item group
+                $item_group = 'teams';
+                // Set the item type, rip off any trailing stuff and bad chars
+                $item_type = rtrim(str_replace(["\x00", "\x02"], '', $item), '_t');
+            } else {
+                // We can assume it is data belonging to a previously defined item
+
+                // Make a temp buffer so we have easier access to the data
+                $buf_temp = new Buffer($item, Buffer::NUMBER_TYPE_BIGENDIAN);
+                // Get the values
+                while ($buf_temp->getLength()) {
+                    // No value so break the loop, end of string
+                    if (($val = $buf_temp->readString()) === '') {
+                        break;
+                    }
+                    // Add the value to the proper item in the correct group
+                    $result->addSub($item_group, $item_type, utf8_encode(trim($val)));
+                }
+                // Unset our buffer
+                unset($buf_temp);
+            }
+        }
+        // Free up some memory
+        unset($count, $data, $item, $item_group, $item_type, $val);
+    }
+}

+ 34 - 0
OGP/php-query/gameq/Protocols/Gamespy4.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * GameSpy4 Protocol Class
+ *
+ * By all accounts GameSpy 4 seems to be GameSpy 3.
+ *
+ * References:
+ * http://www.deletedscreen.com/?p=951
+ * http://pastebin.com/2zZFDuTd
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Gamespy4 extends Gamespy3
+{
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Gmod.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Gmod
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Gmod extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'gmod';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Garry's Mod";
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Grav.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Grav Online Protocol Class
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Grav extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'grav';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "GRAV Online";
+}

+ 173 - 0
OGP/php-query/gameq/Protocols/Gta5m.php

@@ -0,0 +1,173 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Buffer;
+use GameQ\Exception\Protocol as Exception;
+use GameQ\Protocol;
+use GameQ\Result;
+
+/**
+ * GTA Five M Protocol Class
+ *
+ * Server base can be found at https://fivem.net/
+ *
+ * Based on code found at https://github.com/LiquidObsidian/fivereborn-query
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Gta5m extends Protocol
+{
+
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_STATUS => "\xFF\xFF\xFF\xFFgetinfo xxx",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        "\xFF\xFF\xFF\xFFinfoResponse" => "processStatus",
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'gta5m';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'gta5m';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "GTA Five M";
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'gametype'   => 'gametype',
+            'hostname'   => 'hostname',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'sv_maxclients',
+            'mod'        => 'gamename',
+            'numplayers' => 'clients',
+            'password'   => 'privateClients',
+        ],
+    ];
+
+    /**
+     * Process the response
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function processResponse()
+    {
+        // In case it comes back as multiple packets (it shouldn't)
+        $buffer = new Buffer(implode('', $this->packets_response));
+
+        // Figure out what packet response this is for
+        $response_type = $buffer->readString(PHP_EOL);
+
+        // Figure out which packet response this is
+        if (empty($response_type) || !array_key_exists($response_type, $this->responses)) {
+            throw new Exception(__METHOD__ . " response type '{$response_type}' is not valid");
+        }
+
+        // Offload the call
+        $results = call_user_func_array([$this, $this->responses[$response_type]], [$buffer]);
+
+        return $results;
+    }
+
+    /*
+     * Internal methods
+     */
+
+    /**
+     * Handle processing the status response
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processStatus(Buffer $buffer)
+    {
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Lets peek and see if the data starts with a \
+        if ($buffer->lookAhead(1) == '\\') {
+            // Burn the first one
+            $buffer->skip(1);
+        }
+
+        // Explode the data
+        $data = explode('\\', $buffer->getBuffer());
+
+        // No longer needed
+        unset($buffer);
+
+        $itemCount = count($data);
+
+        // Now lets loop the array
+        for ($x = 0; $x < $itemCount; $x += 2) {
+            // Set some local vars
+            $key = $data[$x];
+            $val = $data[$x + 1];
+
+            if (in_array($key, ['challenge'])) {
+                continue; // skip
+            }
+
+            // Regular variable so just add the value.
+            $result->add($key, $val);
+        }
+
+        /*var_dump($data);
+        var_dump($result->fetch());
+
+        exit;*/
+
+        return $result->fetch();
+    }
+}

+ 163 - 0
OGP/php-query/gameq/Protocols/Gtan.php

@@ -0,0 +1,163 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+namespace GameQ\Protocols;
+
+use GameQ\Exception\Protocol as Exception;
+use GameQ\Result;
+use GameQ\Server;
+
+/**
+ * Grand Theft Auto Network Protocol Class
+ * https://stats.gtanet.work/
+ *
+ * Result from this call should be a header + JSON response
+ *
+ * References:
+ * - https://master.gtanet.work/apiservers
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Gtan extends Http
+{
+    /**
+     * Packets to send
+     *
+     * @var array
+     */
+    protected $packets = [
+        //self::PACKET_STATUS => "GET /apiservers HTTP/1.0\r\nHost: master.gtanet.work\r\nAccept: */*\r\n\r\n",
+        self::PACKET_STATUS => "GET /gtan/api.php?ip=%s&raw HTTP/1.0\r\nHost: multiplayerhosting.info\r\nAccept: */*\r\n\r\n",
+    ];
+
+    /**
+     * Http protocol is SSL
+     *
+     * @var string
+     */
+    protected $transport = self::TRANSPORT_SSL;
+
+    /**
+     * The protocol being used
+     *
+     * @var string
+     */
+    protected $protocol = 'gtan';
+
+    /**
+     * String name of this protocol class
+     *
+     * @var string
+     */
+    protected $name = 'gtan';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @var string
+     */
+    protected $name_long = "Grand Theft Auto Network";
+
+    /**
+     * Holds the real ip so we can overwrite it back
+     *
+     * @var string
+     */
+    protected $realIp = null;
+
+    protected $realPortQuery = null;
+
+    /**
+     * Normalize some items
+     *
+     * @var array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'hostname'   => 'hostname',
+            'mapname'    => 'map',
+            'mod'        => 'mod',
+            'maxplayers' => 'maxplayers',
+            'numplayers' => 'numplayers',
+            'password'   => 'password',
+        ],
+    ];
+
+    public function beforeSend(Server $server)
+    {
+        // Loop over the packets and update them
+        foreach ($this->packets as $packetType => $packet) {
+            // Fill out the packet with the server info
+            $this->packets[$packetType] = sprintf($packet, $server->ip . ':' . $server->port_query);
+        }
+
+        $this->realIp = $server->ip;
+        $this->realPortQuery = $server->port_query;
+
+        // Override the existing settings
+        //$server->ip = 'master.gtanet.work';
+        $server->ip = 'multiplayerhosting.info';
+        $server->port_query = 443;
+    }
+
+    /**
+     * Process the response
+     *
+     * @return array
+     * @throws Exception
+     */
+    public function processResponse()
+    {
+        // No response, assume offline
+        if (empty($this->packets_response)) {
+            return [
+                'gq_address'    => $this->realIp,
+                'gq_port_query' => $this->realPortQuery,
+            ];
+        }
+
+        // Implode and rip out the JSON
+        preg_match('/\{(.*)\}/ms', implode('', $this->packets_response), $matches);
+
+        // Return should be JSON, let's validate
+        if (!isset($matches[0]) || ($json = json_decode($matches[0])) === null) {
+            throw new Exception("JSON response from Gtan protocol is invalid.");
+        }
+
+        $result = new Result();
+
+        // Server is always dedicated
+        $result->add('dedicated', 1);
+
+        $result->add('gq_address', $this->realIp);
+        $result->add('gq_port_query', $this->realPortQuery);
+
+        // Add server items
+        $result->add('hostname', $json->ServerName);
+        $result->add('serverversion', $json->ServerVersion);
+        $result->add('map', ((!empty($json->Map)) ? $json->Map : 'Los Santos/Blaine Country'));
+        $result->add('mod', $json->Gamemode);
+        $result->add('password', (int)$json->Passworded);
+        $result->add('numplayers', $json->CurrentPlayers);
+        $result->add('maxplayers', $json->MaxPlayers);
+
+        return $result->fetch();
+    }
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Hl2dm.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Hl2dm
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Hl2dm extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'hl2dm';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Half Life 2: Deathmatch";
+}

+ 67 - 0
OGP/php-query/gameq/Protocols/Http.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+
+/**
+ * Class Http
+ *
+ * Generic HTTP protocol class.  Useful for making http based requests
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+abstract class Http extends Protocol
+{
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'http';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'http';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Generic HTTP protocol";
+
+    /**
+     * Http protocol is TCP
+     *
+     * @var string
+     */
+    protected $transport = self::TRANSPORT_TCP;
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = null;
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Hurtworld.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Hurtworld
+ *
+ * @package GameQ\Protocols
+ * @author  Nikolay Ipanyuk <[email protected]>
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Hurtworld extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'hurtworld';
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Hurtworld";
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Insurgency.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Insurgency
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Insurgency extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'insurgency';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Insurgency";
+}

+ 49 - 0
OGP/php-query/gameq/Protocols/Insurgencysand.php

@@ -0,0 +1,49 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Insurgency Sandstorm Class
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Insurgencysand extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'insurgencysand';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Insurgency: Sandstorm";
+
+    /**
+     * query_port = client_port + 29
+     *
+     * @type int
+     */
+    protected $port_diff = 29;
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Jediacademy.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Jedi Academy Protocol Class
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Jediacademy extends Quake3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'jediacademy';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Star Wars Jedi Knight: Jedi Academy";
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Jedioutcast.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Jedi Outcast Protocol Class
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Jedioutcast extends Quake3
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'jedioutcast';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Star Wars Jedi Knight II: Jedi Outcast";
+}

+ 127 - 0
OGP/php-query/gameq/Protocols/Justcause2.php

@@ -0,0 +1,127 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Buffer;
+use GameQ\Result;
+
+/**
+ * Just Cause 2 Multiplayer Protocol Class
+ *
+ * Special thanks to Woet for some insight on packing
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Justcause2 extends Gamespy4
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'justcause2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Just Cause 2 Multiplayer";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = "steam://connect/%s:%d/";
+
+    /**
+     * Change the packets used
+     *
+     * @var array
+     */
+    protected $packets = [
+        self::PACKET_CHALLENGE => "\xFE\xFD\x09\x10\x20\x30\x40",
+        self::PACKET_ALL       => "\xFE\xFD\x00\x10\x20\x30\x40%s\xFF\xFF\xFF\x02",
+    ];
+
+    /**
+     * Override the packet split
+     *
+     * @var string
+     */
+    protected $packetSplit = "/\\x00\\x00\\x00/m";
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'gametype'   => 'gametype',
+            'hostname'   => 'hostname',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'maxplayers',
+            'numplayers' => 'numplayers',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name' => 'name',
+            'ping' => 'ping',
+        ],
+    ];
+
+    /**
+     * Overload so we can add in some static data points
+     *
+     * @param Buffer $buffer
+     * @param Result $result
+     */
+    protected function processDetails(Buffer &$buffer, Result &$result)
+    {
+        parent::processDetails($buffer, $result);
+
+        // Add in map
+        $result->add('mapname', 'Panau');
+        $result->add('dedicated', 'true');
+    }
+
+    /**
+     * Override the parent, this protocol is returned differently
+     *
+     * @param Buffer $buffer
+     * @param Result $result
+     *
+     * @see Gamespy3::processPlayersAndTeams()
+     */
+    protected function processPlayersAndTeams(Buffer &$buffer, Result &$result)
+    {
+        // Loop until we run out of data
+        while ($buffer->getLength()) {
+            $result->addPlayer('name', $buffer->readString());
+            $result->addPlayer('steamid', $buffer->readString());
+            $result->addPlayer('ping', $buffer->readInt16());
+        }
+    }
+}

+ 50 - 0
OGP/php-query/gameq/Protocols/Justcause3.php

@@ -0,0 +1,50 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Just Cause 3
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Justcause3 extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'justcause3';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Just Cause 3";
+
+    /**
+     * Query port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+}

+ 96 - 0
OGP/php-query/gameq/Protocols/Killingfloor.php

@@ -0,0 +1,96 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Buffer;
+use GameQ\Result;
+
+/**
+ * Class Killing floor
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Killingfloor extends Unreal2
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'killing floor';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Killing Floor";
+
+    /**
+     * query_port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = "steam://connect/%s:%d/";
+
+    /**
+     * Overload the default detail process since this version is different
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processDetails(Buffer $buffer)
+    {
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        $result->add('serverid', $buffer->readInt32()); // 0
+        $result->add('serverip', $buffer->readPascalString(1)); // empty
+        $result->add('gameport', $buffer->readInt32());
+        $result->add('queryport', $buffer->readInt32()); // 0
+
+        // We burn the first char since it is not always correct with the hostname
+        $buffer->skip(1);
+
+        // Read as a regular string since the length is incorrect (what we skipped earlier)
+        $result->add('servername', utf8_encode($buffer->readString()));
+
+        // The rest is read as normal
+        $result->add('mapname', utf8_encode($buffer->readPascalString(1)));
+        $result->add('gametype', $buffer->readPascalString(1));
+        $result->add('numplayers', $buffer->readInt32());
+        $result->add('maxplayers', $buffer->readInt32());
+        $result->add('currentwave', $buffer->readInt32());
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+}

+ 51 - 0
OGP/php-query/gameq/Protocols/Killingfloor2.php

@@ -0,0 +1,51 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Killing floor
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Killingfloor2 extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'killing floor 2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Killing Floor 2";
+
+    /**
+     * query_port = client_port + 19238
+     * 27015 = 7777 + 19238
+     *
+     * @type int
+     */
+    protected $port_diff = 19238;
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/L4d.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class L4d
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class L4d extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'l4d';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Left 4 Dead";
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/L4d2.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class L4d2
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class L4d2 extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'l4d2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Left 4 Dead 2";
+}

+ 214 - 0
OGP/php-query/gameq/Protocols/Lhmp.php

@@ -0,0 +1,214 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+use GameQ\Exception\Protocol as Exception;
+
+/**
+ * Lost Heaven Protocol class
+ *
+ * Reference: http://lh-mp.eu/wiki/index.php/Query_System
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Lhmp extends Protocol
+{
+
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_DETAILS => "LHMPo",
+        self::PACKET_PLAYERS => "LHMPp",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        "LHMPo" => "processDetails",
+        "LHMPp" => "processPlayers",
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'lhmp';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'lhmp';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Lost Heaven";
+
+    /**
+     * query_port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'gametype'   => 'gamemode',
+            'hostname'   => 'servername',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'maxplayers',
+            'numplayers' => 'numplayers',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name'  => 'name',
+        ],
+    ];
+
+    /**
+     * Process the response
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function processResponse()
+    {
+        // Will hold the packets after sorting
+        $packets = [];
+
+        // We need to pre-sort these for split packets so we can do extra work where needed
+        foreach ($this->packets_response as $response) {
+            $buffer = new Buffer($response);
+
+            // Pull out the header
+            $header = $buffer->read(5);
+
+            // Add the packet to the proper section, we will combine later
+            $packets[$header][] = $buffer->getBuffer();
+        }
+
+        unset($buffer);
+
+        $results = [];
+
+        // Now let's iterate and process
+        foreach ($packets as $header => $packetGroup) {
+            // Figure out which packet response this is
+            if (!array_key_exists($header, $this->responses)) {
+                throw new Exception(__METHOD__ . " response type '{$header}' is not valid");
+            }
+
+            // Now we need to call the proper method
+            $results = array_merge(
+                $results,
+                call_user_func_array([$this, $this->responses[$header]], [new Buffer(implode($packetGroup))])
+            );
+        }
+
+        unset($packets);
+
+        return $results;
+    }
+
+    /*
+     * Internal methods
+     */
+
+    /**
+     * Handles processing the details data into a usable format
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     * @throws Exception
+     */
+    protected function processDetails(Buffer $buffer)
+    {
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        $result->add('protocol', $buffer->readString());
+        $result->add('password', $buffer->readString());
+        $result->add('numplayers', $buffer->readInt16());
+        $result->add('maxplayers', $buffer->readInt16());
+        $result->add('servername', utf8_encode($buffer->readPascalString()));
+        $result->add('gamemode', $buffer->readPascalString());
+        $result->add('website', utf8_encode($buffer->readPascalString()));
+        $result->add('mapname', utf8_encode($buffer->readPascalString()));
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Handles processing the player data into a usable format
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processPlayers(Buffer $buffer)
+    {
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Get the number of players
+        $result->add('numplayers', $buffer->readInt16());
+
+        // Parse players
+        while ($buffer->getLength()) {
+            // Player id
+            if (($id = $buffer->readInt16()) !== 0) {
+                // Add the results
+                $result->addPlayer('id', $id);
+                $result->addPlayer('name', utf8_encode($buffer->readPascalString()));
+            }
+        }
+
+        unset($buffer, $id);
+
+        return $result->fetch();
+    }
+}

+ 87 - 0
OGP/php-query/gameq/Protocols/Minecraft.php

@@ -0,0 +1,87 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Minecraft Protocol Class
+ *
+ * Thanks to https://github.com/xPaw/PHP-Minecraft-Query for helping me realize this is
+ * Gamespy 3 Protocol.  Make sure you enable the items below for it to work.
+ *
+ * Information from original author:
+ * Instructions
+ *
+ * Before using this class, you need to make sure that your server is running GS4 status listener.
+ *
+ * Look for those settings in server.properties:
+ *
+ *    enable-query=true
+ *    query.port=25565
+ *
+ * @package GameQ\Protocols
+ *
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Minecraft extends Gamespy3
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'minecraft';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Minecraft";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = "minecraft://%s:%d/";
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'gametype'   => 'game_id',
+            'hostname'   => 'hostname',
+            'mapname'    => 'map',
+            'maxplayers' => 'maxplayers',
+            'numplayers' => 'numplayers',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name' => 'player',
+        ],
+    ];
+}

+ 44 - 0
OGP/php-query/gameq/Protocols/Minecraftpe.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Minecraft PE (BE) Protocol Class
+ *
+ * @package GameQ\Protocols
+ *
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Minecraftpe extends Minecraft
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'minecraftpe';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "MinecraftPE";
+}

+ 79 - 0
OGP/php-query/gameq/Protocols/Mohaa.php

@@ -0,0 +1,79 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Medal of honor: Allied Assault Protocol Class
+ *
+ * @package GameQ\Protocols
+ * @author  Bram <https://github.com/Stormyy>
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Mohaa extends Gamespy
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'mohaa';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Medal of honor: Allied Assault";
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'gametype'   => 'gametype',
+            'hostname'   => 'hostname',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'maxplayers',
+            'numplayers' => 'numplayers',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name'  => 'player',
+            'score' => 'frags',
+            'ping'  => 'ping',
+        ],
+    ];
+
+    /**
+     * Query port is always the client port + 97 in MOHAA
+     *
+     * @param int $clientPort
+     *
+     * @return int
+     */
+    public function findQueryPort($clientPort)
+    {
+        return $clientPort + 97;
+    }
+}

+ 53 - 0
OGP/php-query/gameq/Protocols/Mordhau.php

@@ -0,0 +1,53 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class MORDHAU
+ *
+ * @package GameQ\Protocols
+ * @author Wilson Jesus <>
+ */
+class Mordhau extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'mordhau';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "MORDHAU";
+
+    #protected $port = 7777;
+
+    /**
+     * query_port = client_port + 19238
+     * 27015 = 7777 + 19238
+     *
+     * @type int
+     */
+    #protected $port_diff = 19238;
+}

+ 59 - 0
OGP/php-query/gameq/Protocols/Mta.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Multi Theft Auto
+ *
+ * @package GameQ\Protocols
+ *
+ * @author  Marcel Bößendörfer <[email protected]>
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Mta extends Ase
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'mta';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Multi Theft Auto";
+
+    /**
+     * query_port = client_port + 123
+     *
+     * @type int
+     */
+    protected $port_diff = 123;
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = "mtasa://%s:%d/";
+}

+ 194 - 0
OGP/php-query/gameq/Protocols/Mumble.php

@@ -0,0 +1,194 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Result;
+use GameQ\Exception\Protocol as Exception;
+
+/**
+ * Mumble Protocol class
+ *
+ * References:
+ * https://github.com/edmundask/MurmurQuery - Thanks to skylord123
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Mumble extends Protocol
+{
+
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_ALL => "\x6A\x73\x6F\x6E", // JSON packet
+    ];
+
+    /**
+     * The transport mode for this protocol is TCP
+     *
+     * @type string
+     */
+    protected $transport = self::TRANSPORT_TCP;
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'mumble';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'mumble';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Mumble Server";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = "mumble://%s:%d/";
+
+    /**
+     * 27800 = 64738 - 36938
+     *
+     * @type int
+     */
+    protected $port_diff = -36938;
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            'dedicated'  => 'dedicated',
+            'gametype'   => 'gametype',
+            'hostname'   => 'name',
+            'numplayers' => 'numplayers',
+            'maxplayers' => 'x_gtmurmur_max_users',
+        ],
+        // Player
+        'player'  => [
+            'name' => 'name',
+            'ping' => 'tcpPing',
+            'team' => 'channel',
+            'time' => 'onlinesecs',
+        ],
+        // Team
+        'team'    => [
+            'name' => 'name',
+        ],
+    ];
+
+    /**
+     * Process the response
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function processResponse()
+    {
+
+        // Try to json_decode, make it into an array
+        if (($data = json_decode(implode('', $this->packets_response), true)) === null) {
+            throw new Exception(__METHOD__ . " Unable to decode JSON data.");
+        }
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Always dedicated
+        $result->add('dedicated', 1);
+
+        // Let's iterate over the response items, there are a lot
+        foreach ($data as $key => $value) {
+            // Ignore root for now, that is where all of the channel/player info is housed
+            if (in_array($key, ['root'])) {
+                continue;
+            }
+
+            // Add them as is
+            $result->add($key, $value);
+        }
+
+        // Offload the channel and user parsing
+        $this->processChannelsAndUsers($data['root'], $result);
+
+        unset($data);
+
+        // Manually set the number of players
+        $result->add('numplayers', count($result->get('players')));
+
+        return $result->fetch();
+    }
+
+    /*
+     * Internal methods
+     */
+
+    /**
+     * Handles processing the the channels and user info
+     *
+     * @param array         $data
+     * @param \GameQ\Result $result
+     */
+    protected function processChannelsAndUsers(array $data, Result &$result)
+    {
+
+        // Let's add all of the channel information
+        foreach ($data as $key => $value) {
+            // We will handle these later
+            if (in_array($key, ['channels', 'users'])) {
+                // skip
+                continue;
+            }
+
+            // Add the channel property as a team
+            $result->addTeam($key, $value);
+        }
+
+        // Itereate over the users in this channel
+        foreach ($data['users'] as $user) {
+            foreach ($user as $key => $value) {
+                $result->addPlayer($key, $value);
+            }
+        }
+
+        // Offload more channels to parse
+        foreach ($data['channels'] as $channel) {
+            $this->processChannelsAndUsers($channel, $result);
+        }
+    }
+}

+ 49 - 0
OGP/php-query/gameq/Protocols/Ns2.php

@@ -0,0 +1,49 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Ns2
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Ns2 extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'ns2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Natural Selection 2";
+
+    /**
+     * query_port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Pixark.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class PixARK
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Pixark extends Arkse
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'pixark';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "PixARK";
+}

+ 45 - 0
OGP/php-query/gameq/Protocols/Projectrealitybf2.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Projectrealitybf2
+ *
+ * Based off of BF2
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Projectrealitybf2 extends Bf2
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'projectrealitybf2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Project Reality: Battlefield 2";
+}

+ 219 - 0
OGP/php-query/gameq/Protocols/Quake2.php

@@ -0,0 +1,219 @@
+<?php
+
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+use GameQ\Exception\Protocol as Exception;
+
+/**
+ * Quake2 Protocol Class
+ *
+ * Handles processing Quake 3 servers
+ *
+ * @package GameQ\Protocols
+ */
+class Quake2 extends Protocol
+{
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_STATUS => "\xFF\xFF\xFF\xFFstatus\x00",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        "\xFF\xFF\xFF\xFF\x70\x72\x69\x6e\x74" => 'processStatus',
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'quake2';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'quake2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Quake 2 Server";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = null;
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'gametype'   => 'gamename',
+            'hostname'   => 'hostname',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'maxclients',
+            'mod'        => 'g_gametype',
+            'numplayers' => 'clients',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name'  => 'name',
+            'ping'  => 'ping',
+            'score' => 'frags',
+        ],
+    ];
+
+    /**
+     * Handle response from the server
+     *
+     * @return mixed
+     * @throws Exception
+     */
+    public function processResponse()
+    {
+        // Make a buffer
+        $buffer = new Buffer(implode('', $this->packets_response));
+
+        // Grab the header
+        $header = $buffer->readString("\x0A");
+
+        // Figure out which packet response this is
+        if (empty($header) || !array_key_exists($header, $this->responses)) {
+            throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
+        }
+
+        return call_user_func_array([$this, $this->responses[$header]], [$buffer]);
+    }
+
+    /**
+     * Process the status response
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processStatus(Buffer $buffer)
+    {
+        // We need to split the data and offload
+        $results = $this->processServerInfo(new Buffer($buffer->readString("\x0A")));
+
+        $results = array_merge_recursive(
+            $results,
+            $this->processPlayers(new Buffer($buffer->getBuffer()))
+        );
+
+        unset($buffer);
+
+        // Return results
+        return $results;
+    }
+
+    /**
+     * Handle processing the server information
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processServerInfo(Buffer $buffer)
+    {
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Burn leading \ if one exists
+        $buffer->readString('\\');
+
+        // Key / value pairs
+        while ($buffer->getLength()) {
+            // Add result
+            $result->add(
+                trim($buffer->readString('\\')),
+                utf8_encode(trim($buffer->readStringMulti(['\\', "\x0a"])))
+            );
+        }
+
+        $result->add('password', 0);
+        $result->add('mod', 0);
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Handle processing of player data
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processPlayers(Buffer $buffer)
+    {
+        // Some games do not have a number of current players
+        $playerCount = 0;
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Loop until we are out of data
+        while ($buffer->getLength()) {
+            // Make a new buffer with this block
+            $playerInfo = new Buffer($buffer->readString("\x0A"));
+
+            // Add player info
+            $result->addPlayer('frags', $playerInfo->readString("\x20"));
+            $result->addPlayer('ping', $playerInfo->readString("\x20"));
+
+            // Skip first "
+            $playerInfo->skip(1);
+
+            // Add player name, encoded
+            $result->addPlayer('name', utf8_encode(trim(($playerInfo->readString('"')))));
+
+            // Skip first "
+            $playerInfo->skip(2);
+
+            // Add address
+            $result->addPlayer('address', trim($playerInfo->readString('"')));
+
+            // Increment
+            $playerCount++;
+
+            // Clear
+            unset($playerInfo);
+        }
+
+        $result->add('clients', $playerCount);
+
+        // Clear
+        unset($buffer, $playerCount);
+
+        return $result->fetch();
+    }
+}

+ 214 - 0
OGP/php-query/gameq/Protocols/Quake3.php

@@ -0,0 +1,214 @@
+<?php
+
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+use GameQ\Exception\Protocol as Exception;
+
+/**
+ * Quake3 Protocol Class
+ *
+ * Handles processing Quake 3 servers
+ *
+ * @package GameQ\Protocols
+ */
+class Quake3 extends Protocol
+{
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_STATUS => "\xFF\xFF\xFF\xFF\x67\x65\x74\x73\x74\x61\x74\x75\x73\x0A",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        "\xFF\xFF\xFF\xFFstatusResponse" => 'processStatus',
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'quake3';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'quake3';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Quake 3 Server";
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = null;
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'gametype'   => 'gamename',
+            'hostname'   => 'sv_hostname',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'sv_maxclients',
+            'mod'        => 'g_gametype',
+            'numplayers' => 'clients',
+            'password'   => ['g_needpass', 'pswrd'],
+        ],
+        // Individual
+        'player'  => [
+            'name'  => 'name',
+            'ping'  => 'ping',
+            'score' => 'frags',
+        ],
+    ];
+
+    /**
+     * Handle response from the server
+     *
+     * @return mixed
+     * @throws Exception
+     */
+    public function processResponse()
+    {
+        // Make a buffer
+        $buffer = new Buffer(implode('', $this->packets_response));
+
+        // Grab the header
+        $header = $buffer->readString("\x0A");
+
+        // Figure out which packet response this is
+        if (empty($header) || !array_key_exists($header, $this->responses)) {
+            throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
+        }
+
+        return call_user_func_array([$this, $this->responses[$header]], [$buffer]);
+    }
+
+    protected function processStatus(Buffer $buffer)
+    {
+        // We need to split the data and offload
+        $results = $this->processServerInfo(new Buffer($buffer->readString("\x0A")));
+
+        $results = array_merge_recursive(
+            $results,
+            $this->processPlayers(new Buffer($buffer->getBuffer()))
+        );
+
+        unset($buffer);
+
+        // Return results
+        return $results;
+    }
+
+    /**
+     * Handle processing the server information
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processServerInfo(Buffer $buffer)
+    {
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Burn leading \ if one exists
+        $buffer->readString('\\');
+
+        // Key / value pairs
+        while ($buffer->getLength()) {
+            // Add result
+            $result->add(
+                trim($buffer->readString('\\')),
+                utf8_encode(trim($buffer->readStringMulti(['\\', "\x0a"])))
+            );
+        }
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Handle processing of player data
+     *
+     * @param Buffer $buffer
+     *
+     * @return array
+     * @throws Exception
+     */
+    protected function processPlayers(Buffer $buffer)
+    {
+        // Some games do not have a number of current players
+        $playerCount = 0;
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Loop until we are out of data
+        while ($buffer->getLength()) {
+            // Add player info
+            $result->addPlayer('frags', $buffer->readString("\x20"));
+            $result->addPlayer('ping', $buffer->readString("\x20"));
+
+            // Look ahead to see if we have a name or team
+            $checkTeam = $buffer->lookAhead(1);
+
+            // We have team info
+            if ($checkTeam != '' and $checkTeam != '"') {
+                $result->addPlayer('team', $buffer->readString("\x20"));
+            }
+
+            // Check to make sure we have player name
+            $checkPlayerName = $buffer->read();
+
+            // Bad response
+            if ($checkPlayerName !== '"') {
+                throw new Exception('Expected " but got ' . $checkPlayerName . ' for beginning of player name string!');
+            }
+
+            // Add player name, encoded
+            $result->addPlayer('name', utf8_encode(trim($buffer->readString('"'))));
+
+            // Burn ending delimiter
+            $buffer->read();
+
+            // Increment
+            $playerCount++;
+        }
+
+        $result->add('clients', $playerCount);
+
+        // Clear
+        unset($buffer, $playerCount);
+
+        return $result->fetch();
+    }
+}

+ 42 - 0
OGP/php-query/gameq/Protocols/Quakelive.php

@@ -0,0 +1,42 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Quake Live
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Quakelive extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'quakelive';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Quake Live";
+}

+ 50 - 0
OGP/php-query/gameq/Protocols/Redorchestra2.php

@@ -0,0 +1,50 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Redorchestra2
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Redorchestra2 extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'redorchestra2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Red Orchestra 2";
+
+    /**
+     * query_port = client_port + 19238
+     * 27015 = 7777 + 19238
+     *
+     * @type int
+     */
+    protected $port_diff = 19238;
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Redorchestraostfront.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Red Orchestra: Ostfront 41-45 Class
+ *
+ * @package GameQ\Protocols
+ * @author naXe <[email protected]>
+ * @author Austin Bischoff <[email protected]>
+ */
+class Redorchestraostfront extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'redorchestraostfront';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Red Orchestra: Ostfront 41-45";
+}

+ 55 - 0
OGP/php-query/gameq/Protocols/Risingstorm2.php

@@ -0,0 +1,55 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Rising Storm 2
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Risingstorm2 extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'rising storm 2';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Rising Storm 2";
+
+    /**
+     * Query port is always 27015
+     *
+     * @param int $clientPort
+     *
+     * @return int
+     */
+    public function findQueryPort($clientPort)
+    {
+        return 27015;
+    }
+}

+ 43 - 0
OGP/php-query/gameq/Protocols/Rust.php

@@ -0,0 +1,43 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class Rust
+ *
+ * @package GameQ\Protocols
+ * @author  Austin Bischoff <[email protected]>
+ */
+class Rust extends Source
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'rust';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Rust";
+}

+ 279 - 0
OGP/php-query/gameq/Protocols/Samp.php

@@ -0,0 +1,279 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+use GameQ\Protocol;
+use GameQ\Buffer;
+use GameQ\Result;
+use GameQ\Server;
+use GameQ\Exception\Protocol as Exception;
+
+/**
+ * San Andreas Multiplayer Protocol Class (samp)
+ *
+ * Note:
+ * Player information will not be returned if player count is over 256
+ *
+ * @author Austin Bischoff <[email protected]>
+ */
+class Samp extends Protocol
+{
+
+    /**
+     * Array of packets we want to look up.
+     * Each key should correspond to a defined method in this or a parent class
+     *
+     * @type array
+     */
+    protected $packets = [
+        self::PACKET_STATUS  => "SAMP%si",
+        self::PACKET_PLAYERS => "SAMP%sd",
+        self::PACKET_RULES   => "SAMP%sr",
+    ];
+
+    /**
+     * Use the response flag to figure out what method to run
+     *
+     * @type array
+     */
+    protected $responses = [
+        "\x69" => "processStatus", // i
+        "\x64" => "processPlayers", // d
+        "\x72" => "processRules", // r
+    ];
+
+    /**
+     * The query protocol used to make the call
+     *
+     * @type string
+     */
+    protected $protocol = 'samp';
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'samp';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "San Andreas Multiplayer";
+
+    /**
+     * Holds the calculated server code that is passed when querying for information
+     *
+     * @type string
+     */
+    protected $server_code = null;
+
+    /**
+     * The client join link
+     *
+     * @type string
+     */
+    protected $join_link = "samp://%s:%d/";
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'hostname'   => ['hostname', 'servername'],
+            'mapname'    => 'mapname',
+            'maxplayers' => 'max_players',
+            'numplayers' => 'num_players',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name'  => 'name',
+            'score' => 'score',
+            'ping'  => 'ping',
+        ],
+    ];
+
+    /**
+     * Handle some work before sending the packets out to the server
+     *
+     * @param \GameQ\Server $server
+     */
+    public function beforeSend(Server $server)
+    {
+
+        // Build the server code
+        $this->server_code = implode('', array_map('chr', explode('.', $server->ip()))) .
+            pack("S", $server->portClient());
+
+        // Loop over the packets and update them
+        foreach ($this->packets as $packetType => $packet) {
+            // Fill out the packet with the server info
+            $this->packets[$packetType] = sprintf($packet, $this->server_code);
+        }
+    }
+
+    /**
+     * Process the response
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    public function processResponse()
+    {
+
+        // Results that will be returned
+        $results = [];
+
+        // Get the length of the server code so we can figure out how much to read later
+        $serverCodeLength = strlen($this->server_code);
+
+        // We need to pre-sort these for split packets so we can do extra work where needed
+        foreach ($this->packets_response as $response) {
+            // Make new buffer
+            $buffer = new Buffer($response);
+
+            // Check the header, should be SAMP
+            if (($header = $buffer->read(4)) !== 'SAMP') {
+                throw new Exception(__METHOD__ . " header response '{$header}' is not valid");
+            }
+
+            // Check to make sure the server response code matches what we sent
+            if ($buffer->read($serverCodeLength) !== $this->server_code) {
+                throw new Exception(__METHOD__ . " code check failed.");
+            }
+
+            // Figure out what packet response this is for
+            $response_type = $buffer->read(1);
+
+            // Figure out which packet response this is
+            if (!array_key_exists($response_type, $this->responses)) {
+                throw new Exception(__METHOD__ . " response type '{$response_type}' is not valid");
+            }
+
+            // Now we need to call the proper method
+            $results = array_merge(
+                $results,
+                call_user_func_array([$this, $this->responses[$response_type]], [$buffer])
+            );
+
+            unset($buffer);
+        }
+
+        return $results;
+    }
+
+    /*
+     * Internal methods
+     */
+
+    /**
+     * Handles processing the server status data
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     * @throws \GameQ\Exception\Protocol
+     */
+    protected function processStatus(Buffer $buffer)
+    {
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Always dedicated
+        $result->add('dedicated', 1);
+
+        // Pull out the server information
+        $result->add('password', $buffer->readInt8());
+        $result->add('num_players', $buffer->readInt16());
+        $result->add('max_players', $buffer->readInt16());
+
+        // These are read differently for these last 3
+        $result->add('servername', utf8_encode($buffer->read($buffer->readInt32())));
+        $result->add('gametype', $buffer->read($buffer->readInt32()));
+        $result->add('language', $buffer->read($buffer->readInt32()));
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Handles processing the player data into a usable format
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processPlayers(Buffer $buffer)
+    {
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Number of players
+        $result->add('num_players', $buffer->readInt16());
+
+        // Run until we run out of buffer
+        while ($buffer->getLength()) {
+            $result->addPlayer('id', $buffer->readInt8());
+            $result->addPlayer('name', utf8_encode($buffer->readPascalString()));
+            $result->addPlayer('score', $buffer->readInt32());
+            $result->addPlayer('ping', $buffer->readInt32());
+        }
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+
+    /**
+     * Handles processing the rules data into a usable format
+     *
+     * @param \GameQ\Buffer $buffer
+     *
+     * @return array
+     */
+    protected function processRules(Buffer $buffer)
+    {
+
+        // Set the result to a new result instance
+        $result = new Result();
+
+        // Number of rules
+        $result->add('num_rules', $buffer->readInt16());
+
+        // Run until we run out of buffer
+        while ($buffer->getLength()) {
+            $result->add($buffer->readPascalString(), $buffer->readPascalString());
+        }
+
+        unset($buffer);
+
+        return $result->fetch();
+    }
+}

+ 75 - 0
OGP/php-query/gameq/Protocols/Serioussam.php

@@ -0,0 +1,75 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Serious Sam Protocol Class
+ *
+ * @author ZCaliptium <[email protected]>
+ */
+class SeriousSam extends Gamespy
+{
+
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'serioussam';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "Serious Sam";
+
+    /**
+     * query_port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+
+    /**
+     * Normalize settings for this protocol
+     *
+     * @type array
+     */
+    protected $normalize = [
+        // General
+        'general' => [
+            // target       => source
+            'dedicated'  => 'dedicated',
+            'gametype'   => 'gametype',
+            'hostname'   => 'hostname',
+            'mapname'    => 'mapname',
+            'maxplayers' => 'maxplayers',
+            'mod'        => 'activemod',
+            'numplayers' => 'numplayers',
+            'password'   => 'password',
+        ],
+        // Individual
+        'player'  => [
+            'name'  => 'player',
+            'ping'  => 'ping',
+            'score' => 'frags',
+        ],
+    ];
+}

+ 49 - 0
OGP/php-query/gameq/Protocols/Sevendaystodie.php

@@ -0,0 +1,49 @@
+<?php
+/**
+ * This file is part of GameQ.
+ *
+ * GameQ is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GameQ is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+namespace GameQ\Protocols;
+
+/**
+ * Class 7 Days to Die
+ *
+ * @package GameQ\Protocols
+ * @author Austin Bischoff <[email protected]>
+ */
+class Sevendaystodie extends Source
+{
+    /**
+     * String name of this protocol class
+     *
+     * @type string
+     */
+    protected $name = 'sevendaystodie';
+
+    /**
+     * Longer string name of this protocol class
+     *
+     * @type string
+     */
+    protected $name_long = "7 Days to Die";
+
+    /**
+     * query_port = client_port + 1
+     *
+     * @type int
+     */
+    protected $port_diff = 1;
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác