Passed
Push — v3 ( efc739...bb78f7 )
by Austin
02:17
created

Gamespy2::processResponse()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 38
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 4.0032

Importance

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