| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877 |
- <?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;
- /**
- * Ventrilo Protocol Class
- *
- * Note that a password is not required for versions >= 3.0.3
- *
- * All values are utf8 encoded upon processing
- *
- * This code ported from GameQ v1/v2. Credit to original author(s) as I just updated it to
- * work within this new system.
- *
- * @author Austin Bischoff <[email protected]>
- */
- class Ventrilo 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 =>
- "V\xc8\xf4\xf9`\xa2\x1e\xa5M\xfb\x03\xccQN\xa1\x10\x95\xaf\xb2g\x17g\x812\xfbW\xfd\x8e\xd2\x22r\x034z\xbb\x98",
- ];
- /**
- * The query protocol used to make the call
- *
- * @type string
- */
- protected $protocol = 'ventrilo';
- /**
- * String name of this protocol class
- *
- * @type string
- */
- protected $name = 'ventrilo';
- /**
- * Longer string name of this protocol class
- *
- * @type string
- */
- protected $name_long = "Ventrilo";
- /**
- * The client join link
- *
- * @type string
- */
- protected $join_link = "ventrilo://%s:%d/";
- /**
- * Normalize settings for this protocol
- *
- * @type array
- */
- protected $normalize = [
- // General
- 'general' => [
- 'dedicated' => 'dedicated',
- 'password' => 'auth',
- 'hostname' => 'name',
- 'numplayers' => 'clientcount',
- 'maxplayers' => 'maxclients',
- ],
- // Player
- 'player' => [
- 'team' => 'cid',
- 'name' => 'name',
- ],
- // Team
- 'team' => [
- 'id' => 'cid',
- 'name' => 'name',
- ],
- ];
- /**
- * Encryption table for the header
- *
- * @type array
- */
- private $head_encrypt_table = [
- 0x80,
- 0xe5,
- 0x0e,
- 0x38,
- 0xba,
- 0x63,
- 0x4c,
- 0x99,
- 0x88,
- 0x63,
- 0x4c,
- 0xd6,
- 0x54,
- 0xb8,
- 0x65,
- 0x7e,
- 0xbf,
- 0x8a,
- 0xf0,
- 0x17,
- 0x8a,
- 0xaa,
- 0x4d,
- 0x0f,
- 0xb7,
- 0x23,
- 0x27,
- 0xf6,
- 0xeb,
- 0x12,
- 0xf8,
- 0xea,
- 0x17,
- 0xb7,
- 0xcf,
- 0x52,
- 0x57,
- 0xcb,
- 0x51,
- 0xcf,
- 0x1b,
- 0x14,
- 0xfd,
- 0x6f,
- 0x84,
- 0x38,
- 0xb5,
- 0x24,
- 0x11,
- 0xcf,
- 0x7a,
- 0x75,
- 0x7a,
- 0xbb,
- 0x78,
- 0x74,
- 0xdc,
- 0xbc,
- 0x42,
- 0xf0,
- 0x17,
- 0x3f,
- 0x5e,
- 0xeb,
- 0x74,
- 0x77,
- 0x04,
- 0x4e,
- 0x8c,
- 0xaf,
- 0x23,
- 0xdc,
- 0x65,
- 0xdf,
- 0xa5,
- 0x65,
- 0xdd,
- 0x7d,
- 0xf4,
- 0x3c,
- 0x4c,
- 0x95,
- 0xbd,
- 0xeb,
- 0x65,
- 0x1c,
- 0xf4,
- 0x24,
- 0x5d,
- 0x82,
- 0x18,
- 0xfb,
- 0x50,
- 0x86,
- 0xb8,
- 0x53,
- 0xe0,
- 0x4e,
- 0x36,
- 0x96,
- 0x1f,
- 0xb7,
- 0xcb,
- 0xaa,
- 0xaf,
- 0xea,
- 0xcb,
- 0x20,
- 0x27,
- 0x30,
- 0x2a,
- 0xae,
- 0xb9,
- 0x07,
- 0x40,
- 0xdf,
- 0x12,
- 0x75,
- 0xc9,
- 0x09,
- 0x82,
- 0x9c,
- 0x30,
- 0x80,
- 0x5d,
- 0x8f,
- 0x0d,
- 0x09,
- 0xa1,
- 0x64,
- 0xec,
- 0x91,
- 0xd8,
- 0x8a,
- 0x50,
- 0x1f,
- 0x40,
- 0x5d,
- 0xf7,
- 0x08,
- 0x2a,
- 0xf8,
- 0x60,
- 0x62,
- 0xa0,
- 0x4a,
- 0x8b,
- 0xba,
- 0x4a,
- 0x6d,
- 0x00,
- 0x0a,
- 0x93,
- 0x32,
- 0x12,
- 0xe5,
- 0x07,
- 0x01,
- 0x65,
- 0xf5,
- 0xff,
- 0xe0,
- 0xae,
- 0xa7,
- 0x81,
- 0xd1,
- 0xba,
- 0x25,
- 0x62,
- 0x61,
- 0xb2,
- 0x85,
- 0xad,
- 0x7e,
- 0x9d,
- 0x3f,
- 0x49,
- 0x89,
- 0x26,
- 0xe5,
- 0xd5,
- 0xac,
- 0x9f,
- 0x0e,
- 0xd7,
- 0x6e,
- 0x47,
- 0x94,
- 0x16,
- 0x84,
- 0xc8,
- 0xff,
- 0x44,
- 0xea,
- 0x04,
- 0x40,
- 0xe0,
- 0x33,
- 0x11,
- 0xa3,
- 0x5b,
- 0x1e,
- 0x82,
- 0xff,
- 0x7a,
- 0x69,
- 0xe9,
- 0x2f,
- 0xfb,
- 0xea,
- 0x9a,
- 0xc6,
- 0x7b,
- 0xdb,
- 0xb1,
- 0xff,
- 0x97,
- 0x76,
- 0x56,
- 0xf3,
- 0x52,
- 0xc2,
- 0x3f,
- 0x0f,
- 0xb6,
- 0xac,
- 0x77,
- 0xc4,
- 0xbf,
- 0x59,
- 0x5e,
- 0x80,
- 0x74,
- 0xbb,
- 0xf2,
- 0xde,
- 0x57,
- 0x62,
- 0x4c,
- 0x1a,
- 0xff,
- 0x95,
- 0x6d,
- 0xc7,
- 0x04,
- 0xa2,
- 0x3b,
- 0xc4,
- 0x1b,
- 0x72,
- 0xc7,
- 0x6c,
- 0x82,
- 0x60,
- 0xd1,
- 0x0d,
- ];
- /**
- * Encryption table for the data
- *
- * @type array
- */
- private $data_encrypt_table = [
- 0x82,
- 0x8b,
- 0x7f,
- 0x68,
- 0x90,
- 0xe0,
- 0x44,
- 0x09,
- 0x19,
- 0x3b,
- 0x8e,
- 0x5f,
- 0xc2,
- 0x82,
- 0x38,
- 0x23,
- 0x6d,
- 0xdb,
- 0x62,
- 0x49,
- 0x52,
- 0x6e,
- 0x21,
- 0xdf,
- 0x51,
- 0x6c,
- 0x76,
- 0x37,
- 0x86,
- 0x50,
- 0x7d,
- 0x48,
- 0x1f,
- 0x65,
- 0xe7,
- 0x52,
- 0x6a,
- 0x88,
- 0xaa,
- 0xc1,
- 0x32,
- 0x2f,
- 0xf7,
- 0x54,
- 0x4c,
- 0xaa,
- 0x6d,
- 0x7e,
- 0x6d,
- 0xa9,
- 0x8c,
- 0x0d,
- 0x3f,
- 0xff,
- 0x6c,
- 0x09,
- 0xb3,
- 0xa5,
- 0xaf,
- 0xdf,
- 0x98,
- 0x02,
- 0xb4,
- 0xbe,
- 0x6d,
- 0x69,
- 0x0d,
- 0x42,
- 0x73,
- 0xe4,
- 0x34,
- 0x50,
- 0x07,
- 0x30,
- 0x79,
- 0x41,
- 0x2f,
- 0x08,
- 0x3f,
- 0x42,
- 0x73,
- 0xa7,
- 0x68,
- 0xfa,
- 0xee,
- 0x88,
- 0x0e,
- 0x6e,
- 0xa4,
- 0x70,
- 0x74,
- 0x22,
- 0x16,
- 0xae,
- 0x3c,
- 0x81,
- 0x14,
- 0xa1,
- 0xda,
- 0x7f,
- 0xd3,
- 0x7c,
- 0x48,
- 0x7d,
- 0x3f,
- 0x46,
- 0xfb,
- 0x6d,
- 0x92,
- 0x25,
- 0x17,
- 0x36,
- 0x26,
- 0xdb,
- 0xdf,
- 0x5a,
- 0x87,
- 0x91,
- 0x6f,
- 0xd6,
- 0xcd,
- 0xd4,
- 0xad,
- 0x4a,
- 0x29,
- 0xdd,
- 0x7d,
- 0x59,
- 0xbd,
- 0x15,
- 0x34,
- 0x53,
- 0xb1,
- 0xd8,
- 0x50,
- 0x11,
- 0x83,
- 0x79,
- 0x66,
- 0x21,
- 0x9e,
- 0x87,
- 0x5b,
- 0x24,
- 0x2f,
- 0x4f,
- 0xd7,
- 0x73,
- 0x34,
- 0xa2,
- 0xf7,
- 0x09,
- 0xd5,
- 0xd9,
- 0x42,
- 0x9d,
- 0xf8,
- 0x15,
- 0xdf,
- 0x0e,
- 0x10,
- 0xcc,
- 0x05,
- 0x04,
- 0x35,
- 0x81,
- 0xb2,
- 0xd5,
- 0x7a,
- 0xd2,
- 0xa0,
- 0xa5,
- 0x7b,
- 0xb8,
- 0x75,
- 0xd2,
- 0x35,
- 0x0b,
- 0x39,
- 0x8f,
- 0x1b,
- 0x44,
- 0x0e,
- 0xce,
- 0x66,
- 0x87,
- 0x1b,
- 0x64,
- 0xac,
- 0xe1,
- 0xca,
- 0x67,
- 0xb4,
- 0xce,
- 0x33,
- 0xdb,
- 0x89,
- 0xfe,
- 0xd8,
- 0x8e,
- 0xcd,
- 0x58,
- 0x92,
- 0x41,
- 0x50,
- 0x40,
- 0xcb,
- 0x08,
- 0xe1,
- 0x15,
- 0xee,
- 0xf4,
- 0x64,
- 0xfe,
- 0x1c,
- 0xee,
- 0x25,
- 0xe7,
- 0x21,
- 0xe6,
- 0x6c,
- 0xc6,
- 0xa6,
- 0x2e,
- 0x52,
- 0x23,
- 0xa7,
- 0x20,
- 0xd2,
- 0xd7,
- 0x28,
- 0x07,
- 0x23,
- 0x14,
- 0x24,
- 0x3d,
- 0x45,
- 0xa5,
- 0xc7,
- 0x90,
- 0xdb,
- 0x77,
- 0xdd,
- 0xea,
- 0x38,
- 0x59,
- 0x89,
- 0x32,
- 0xbc,
- 0x00,
- 0x3a,
- 0x6d,
- 0x61,
- 0x4e,
- 0xdb,
- 0x29,
- ];
- /**
- * Process the response
- *
- * @return array
- * @throws \GameQ\Exception\Protocol
- */
- public function processResponse()
- {
- // We need to decrypt the packets
- $decrypted = $this->decryptPackets($this->packets_response);
- // Now let us convert special characters from hex to ascii all at once
- $decrypted = preg_replace_callback(
- '|%([0-9A-F]{2})|',
- function ($matches) {
- // Pack this into ascii
- return pack('H*', $matches[1]);
- },
- $decrypted
- );
- // Explode into lines
- $lines = explode("\n", $decrypted);
- // Set the result to a new result instance
- $result = new Result();
- // Always dedicated
- $result->add('dedicated', 1);
- // Defaults
- $channelFields = 5;
- $playerFields = 7;
- // Iterate over the lines
- foreach ($lines as $line) {
- // Trim all the outlying space
- $line = trim($line);
- // We dont have anything in this line
- if (strlen($line) == 0) {
- continue;
- }
- /**
- * Everything is in this format: ITEM: VALUE
- *
- * Example:
- * ...
- * MAXCLIENTS: 175
- * VOICECODEC: 3,Speex
- * VOICEFORMAT: 31,32 KHz%2C 16 bit%2C 9 Qlty
- * UPTIME: 9167971
- * PLATFORM: Linux-i386
- * VERSION: 3.0.6
- * ...
- */
- // Check to see if we have a colon, every line should
- if (($colon_pos = strpos($line, ":")) !== false && $colon_pos > 0) {
- // Split the line into key/value pairs
- list($key, $value) = explode(':', $line, 2);
- // Lower the font of the key
- $key = strtolower($key);
- // Trim the value of extra space
- $value = trim($value);
- // Switch and offload items as needed
- switch ($key) {
- case 'client':
- $this->processPlayer($value, $playerFields, $result);
- break;
- case 'channel':
- $this->processChannel($value, $channelFields, $result);
- break;
- // Find the number of fields for the channels
- case 'channelfields':
- $channelFields = count(explode(',', $value));
- break;
- // Find the number of fields for the players
- case 'clientfields':
- $playerFields = count(explode(',', $value));
- break;
- // By default we just add they key as an item
- default:
- $result->add($key, utf8_encode($value));
- break;
- }
- }
- }
- unset($decrypted, $line, $lines, $colon_pos, $key, $value);
- return $result->fetch();
- }
- /*
- * Internal methods
- */
- /**
- * Decrypt the incoming packets
- *
- * @codeCoverageIgnore
- *
- * @param array $packets
- *
- * @return string
- * @throws \GameQ\Exception\Protocol
- */
- protected function decryptPackets(array $packets = [])
- {
- // This will be returned
- $decrypted = [];
- foreach ($packets as $packet) {
- # Header :
- $header = substr($packet, 0, 20);
- $header_items = [];
- $header_key = unpack("n1", $header);
- $key = array_shift($header_key);
- $chars = unpack("C*", substr($header, 2));
- $a1 = $key & 0xFF;
- $a2 = $key >> 8;
- if ($a1 == 0) {
- throw new Exception(__METHOD__ . ": Header key is invalid");
- }
- $table = $this->head_encrypt_table;
- $characterCount = count($chars);
- $key = 0;
- for ($index = 1; $index <= $characterCount; $index++) {
- $chars[$index] -= ($table[$a2] + (($index - 1) % 5)) & 0xFF;
- $a2 = ($a2 + $a1) & 0xFF;
- if (($index % 2) == 0) {
- $short_array = unpack("n1", pack("C2", $chars[$index - 1], $chars[$index]));
- $header_items[$key] = $short_array[1];
- ++$key;
- }
- }
- $header_items = array_combine([
- 'zero',
- 'cmd',
- 'id',
- 'totlen',
- 'len',
- 'totpck',
- 'pck',
- 'datakey',
- 'crc',
- ], $header_items);
- // Check to make sure the number of packets match
- if ($header_items['totpck'] != count($packets)) {
- throw new Exception(__METHOD__ . ": Too few packets received");
- }
- # Data :
- $table = $this->data_encrypt_table;
- $a1 = $header_items['datakey'] & 0xFF;
- $a2 = $header_items['datakey'] >> 8;
- if ($a1 == 0) {
- throw new Exception(__METHOD__ . ": Data key is invalid");
- }
- $chars = unpack("C*", substr($packet, 20));
- $data = "";
- $characterCount = count($chars);
- for ($index = 1; $index <= $characterCount; $index++) {
- $chars[$index] -= ($table[$a2] + (($index - 1) % 72)) & 0xFF;
- $a2 = ($a2 + $a1) & 0xFF;
- $data .= chr($chars[$index]);
- }
- //@todo: Check CRC ???
- $decrypted[$header_items['pck']] = $data;
- }
- // Return the decrypted packets as one string
- return implode('', $decrypted);
- }
- /**
- * Process the channel listing
- *
- * @param string $data
- * @param int $fieldCount
- * @param \GameQ\Result $result
- */
- protected function processChannel($data, $fieldCount, Result &$result)
- {
- // Split the items on the comma
- $items = explode(",", $data, $fieldCount);
- // Iterate over the items for this channel
- foreach ($items as $item) {
- // Split the key=value pair
- list($key, $value) = explode("=", $item, 2);
- $result->addTeam(strtolower($key), utf8_encode($value));
- }
- }
- /**
- * Process the user listing
- *
- * @param string $data
- * @param int $fieldCount
- * @param \GameQ\Result $result
- */
- protected function processPlayer($data, $fieldCount, Result &$result)
- {
- // Split the items on the comma
- $items = explode(",", $data, $fieldCount);
- // Iterate over the items for this player
- foreach ($items as $item) {
- // Split the key=value pair
- list($key, $value) = explode("=", $item, 2);
- $result->addPlayer(strtolower($key), utf8_encode($value));
- }
- }
- }
|