Ventrilo.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. <?php
  2. /**
  3. * This file is part of GameQ.
  4. *
  5. * GameQ is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU Lesser General Public License as published by
  7. * the Free Software Foundation; either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * GameQ is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. namespace GameQ\Protocols;
  19. use GameQ\Protocol;
  20. use GameQ\Result;
  21. use GameQ\Exception\Protocol as Exception;
  22. /**
  23. * Ventrilo Protocol Class
  24. *
  25. * Note that a password is not required for versions >= 3.0.3
  26. *
  27. * All values are utf8 encoded upon processing
  28. *
  29. * This code ported from GameQ v1/v2. Credit to original author(s) as I just updated it to
  30. * work within this new system.
  31. *
  32. * @author Austin Bischoff <[email protected]>
  33. */
  34. class Ventrilo extends Protocol
  35. {
  36. /**
  37. * Array of packets we want to look up.
  38. * Each key should correspond to a defined method in this or a parent class
  39. *
  40. * @type array
  41. */
  42. protected $packets = [
  43. self::PACKET_ALL =>
  44. "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",
  45. ];
  46. /**
  47. * The query protocol used to make the call
  48. *
  49. * @type string
  50. */
  51. protected $protocol = 'ventrilo';
  52. /**
  53. * String name of this protocol class
  54. *
  55. * @type string
  56. */
  57. protected $name = 'ventrilo';
  58. /**
  59. * Longer string name of this protocol class
  60. *
  61. * @type string
  62. */
  63. protected $name_long = "Ventrilo";
  64. /**
  65. * The client join link
  66. *
  67. * @type string
  68. */
  69. protected $join_link = "ventrilo://%s:%d/";
  70. /**
  71. * Normalize settings for this protocol
  72. *
  73. * @type array
  74. */
  75. protected $normalize = [
  76. // General
  77. 'general' => [
  78. 'dedicated' => 'dedicated',
  79. 'password' => 'auth',
  80. 'hostname' => 'name',
  81. 'numplayers' => 'clientcount',
  82. 'maxplayers' => 'maxclients',
  83. ],
  84. // Player
  85. 'player' => [
  86. 'team' => 'cid',
  87. 'name' => 'name',
  88. ],
  89. // Team
  90. 'team' => [
  91. 'id' => 'cid',
  92. 'name' => 'name',
  93. ],
  94. ];
  95. /**
  96. * Encryption table for the header
  97. *
  98. * @type array
  99. */
  100. private $head_encrypt_table = [
  101. 0x80,
  102. 0xe5,
  103. 0x0e,
  104. 0x38,
  105. 0xba,
  106. 0x63,
  107. 0x4c,
  108. 0x99,
  109. 0x88,
  110. 0x63,
  111. 0x4c,
  112. 0xd6,
  113. 0x54,
  114. 0xb8,
  115. 0x65,
  116. 0x7e,
  117. 0xbf,
  118. 0x8a,
  119. 0xf0,
  120. 0x17,
  121. 0x8a,
  122. 0xaa,
  123. 0x4d,
  124. 0x0f,
  125. 0xb7,
  126. 0x23,
  127. 0x27,
  128. 0xf6,
  129. 0xeb,
  130. 0x12,
  131. 0xf8,
  132. 0xea,
  133. 0x17,
  134. 0xb7,
  135. 0xcf,
  136. 0x52,
  137. 0x57,
  138. 0xcb,
  139. 0x51,
  140. 0xcf,
  141. 0x1b,
  142. 0x14,
  143. 0xfd,
  144. 0x6f,
  145. 0x84,
  146. 0x38,
  147. 0xb5,
  148. 0x24,
  149. 0x11,
  150. 0xcf,
  151. 0x7a,
  152. 0x75,
  153. 0x7a,
  154. 0xbb,
  155. 0x78,
  156. 0x74,
  157. 0xdc,
  158. 0xbc,
  159. 0x42,
  160. 0xf0,
  161. 0x17,
  162. 0x3f,
  163. 0x5e,
  164. 0xeb,
  165. 0x74,
  166. 0x77,
  167. 0x04,
  168. 0x4e,
  169. 0x8c,
  170. 0xaf,
  171. 0x23,
  172. 0xdc,
  173. 0x65,
  174. 0xdf,
  175. 0xa5,
  176. 0x65,
  177. 0xdd,
  178. 0x7d,
  179. 0xf4,
  180. 0x3c,
  181. 0x4c,
  182. 0x95,
  183. 0xbd,
  184. 0xeb,
  185. 0x65,
  186. 0x1c,
  187. 0xf4,
  188. 0x24,
  189. 0x5d,
  190. 0x82,
  191. 0x18,
  192. 0xfb,
  193. 0x50,
  194. 0x86,
  195. 0xb8,
  196. 0x53,
  197. 0xe0,
  198. 0x4e,
  199. 0x36,
  200. 0x96,
  201. 0x1f,
  202. 0xb7,
  203. 0xcb,
  204. 0xaa,
  205. 0xaf,
  206. 0xea,
  207. 0xcb,
  208. 0x20,
  209. 0x27,
  210. 0x30,
  211. 0x2a,
  212. 0xae,
  213. 0xb9,
  214. 0x07,
  215. 0x40,
  216. 0xdf,
  217. 0x12,
  218. 0x75,
  219. 0xc9,
  220. 0x09,
  221. 0x82,
  222. 0x9c,
  223. 0x30,
  224. 0x80,
  225. 0x5d,
  226. 0x8f,
  227. 0x0d,
  228. 0x09,
  229. 0xa1,
  230. 0x64,
  231. 0xec,
  232. 0x91,
  233. 0xd8,
  234. 0x8a,
  235. 0x50,
  236. 0x1f,
  237. 0x40,
  238. 0x5d,
  239. 0xf7,
  240. 0x08,
  241. 0x2a,
  242. 0xf8,
  243. 0x60,
  244. 0x62,
  245. 0xa0,
  246. 0x4a,
  247. 0x8b,
  248. 0xba,
  249. 0x4a,
  250. 0x6d,
  251. 0x00,
  252. 0x0a,
  253. 0x93,
  254. 0x32,
  255. 0x12,
  256. 0xe5,
  257. 0x07,
  258. 0x01,
  259. 0x65,
  260. 0xf5,
  261. 0xff,
  262. 0xe0,
  263. 0xae,
  264. 0xa7,
  265. 0x81,
  266. 0xd1,
  267. 0xba,
  268. 0x25,
  269. 0x62,
  270. 0x61,
  271. 0xb2,
  272. 0x85,
  273. 0xad,
  274. 0x7e,
  275. 0x9d,
  276. 0x3f,
  277. 0x49,
  278. 0x89,
  279. 0x26,
  280. 0xe5,
  281. 0xd5,
  282. 0xac,
  283. 0x9f,
  284. 0x0e,
  285. 0xd7,
  286. 0x6e,
  287. 0x47,
  288. 0x94,
  289. 0x16,
  290. 0x84,
  291. 0xc8,
  292. 0xff,
  293. 0x44,
  294. 0xea,
  295. 0x04,
  296. 0x40,
  297. 0xe0,
  298. 0x33,
  299. 0x11,
  300. 0xa3,
  301. 0x5b,
  302. 0x1e,
  303. 0x82,
  304. 0xff,
  305. 0x7a,
  306. 0x69,
  307. 0xe9,
  308. 0x2f,
  309. 0xfb,
  310. 0xea,
  311. 0x9a,
  312. 0xc6,
  313. 0x7b,
  314. 0xdb,
  315. 0xb1,
  316. 0xff,
  317. 0x97,
  318. 0x76,
  319. 0x56,
  320. 0xf3,
  321. 0x52,
  322. 0xc2,
  323. 0x3f,
  324. 0x0f,
  325. 0xb6,
  326. 0xac,
  327. 0x77,
  328. 0xc4,
  329. 0xbf,
  330. 0x59,
  331. 0x5e,
  332. 0x80,
  333. 0x74,
  334. 0xbb,
  335. 0xf2,
  336. 0xde,
  337. 0x57,
  338. 0x62,
  339. 0x4c,
  340. 0x1a,
  341. 0xff,
  342. 0x95,
  343. 0x6d,
  344. 0xc7,
  345. 0x04,
  346. 0xa2,
  347. 0x3b,
  348. 0xc4,
  349. 0x1b,
  350. 0x72,
  351. 0xc7,
  352. 0x6c,
  353. 0x82,
  354. 0x60,
  355. 0xd1,
  356. 0x0d,
  357. ];
  358. /**
  359. * Encryption table for the data
  360. *
  361. * @type array
  362. */
  363. private $data_encrypt_table = [
  364. 0x82,
  365. 0x8b,
  366. 0x7f,
  367. 0x68,
  368. 0x90,
  369. 0xe0,
  370. 0x44,
  371. 0x09,
  372. 0x19,
  373. 0x3b,
  374. 0x8e,
  375. 0x5f,
  376. 0xc2,
  377. 0x82,
  378. 0x38,
  379. 0x23,
  380. 0x6d,
  381. 0xdb,
  382. 0x62,
  383. 0x49,
  384. 0x52,
  385. 0x6e,
  386. 0x21,
  387. 0xdf,
  388. 0x51,
  389. 0x6c,
  390. 0x76,
  391. 0x37,
  392. 0x86,
  393. 0x50,
  394. 0x7d,
  395. 0x48,
  396. 0x1f,
  397. 0x65,
  398. 0xe7,
  399. 0x52,
  400. 0x6a,
  401. 0x88,
  402. 0xaa,
  403. 0xc1,
  404. 0x32,
  405. 0x2f,
  406. 0xf7,
  407. 0x54,
  408. 0x4c,
  409. 0xaa,
  410. 0x6d,
  411. 0x7e,
  412. 0x6d,
  413. 0xa9,
  414. 0x8c,
  415. 0x0d,
  416. 0x3f,
  417. 0xff,
  418. 0x6c,
  419. 0x09,
  420. 0xb3,
  421. 0xa5,
  422. 0xaf,
  423. 0xdf,
  424. 0x98,
  425. 0x02,
  426. 0xb4,
  427. 0xbe,
  428. 0x6d,
  429. 0x69,
  430. 0x0d,
  431. 0x42,
  432. 0x73,
  433. 0xe4,
  434. 0x34,
  435. 0x50,
  436. 0x07,
  437. 0x30,
  438. 0x79,
  439. 0x41,
  440. 0x2f,
  441. 0x08,
  442. 0x3f,
  443. 0x42,
  444. 0x73,
  445. 0xa7,
  446. 0x68,
  447. 0xfa,
  448. 0xee,
  449. 0x88,
  450. 0x0e,
  451. 0x6e,
  452. 0xa4,
  453. 0x70,
  454. 0x74,
  455. 0x22,
  456. 0x16,
  457. 0xae,
  458. 0x3c,
  459. 0x81,
  460. 0x14,
  461. 0xa1,
  462. 0xda,
  463. 0x7f,
  464. 0xd3,
  465. 0x7c,
  466. 0x48,
  467. 0x7d,
  468. 0x3f,
  469. 0x46,
  470. 0xfb,
  471. 0x6d,
  472. 0x92,
  473. 0x25,
  474. 0x17,
  475. 0x36,
  476. 0x26,
  477. 0xdb,
  478. 0xdf,
  479. 0x5a,
  480. 0x87,
  481. 0x91,
  482. 0x6f,
  483. 0xd6,
  484. 0xcd,
  485. 0xd4,
  486. 0xad,
  487. 0x4a,
  488. 0x29,
  489. 0xdd,
  490. 0x7d,
  491. 0x59,
  492. 0xbd,
  493. 0x15,
  494. 0x34,
  495. 0x53,
  496. 0xb1,
  497. 0xd8,
  498. 0x50,
  499. 0x11,
  500. 0x83,
  501. 0x79,
  502. 0x66,
  503. 0x21,
  504. 0x9e,
  505. 0x87,
  506. 0x5b,
  507. 0x24,
  508. 0x2f,
  509. 0x4f,
  510. 0xd7,
  511. 0x73,
  512. 0x34,
  513. 0xa2,
  514. 0xf7,
  515. 0x09,
  516. 0xd5,
  517. 0xd9,
  518. 0x42,
  519. 0x9d,
  520. 0xf8,
  521. 0x15,
  522. 0xdf,
  523. 0x0e,
  524. 0x10,
  525. 0xcc,
  526. 0x05,
  527. 0x04,
  528. 0x35,
  529. 0x81,
  530. 0xb2,
  531. 0xd5,
  532. 0x7a,
  533. 0xd2,
  534. 0xa0,
  535. 0xa5,
  536. 0x7b,
  537. 0xb8,
  538. 0x75,
  539. 0xd2,
  540. 0x35,
  541. 0x0b,
  542. 0x39,
  543. 0x8f,
  544. 0x1b,
  545. 0x44,
  546. 0x0e,
  547. 0xce,
  548. 0x66,
  549. 0x87,
  550. 0x1b,
  551. 0x64,
  552. 0xac,
  553. 0xe1,
  554. 0xca,
  555. 0x67,
  556. 0xb4,
  557. 0xce,
  558. 0x33,
  559. 0xdb,
  560. 0x89,
  561. 0xfe,
  562. 0xd8,
  563. 0x8e,
  564. 0xcd,
  565. 0x58,
  566. 0x92,
  567. 0x41,
  568. 0x50,
  569. 0x40,
  570. 0xcb,
  571. 0x08,
  572. 0xe1,
  573. 0x15,
  574. 0xee,
  575. 0xf4,
  576. 0x64,
  577. 0xfe,
  578. 0x1c,
  579. 0xee,
  580. 0x25,
  581. 0xe7,
  582. 0x21,
  583. 0xe6,
  584. 0x6c,
  585. 0xc6,
  586. 0xa6,
  587. 0x2e,
  588. 0x52,
  589. 0x23,
  590. 0xa7,
  591. 0x20,
  592. 0xd2,
  593. 0xd7,
  594. 0x28,
  595. 0x07,
  596. 0x23,
  597. 0x14,
  598. 0x24,
  599. 0x3d,
  600. 0x45,
  601. 0xa5,
  602. 0xc7,
  603. 0x90,
  604. 0xdb,
  605. 0x77,
  606. 0xdd,
  607. 0xea,
  608. 0x38,
  609. 0x59,
  610. 0x89,
  611. 0x32,
  612. 0xbc,
  613. 0x00,
  614. 0x3a,
  615. 0x6d,
  616. 0x61,
  617. 0x4e,
  618. 0xdb,
  619. 0x29,
  620. ];
  621. /**
  622. * Process the response
  623. *
  624. * @return array
  625. * @throws \GameQ\Exception\Protocol
  626. */
  627. public function processResponse()
  628. {
  629. // We need to decrypt the packets
  630. $decrypted = $this->decryptPackets($this->packets_response);
  631. // Now let us convert special characters from hex to ascii all at once
  632. $decrypted = preg_replace_callback(
  633. '|%([0-9A-F]{2})|',
  634. function ($matches) {
  635. // Pack this into ascii
  636. return pack('H*', $matches[1]);
  637. },
  638. $decrypted
  639. );
  640. // Explode into lines
  641. $lines = explode("\n", $decrypted);
  642. // Set the result to a new result instance
  643. $result = new Result();
  644. // Always dedicated
  645. $result->add('dedicated', 1);
  646. // Defaults
  647. $channelFields = 5;
  648. $playerFields = 7;
  649. // Iterate over the lines
  650. foreach ($lines as $line) {
  651. // Trim all the outlying space
  652. $line = trim($line);
  653. // We dont have anything in this line
  654. if (strlen($line) == 0) {
  655. continue;
  656. }
  657. /**
  658. * Everything is in this format: ITEM: VALUE
  659. *
  660. * Example:
  661. * ...
  662. * MAXCLIENTS: 175
  663. * VOICECODEC: 3,Speex
  664. * VOICEFORMAT: 31,32 KHz%2C 16 bit%2C 9 Qlty
  665. * UPTIME: 9167971
  666. * PLATFORM: Linux-i386
  667. * VERSION: 3.0.6
  668. * ...
  669. */
  670. // Check to see if we have a colon, every line should
  671. if (($colon_pos = strpos($line, ":")) !== false && $colon_pos > 0) {
  672. // Split the line into key/value pairs
  673. list($key, $value) = explode(':', $line, 2);
  674. // Lower the font of the key
  675. $key = strtolower($key);
  676. // Trim the value of extra space
  677. $value = trim($value);
  678. // Switch and offload items as needed
  679. switch ($key) {
  680. case 'client':
  681. $this->processPlayer($value, $playerFields, $result);
  682. break;
  683. case 'channel':
  684. $this->processChannel($value, $channelFields, $result);
  685. break;
  686. // Find the number of fields for the channels
  687. case 'channelfields':
  688. $channelFields = count(explode(',', $value));
  689. break;
  690. // Find the number of fields for the players
  691. case 'clientfields':
  692. $playerFields = count(explode(',', $value));
  693. break;
  694. // By default we just add they key as an item
  695. default:
  696. $result->add($key, utf8_encode($value));
  697. break;
  698. }
  699. }
  700. }
  701. unset($decrypted, $line, $lines, $colon_pos, $key, $value);
  702. return $result->fetch();
  703. }
  704. /*
  705. * Internal methods
  706. */
  707. /**
  708. * Decrypt the incoming packets
  709. *
  710. * @codeCoverageIgnore
  711. *
  712. * @param array $packets
  713. *
  714. * @return string
  715. * @throws \GameQ\Exception\Protocol
  716. */
  717. protected function decryptPackets(array $packets = [])
  718. {
  719. // This will be returned
  720. $decrypted = [];
  721. foreach ($packets as $packet) {
  722. # Header :
  723. $header = substr($packet, 0, 20);
  724. $header_items = [];
  725. $header_key = unpack("n1", $header);
  726. $key = array_shift($header_key);
  727. $chars = unpack("C*", substr($header, 2));
  728. $a1 = $key & 0xFF;
  729. $a2 = $key >> 8;
  730. if ($a1 == 0) {
  731. throw new Exception(__METHOD__ . ": Header key is invalid");
  732. }
  733. $table = $this->head_encrypt_table;
  734. $characterCount = count($chars);
  735. $key = 0;
  736. for ($index = 1; $index <= $characterCount; $index++) {
  737. $chars[$index] -= ($table[$a2] + (($index - 1) % 5)) & 0xFF;
  738. $a2 = ($a2 + $a1) & 0xFF;
  739. if (($index % 2) == 0) {
  740. $short_array = unpack("n1", pack("C2", $chars[$index - 1], $chars[$index]));
  741. $header_items[$key] = $short_array[1];
  742. ++$key;
  743. }
  744. }
  745. $header_items = array_combine([
  746. 'zero',
  747. 'cmd',
  748. 'id',
  749. 'totlen',
  750. 'len',
  751. 'totpck',
  752. 'pck',
  753. 'datakey',
  754. 'crc',
  755. ], $header_items);
  756. // Check to make sure the number of packets match
  757. if ($header_items['totpck'] != count($packets)) {
  758. throw new Exception(__METHOD__ . ": Too few packets received");
  759. }
  760. # Data :
  761. $table = $this->data_encrypt_table;
  762. $a1 = $header_items['datakey'] & 0xFF;
  763. $a2 = $header_items['datakey'] >> 8;
  764. if ($a1 == 0) {
  765. throw new Exception(__METHOD__ . ": Data key is invalid");
  766. }
  767. $chars = unpack("C*", substr($packet, 20));
  768. $data = "";
  769. $characterCount = count($chars);
  770. for ($index = 1; $index <= $characterCount; $index++) {
  771. $chars[$index] -= ($table[$a2] + (($index - 1) % 72)) & 0xFF;
  772. $a2 = ($a2 + $a1) & 0xFF;
  773. $data .= chr($chars[$index]);
  774. }
  775. //@todo: Check CRC ???
  776. $decrypted[$header_items['pck']] = $data;
  777. }
  778. // Return the decrypted packets as one string
  779. return implode('', $decrypted);
  780. }
  781. /**
  782. * Process the channel listing
  783. *
  784. * @param string $data
  785. * @param int $fieldCount
  786. * @param \GameQ\Result $result
  787. */
  788. protected function processChannel($data, $fieldCount, Result &$result)
  789. {
  790. // Split the items on the comma
  791. $items = explode(",", $data, $fieldCount);
  792. // Iterate over the items for this channel
  793. foreach ($items as $item) {
  794. // Split the key=value pair
  795. list($key, $value) = explode("=", $item, 2);
  796. $result->addTeam(strtolower($key), utf8_encode($value));
  797. }
  798. }
  799. /**
  800. * Process the user listing
  801. *
  802. * @param string $data
  803. * @param int $fieldCount
  804. * @param \GameQ\Result $result
  805. */
  806. protected function processPlayer($data, $fieldCount, Result &$result)
  807. {
  808. // Split the items on the comma
  809. $items = explode(",", $data, $fieldCount);
  810. // Iterate over the items for this player
  811. foreach ($items as $item) {
  812. // Split the key=value pair
  813. list($key, $value) = explode("=", $item, 2);
  814. $result->addPlayer(strtolower($key), utf8_encode($value));
  815. }
  816. }
  817. }