Completed
Push — v3 ( 339589...25b7b0 )
by Austin
07:32
created

Bfbc2::decode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 19
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 19
ccs 7
cts 7
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 7
nc 2
nop 1
crap 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\Protocol;
22
use GameQ\Buffer;
23
use GameQ\Result;
24
use GameQ\Exception\Protocol as Exception;
25
26
/**
27
 * Battlefield Bad Company 2 Protocol Class
28
 *
29
 * NOTE:  There are no qualifiers to the response packets sent back from the server as to which response packet
30
 * belongs to which query request.  For now this class assumes the responses are in the same order as the order in
31
 * which the packets were sent to the server.  If this assumption turns out to be wrong there is easy way to tell which
32
 * response belongs to which query.  Hopefully this assumption will hold true as it has in my testing.
33
 *
34
 * @package GameQ\Protocols
35
 * @author  Austin Bischoff <[email protected]>
36
 */
37
class Bfbc2 extends Protocol
38
{
39
40
    /**
41
     * Array of packets we want to query.
42
     *
43
     * @type array
44
     */
45
    protected $packets = [
46
        self::PACKET_VERSION => "\x00\x00\x00\x00\x18\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00version\x00",
47
        self::PACKET_STATUS  => "\x00\x00\x00\x00\x1b\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x00\x00serverInfo\x00",
48
        self::PACKET_PLAYERS => "\x00\x00\x00\x00\x24\x00\x00\x00\x02\x00\x00\x00\x0b\x00\x00\x00listPlayers\x00\x03\x00\x00\x00\x61ll\x00",
49
    ];
50
51
    /**
52
     * Use the response flag to figure out what method to run
53
     *
54
     * @type array
55
     */
56
    protected $responses = [
57
        "processVersion",
58
        "processDetails",
59
        "processPlayers",
60
    ];
61
62
    /**
63
     * The transport mode for this protocol is TCP
64
     *
65
     * @type string
66
     */
67
    protected $transport = self::TRANSPORT_TCP;
68
69
    /**
70
     * The query protocol used to make the call
71
     *
72
     * @type string
73
     */
74
    protected $protocol = 'bfbc2';
75
76
    /**
77
     * String name of this protocol class
78
     *
79
     * @type string
80
     */
81
    protected $name = 'bfbc2';
82
83
    /**
84
     * Longer string name of this protocol class
85
     *
86
     * @type string
87
     */
88
    protected $name_long = "Battlefield Bad Company 2";
89
90
    /**
91
     * The client join link
92
     *
93
     * @type string
94
     */
95
    protected $join_link = null;
96
97
    /**
98
     * query_port = client_port + 29321
99
     * 48888 = 19567 + 29321
100
     *
101
     * @type int
102
     */
103
    protected $port_diff = 29321;
104
105
    /**
106
     * Normalize settings for this protocol
107
     *
108
     * @type array
109
     */
110
    protected $normalize = [
111
        // General
112
        'general' => [
113
            // target       => source
114
            'dedicated'  => 'dedicated',
115
            'hostname'   => 'hostname',
116
            'mapname'    => 'map',
117
            'maxplayers' => 'max_players',
118
            'numplayers' => 'num_players',
119
            'password'   => 'password',
120
        ],
121
        'player'  => [
122
            'name'  => 'name',
123
            'score' => 'score',
124
            'ping'  => 'ping',
125
        ],
126
        'team'    => [
127
            'score' => 'tickets',
128
        ],
129
    ];
130
131
    /**
132
     * Process the response for the StarMade server
133
     *
134
     * @return array
135
     * @throws \GameQ\Exception\Protocol
136
     */
137 4
    public function processResponse()
138
    {
139
140
        //print_r($this->packets_response);
141
142
        // Holds the results sent back
143 4
        $results = [];
144
145
        // Iterate over the response packets
146
        // @todo: This protocol has no packet ordering, ids or anyway to identify which packet coming back belongs to which initial call.
147 4
        foreach ($this->packets_response as $i => $packet) {
148
            // Create a new buffer
149 4
            $buffer = new Buffer($packet);
150
151
            // Burn first 4 bytes, same across all packets
152 4
            $buffer->skip(4);
153
154
            // Get the packet length
155 4
            $packetLength = $buffer->getLength();
156
157
            // Check to make sure the expected length matches the real length
158
            // Subtract 4 for the header burn
159 4
            if ($packetLength != ($buffer->readInt32() - 4)) {
160 1
                throw new Exception(__METHOD__ . " packet length does not match expected length!");
161
            }
162
163
            // We assume the packets are coming back in the same order as sent, this maybe incorrect...
164 3
            $results = array_merge(
165
                $results,
166 3
                call_user_func_array([$this, $this->responses[$i]], [$buffer])
167
            );
168
        }
169
170 3
        unset($buffer, $packetLength);
171
172 3
        return $results;
173
    }
174
175
    /*
176
     * Internal Methods
177
     */
178
179
    /**
180
     * Decode the buffer into a usable format
181
     *
182
     * @param \GameQ\Buffer $buffer
183
     *
184
     * @return array
185
     */
186 3
    protected function decode(Buffer $buffer)
187
    {
188
189 3
        $items = [];
190
191
        // Get the number of words in this buffer
192 3
        $itemCount = $buffer->readInt32();
193
194
        // Loop over the number of items
195 3
        for ($i = 0; $i < $itemCount; $i++) {
196
            // Length of the string
197 3
            $buffer->readInt32();
198
199
            // Just read the string
200 3
            $items[$i] = $buffer->readString();
201
        }
202
203 3
        return $items;
204
    }
205
206
    /**
207
     * Process the server details
208
     *
209
     * @param \GameQ\Buffer $buffer
210
     *
211
     * @return array
212
     */
213 3
    protected function processDetails(Buffer $buffer)
0 ignored issues
show
Complexity introduced by
This operation has 1250 execution paths which exceeds the configured maximum of 200.

A high number of execution paths generally suggests many nested conditional statements and make the code less readible. This can usually be fixed by splitting the method into several smaller methods.

You can also find more information in the “Code” section of your repository.

Loading history...
214
    {
215
216
        // Decode into items
217 3
        $items = $this->decode($buffer);
218
219
        // Set the result to a new result instance
220 3
        $result = new Result();
221
222
        // Server is always dedicated
223 3
        $result->add('dedicated', 1);
224
225
        // These are the same no matter what mode the server is in
226 3
        $result->add('hostname', $items[1]);
227 3
        $result->add('num_players', (int)$items[2]);
228 3
        $result->add('max_players', (int)$items[3]);
229 3
        $result->add('gametype', $items[4]);
230 3
        $result->add('map', $items[5]);
231 3
        $result->add('roundsplayed', (int)$items[6]);
232 3
        $result->add('roundstotal', (int)$items[7]);
233 3
        $result->add('num_teams', (int)$items[8]);
234
235
        // Set the current index
236 3
        $index_current = 9;
237
238
        // Pull the team count
239 3
        $teamCount = $result->get('num_teams');
240
241
        // Loop for the number of teams found, increment along the way
242 3
        for ($id = 1; $id <= $teamCount; $id++, $index_current++) {
243
            // Shows the tickets
244 3
            $result->addTeam('tickets', $items[$index_current]);
245
            // We add an id so we know which team this is
246 3
            $result->addTeam('id', $id);
247
        }
248
249
        // Get and set the rest of the data points.
250 3
        $result->add('targetscore', (int)$items[$index_current]);
251 3
        $result->add('online', 1); // Forced true, shows accepting players
252 3
        $result->add('ranked', (($items[$index_current + 2] == 'true') ? 1 : 0));
253 3
        $result->add('punkbuster', (($items[$index_current + 3] == 'true') ? 1 : 0));
254 3
        $result->add('password', (($items[$index_current + 4] == 'true') ? 1 : 0));
255 3
        $result->add('uptime', (int)$items[$index_current + 5]);
256 3
        $result->add('roundtime', (int)$items[$index_current + 6]);
257 3
        $result->add('mod', $items[$index_current + 7]);
258
259 3
        $result->add('ip_port', $items[$index_current + 9]);
260 3
        $result->add('punkbuster_version', $items[$index_current + 10]);
261 3
        $result->add('join_queue', (($items[$index_current + 11] == 'true') ? 1 : 0));
262 3
        $result->add('region', $items[$index_current + 12]);
263
264 3
        unset($items, $index_current, $teamCount, $buffer);
265
266 3
        return $result->fetch();
267
    }
268
269
    /**
270
     * Process the server version
271
     *
272
     * @param \GameQ\Buffer $buffer
273
     *
274
     * @return array
275
     */
276 3
    protected function processVersion(Buffer $buffer)
277
    {
278
        // Decode into items
279 3
        $items = $this->decode($buffer);
280
281
        // Set the result to a new result instance
282 3
        $result = new Result();
283
284 3
        $result->add('version', $items[2]);
285
286 3
        unset($buffer, $items);
287
288 3
        return $result->fetch();
289
    }
290
291
    /**
292
     * Process the players
293
     *
294
     * @param \GameQ\Buffer $buffer
295
     *
296
     * @return array
297
     */
298 3
    protected function processPlayers(Buffer $buffer)
299
    {
300
301
        // Decode into items
302 3
        $items = $this->decode($buffer);
303
304
        // Set the result to a new result instance
305 3
        $result = new Result();
306
307
        // Number of data points per player
308 3
        $numTags = $items[1];
309
310
        // Grab the tags for each player
311 3
        $tags = array_slice($items, 2, $numTags);
312
313
        // Get the player count
314 3
        $playerCount = $items[$numTags + 2];
315
316
        // Iterate over the index until we run out of players
317 3
        for ($i = 0, $x = $numTags + 3; $i < $playerCount; $i++, $x += $numTags) {
318
            // Loop over the player tags and extract the info for that tag
319 2
            foreach ($tags as $index => $tag) {
320 2
                $result->addPlayer($tag, $items[($x + $index)]);
321
            }
322
        }
323
324 3
        return $result->fetch();
325
    }
326
}
327