Passed
Push — v3 ( e623f3...bf0201 )
by
unknown
02:33
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
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
        }
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 48
            );
148
        }
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
     * @throws \GameQ\Exception\Protocol
167
     */
168 48
    protected function processDetails(Buffer $buffer)
169
    {
170
171
        // Set the result to a new result instance
172 48
        $result = new Result();
173
174
        // We go until we hit an empty key
175 48
        while ($buffer->getLength()) {
176 48
            $key = $buffer->readString();
177 48
            if (strlen($key) == 0) {
178 48
                break;
179
            }
180 48
            $result->add($key, Str::isoToUtf8($buffer->readString()));
181
        }
182
183 48
        unset($buffer);
184
185 48
        return $result->fetch();
186
    }
187
188
    /**
189
     * Handles processing the players data into a usable format
190
     *
191
     * @param \GameQ\Buffer $buffer
192
     *
193
     * @return array
194
     * @throws Exception
195
     * @throws \GameQ\Exception\Protocol
196
     */
197 48
    protected function processPlayers(Buffer $buffer)
198
    {
199
200
        // Set the result to a new result instance
201 48
        $result = new Result();
202
203
        // Skip the header
204 48
        $buffer->skip(1);
205
206
        // Players are first
207 48
        $this->parsePlayerTeam('players', $buffer, $result);
208
209
        // Teams are next
210 48
        $this->parsePlayerTeam('teams', $buffer, $result);
211
212 48
        unset($buffer);
213
214 48
        return $result->fetch();
215
    }
216
217
    /**
218
     * Parse the player/team info returned from the player call
219
     *
220
     * @param string        $dataType
221
     * @param \GameQ\Buffer $buffer
222
     * @param \GameQ\Result $result
223
     *
224
     * @throws Exception
225
     * @throws \GameQ\Exception\Protocol
226
     */
227 48
    protected function parsePlayerTeam($dataType, Buffer &$buffer, Result &$result)
228
    {
229
230
        // Do count
231 48
        $result->add('num_' . $dataType, $buffer->readInt8());
232
233
        // Variable names
234 48
        $varNames = [];
235
236
        // Loop until we run out of length
237 48
        while ($buffer->getLength()) {
238 48
            $varNames[] = str_replace('_', '', $buffer->readString());
239
240 48
            if ($buffer->lookAhead() === "\x00") {
241 48
                $buffer->skip();
242 48
                break;
243
            }
244
        }
245
246
        // Check if there are any value entries
247 48
        if ($buffer->lookAhead() == "\x00") {
248 12
            $buffer->skip();
249
250 12
            return;
251
        }
252
253
        // Get the values
254 48
        while ($buffer->getLength() > 4) {
255 42
            foreach ($varNames as $varName) {
256 42
                $result->addSub($dataType, Str::isoToUtf8($varName), Str::isoToUtf8($buffer->readString()));
257
            }
258 42
            if ($buffer->lookAhead() === "\x00") {
259 36
                $buffer->skip();
260 36
                break;
261
            }
262
        }
263
264 48
        return;
265
    }
266
}
267