Passed
Pull Request — v3 (#729)
by
unknown
05:18 queued 02:43
created

Gamespy2::processResponse()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 38
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 4.0023

Importance

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