Passed
Push — v3 ( 962004...bdfc5f )
by Austin
06:52
created

Bf3::decode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 18
ccs 7
cts 7
cp 1
rs 10
cc 2
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 3 Protocol Class
28
 *
29
 * Good place for doc status and info is http://www.fpsadmin.com/forum/showthread.php?t=24134
30
 *
31
 * @package GameQ\Protocols
32
 * @author  Austin Bischoff <[email protected]>
33
 */
34
class Bf3 extends Protocol
35
{
36
37
    /**
38
     * Array of packets we want to query.
39
     *
40
     * @type array
41
     */
42
    protected $packets = [
43
        self::PACKET_STATUS  => "\x00\x00\x00\x21\x1b\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x00\x00serverInfo\x00",
44
        self::PACKET_VERSION => "\x00\x00\x00\x22\x18\x00\x00\x00\x01\x00\x00\x00\x07\x00\x00\x00version\x00",
45
        self::PACKET_PLAYERS =>
46
            "\x00\x00\x00\x23\x24\x00\x00\x00\x02\x00\x00\x00\x0b\x00\x00\x00listPlayers\x00\x03\x00\x00\x00\x61ll\x00",
47
    ];
48
49
    /**
50
     * Use the response flag to figure out what method to run
51
     *
52
     * @type array
53
     */
54
    protected $responses = [
55
        1627389952 => "processDetails", // a
56
        1644167168 => "processVersion", // b
57
        1660944384 => "processPlayers", // c
58
    ];
59
60
    /**
61
     * The transport mode for this protocol is TCP
62
     *
63
     * @type string
64
     */
65
    protected $transport = self::TRANSPORT_TCP;
66
67
    /**
68
     * The query protocol used to make the call
69
     *
70
     * @type string
71
     */
72
    protected $protocol = 'bf3';
73
74
    /**
75
     * String name of this protocol class
76
     *
77
     * @type string
78
     */
79
    protected $name = 'bf3';
80
81
    /**
82
     * Longer string name of this protocol class
83
     *
84
     * @type string
85
     */
86
    protected $name_long = "Battlefield 3";
87
88
    /**
89
     * The client join link
90
     *
91
     * @type string
92
     */
93
    protected $join_link = null;
94
95
    /**
96
     * query_port = client_port + 22000
97
     * 47200 = 25200 + 22000
98
     *
99
     * @type int
100
     */
101
    protected $port_diff = 22000;
102
103
    /**
104
     * Normalize settings for this protocol
105
     *
106
     * @type array
107
     */
108
    protected $normalize = [
109
        // General
110
        'general' => [
111
            // target       => source
112
            'dedicated'  => 'dedicated',
113
            'hostname'   => 'hostname',
114
            'mapname'    => 'map',
115
            'maxplayers' => 'max_players',
116
            'numplayers' => 'num_players',
117
            'password'   => 'password',
118
        ],
119
        'player'  => [
120
            'name'  => 'name',
121
            'score' => 'score',
122
            'ping'  => 'ping',
123
        ],
124
        'team'    => [
125
            'score' => 'tickets',
126
        ],
127
    ];
128
129
    /**
130
     * Process the response for the StarMade server
131
     *
132
     * @return array
133
     * @throws \GameQ\Exception\Protocol
134
     */
135 8
    public function processResponse()
136
    {
137
138
        // Holds the results sent back
139 8
        $results = [];
140
141
        // Holds the processed packets after having been reassembled
142 8
        $processed = [];
143
144
        // Start up the index for the processed
145 8
        $sequence_id_last = 0;
146
147 8
        foreach ($this->packets_response as $packet) {
148
            // Create a new buffer
149 8
            $buffer = new Buffer($packet);
150
151
            // Each "good" packet begins with sequence_id (32-bit)
152 8
            $sequence_id = $buffer->readInt32();
153
154
            // Sequence id is a response
155 8
            if (array_key_exists($sequence_id, $this->responses)) {
156 8
                $processed[$sequence_id] = $buffer->getBuffer();
157 8
                $sequence_id_last = $sequence_id;
158
            } else {
159
                // This is a continuation of the previous packet, reset the buffer and append
160 7
                $buffer->jumpto(0);
161
162
                // Append
163 8
                $processed[$sequence_id_last] .= $buffer->getBuffer();
164
            }
165
        }
166
167 8
        unset($buffer, $sequence_id_last, $sequence_id);
168
169
        // Iterate over the combined packets and do some work
170 8
        foreach ($processed as $sequence_id => $data) {
171
            // Create a new buffer
172 8
            $buffer = new Buffer($data);
173
174
            // Get the length of the packet
175 8
            $packetLength = $buffer->getLength();
176
177
            // Check to make sure the expected length matches the real length
178
            // Subtract 4 for the sequence_id pulled out earlier
179 8
            if ($packetLength != ($buffer->readInt32() - 4)) {
180 1
                throw new Exception(__METHOD__ . " packet length does not match expected length!");
181
            }
182
183
            // Now we need to call the proper method
184 7
            $results = array_merge(
185 7
                $results,
186 7
                call_user_func_array([$this, $this->responses[$sequence_id]], [$buffer])
187
            );
188
        }
189
190 7
        return $results;
191
    }
192
193
    /*
194
     * Internal Methods
195
     */
196
197
    /**
198
     * Decode the buffer into a usable format
199
     *
200
     * @param \GameQ\Buffer $buffer
201
     *
202
     * @return array
203
     */
204 7
    protected function decode(Buffer $buffer)
205
    {
206
207 7
        $items = [];
208
209
        // Get the number of words in this buffer
210 7
        $itemCount = $buffer->readInt32();
211
212
        // Loop over the number of items
213 7
        for ($i = 0; $i < $itemCount; $i++) {
214
            // Length of the string
215 7
            $buffer->readInt32();
216
217
            // Just read the string
218 7
            $items[$i] = $buffer->readString();
219
        }
220
221 7
        return $items;
222
    }
223
224
    /**
225
     * Process the server details
226
     *
227
     * @param \GameQ\Buffer $buffer
228
     *
229
     * @return array
230
     */
231 3
    protected function processDetails(Buffer $buffer)
232
    {
233
234
        // Decode into items
235 3
        $items = $this->decode($buffer);
236
237
        // Set the result to a new result instance
238 3
        $result = new Result();
239
240
        // Server is always dedicated
241 3
        $result->add('dedicated', 1);
242
243
        // These are the same no matter what mode the server is in
244 3
        $result->add('hostname', $items[1]);
245 3
        $result->add('num_players', (int)$items[2]);
246 3
        $result->add('max_players', (int)$items[3]);
247 3
        $result->add('gametype', $items[4]);
248 3
        $result->add('map', $items[5]);
249 3
        $result->add('roundsplayed', (int)$items[6]);
250 3
        $result->add('roundstotal', (int)$items[7]);
251 3
        $result->add('num_teams', (int)$items[8]);
252
253
        // Set the current index
254 3
        $index_current = 9;
255
256
        // Pull the team count
257 3
        $teamCount = $result->get('num_teams');
258
259
        // Loop for the number of teams found, increment along the way
260 3
        for ($id = 1; $id <= $teamCount; $id++, $index_current++) {
261
            // Shows the tickets
262 3
            $result->addTeam('tickets', $items[$index_current]);
263
            // We add an id so we know which team this is
264 3
            $result->addTeam('id', $id);
265
        }
266
267
        // Get and set the rest of the data points.
268 3
        $result->add('targetscore', (int)$items[$index_current]);
269 3
        $result->add('online', 1); // Forced true, it seems $words[$index_current + 1] is always empty
270 3
        $result->add('ranked', (int)$items[$index_current + 2]);
271 3
        $result->add('punkbuster', (int)$items[$index_current + 3]);
272 3
        $result->add('password', (int)$items[$index_current + 4]);
273 3
        $result->add('uptime', (int)$items[$index_current + 5]);
274 3
        $result->add('roundtime', (int)$items[$index_current + 6]);
275
        // Added in R9
276 3
        $result->add('ip_port', $items[$index_current + 7]);
277 3
        $result->add('punkbuster_version', $items[$index_current + 8]);
278 3
        $result->add('join_queue', (int)$items[$index_current + 9]);
279 3
        $result->add('region', $items[$index_current + 10]);
280 3
        $result->add('pingsite', $items[$index_current + 11]);
281 3
        $result->add('country', $items[$index_current + 12]);
282
        // Added in R29, No docs as of yet
283 3
        $result->add('quickmatch', (int)$items[$index_current + 13]); // Guessed from research
284
285 3
        unset($items, $index_current, $teamCount, $buffer);
286
287 3
        return $result->fetch();
288
    }
289
290
    /**
291
     * Process the server version
292
     *
293
     * @param \GameQ\Buffer $buffer
294
     *
295
     * @return array
296
     */
297 7
    protected function processVersion(Buffer $buffer)
298
    {
299
300
        // Decode into items
301 7
        $items = $this->decode($buffer);
302
303
        // Set the result to a new result instance
304 7
        $result = new Result();
305
306 7
        $result->add('version', $items[2]);
307
308 7
        unset($buffer, $items);
309
310 7
        return $result->fetch();
311
    }
312
313
    /**
314
     * Process the players
315
     *
316
     * @param \GameQ\Buffer $buffer
317
     *
318
     * @return array
319
     */
320 7
    protected function processPlayers(Buffer $buffer)
321
    {
322
323
        // Decode into items
324 7
        $items = $this->decode($buffer);
325
326
        // Set the result to a new result instance
327 7
        $result = new Result();
328
329
        // Number of data points per player
330 7
        $numTags = $items[1];
331
332
        // Grab the tags for each player
333 7
        $tags = array_slice($items, 2, $numTags);
334
335
        // Get the player count
336 7
        $playerCount = $items[$numTags + 2];
337
338
        // Iterate over the index until we run out of players
339 7
        for ($i = 0, $x = $numTags + 3; $i < $playerCount; $i++, $x += $numTags) {
340
            // Loop over the player tags and extract the info for that tag
341 6
            foreach ($tags as $index => $tag) {
342 6
                $result->addPlayer($tag, $items[($x + $index)]);
343
            }
344
        }
345
346 7
        return $result->fetch();
347
    }
348
}
349