Gamespy2::parsePlayerTeam()   B
last analyzed

Complexity

Conditions 7
Paths 18

Size

Total Lines 37
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 17
c 1
b 0
f 0
dl 0
loc 37
ccs 18
cts 18
cp 1
rs 8.8333
cc 7
nc 18
nop 3
crap 7
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\Buffer;
22
use GameQ\Exception\Protocol as Exception;
23
use GameQ\Helpers\Str;
24
use GameQ\Protocol;
25
use GameQ\Result;
26
27
/**
28
 * GameSpy2 Protocol class
29
 *
30
 * Given the ability for non utf-8 characters to be used as hostnames, player names, etc... this
31
 * version returns all strings utf-8 encoded.  To access the proper version of a
32
 * string response you must use Str::utf8ToIso() on the specific response.
33
 *
34
 * @author Austin Bischoff <[email protected]>
35
 */
36
class Gamespy2 extends Protocol
37
{
38
    /**
39
     * Define the state of this class
40
     *
41
     * @var int
42
     */
43
    protected $state = self::STATE_BETA;
44
45
    /**
46
     * Array of packets we want to look up.
47
     * Each key should correspond to a defined method in this or a parent class
48
     *
49
     * @var array
50
     */
51
    protected $packets = [
52
        self::PACKET_DETAILS => "\xFE\xFD\x00\x43\x4F\x52\x59\xFF\x00\x00",
53
        self::PACKET_PLAYERS => "\xFE\xFD\x00\x43\x4F\x52\x58\x00\xFF\xFF",
54
    ];
55
56
    /**
57
     * Use the response flag to figure out what method to run
58
     *
59
     * @var array
60
     */
61
    protected $responses = [
62
        "\x00\x43\x4F\x52\x59" => "processDetails",
63
        "\x00\x43\x4F\x52\x58" => "processPlayers",
64
    ];
65
66
    /**
67
     * The query protocol used to make the call
68
     *
69
     * @var string
70
     */
71
    protected $protocol = 'gamespy2';
72
73
    /**
74
     * String name of this protocol class
75
     *
76
     * @var string
77
     */
78
    protected $name = 'gamespy2';
79
80
    /**
81
     * Longer string name of this protocol class
82
     *
83
     * @var string
84
     */
85
    protected $name_long = "GameSpy2 Server";
86
87
    /**
88
     * Normalize settings for this protocol
89
     *
90
     * @var array
91
     */
92
    protected $normalize = [
93
        // General
94
        'general' => [
95
            // target       => source
96
            'dedicated'  => 'dedicated',
97
            'gametype'   => 'gametype',
98
            'hostname'   => 'hostname',
99
            'mapname'    => 'mapname',
100
            'maxplayers' => 'maxplayers',
101
            'mod'        => 'mod',
102
            'numplayers' => 'numplayers',
103
            'password'   => 'password',
104
        ],
105
    ];
106
107
108
    /**
109
     * Process the response
110
     *
111
     * @return array
112
     * @throws Exception
113
     */
114 48
    public function processResponse()
115
    {
116
        // Will hold the packets after sorting
117 48
        $packets = [];
118
119
        // We need to pre-sort these for split packets so we can do extra work where needed
120 48
        foreach ($this->packets_response as $response) {
121 48
            $buffer = new Buffer($response);
122
123
            // Pull out the header
124 48
            $header = $buffer->read(5);
125
126
            // Add the packet to the proper section, we will combine later
127 48
            $packets[$header][] = $buffer->getBuffer();
128
        }
129
130 48
        unset($buffer);
131
132 48
        $results = [];
133
134
        // Now let's iterate and process
135 48
        foreach ($packets as $header => $packetGroup) {
136
            // Figure out which packet response this is
137 48
            if (!array_key_exists($header, $this->responses)) {
138
                throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
139
            }
140
141
            // Now we need to call the proper method
142 48
            $results = array_merge(
143 48
                $results,
144 48
                call_user_func_array([$this, $this->responses[$header]], [new Buffer(implode($packetGroup))])
145 48
            );
146
        }
147
148 48
        unset($packets);
149
150 48
        return $results;
151
    }
152
153
    // Internal methods
154
155
    /**
156
     * Handles processing the details data into a usable format
157
     *
158
     * @param \GameQ\Buffer $buffer
159
     *
160
     * @return array
161
     * @throws Exception
162
     * @throws \GameQ\Exception\Protocol
163
     */
164 48
    protected function processDetails(Buffer $buffer)
165
    {
166
        // Set the result to a new result instance
167 48
        $result = new Result();
168
169
        // We go until we hit an empty key
170 48
        while ($buffer->getLength()) {
171 48
            $key = $buffer->readString();
172 48
            if (strlen($key) == 0) {
173 48
                break;
174
            }
175 48
            $result->add($key, Str::isoToUtf8($buffer->readString()));
176
        }
177
178 48
        unset($buffer);
179
180 48
        return $result->fetch();
181
    }
182
183
    /**
184
     * Handles processing the players data into a usable format
185
     *
186
     * @param \GameQ\Buffer $buffer
187
     *
188
     * @return array
189
     * @throws Exception
190
     * @throws \GameQ\Exception\Protocol
191
     */
192 48
    protected function processPlayers(Buffer $buffer)
193
    {
194
        // Set the result to a new result instance
195 48
        $result = new Result();
196
197
        // Skip the header
198 48
        $buffer->skip(1);
199
200
        // Players are first
201 48
        $this->parsePlayerTeam('players', $buffer, $result);
202
203
        // Teams are next
204 48
        $this->parsePlayerTeam('teams', $buffer, $result);
205
206 48
        unset($buffer);
207
208 48
        return $result->fetch();
209
    }
210
211
    /**
212
     * Parse the player/team info returned from the player call
213
     *
214
     * @param string        $dataType
215
     * @param \GameQ\Buffer $buffer
216
     * @param \GameQ\Result $result
217
     *
218
     * @throws Exception
219
     * @throws \GameQ\Exception\Protocol
220
     */
221 48
    protected function parsePlayerTeam($dataType, Buffer &$buffer, Result &$result)
222
    {
223
        // Do count
224 48
        $result->add('num_' . $dataType, $buffer->readInt8());
225
226
        // Variable names
227 48
        $varNames = [];
228
229
        // Loop until we run out of length
230 48
        while ($buffer->getLength()) {
231 48
            $varNames[] = str_replace('_', '', $buffer->readString());
232
233 48
            if ($buffer->lookAhead() === "\x00") {
234 48
                $buffer->skip();
235 48
                break;
236
            }
237
        }
238
239
        // Check if there are any value entries
240 48
        if ($buffer->lookAhead() == "\x00") {
241 12
            $buffer->skip();
242
243 12
            return;
244
        }
245
246
        // Get the values
247 48
        while ($buffer->getLength() > 4) {
248 42
            foreach ($varNames as $varName) {
249 42
                $result->addSub($dataType, Str::isoToUtf8($varName), Str::isoToUtf8($buffer->readString()));
250
            }
251 42
            if ($buffer->lookAhead() === "\x00") {
252 36
                $buffer->skip();
253 36
                break;
254
            }
255
        }
256
257 48
        return;
258
    }
259
}
260