Ventrilo   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 831
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 21
eloc 628
c 2
b 0
f 0
dl 0
loc 831
ccs 48
cts 48
cp 1
rs 9.972

4 Methods

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