Cs2d   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 227
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 13
eloc 78
c 1
b 0
f 0
dl 0
loc 227
ccs 57
cts 57
cp 1
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A readFlag() 0 3 1
A processDetails() 0 28 2
A processPlayers() 0 24 3
B processResponse() 0 57 7
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\Buffer;
22
use GameQ\Exception\Protocol as Exception;
23
use GameQ\Helpers\Str;
24
use GameQ\Protocol;
25
use GameQ\Result;
26
27
/**
28
 * Counter-Strike 2d Protocol Class
29
 *
30
 * Note:
31
 * Unable to make player information calls work as the protocol does not like parallel requests
32
 *
33
 * @author  Austin Bischoff <[email protected]>
34
 */
35
class Cs2d extends Protocol
36
{
37
    /**
38
     * Array of packets we want to query.
39
     *
40
     * @var array
41
     */
42
    protected $packets = [
43
        self::PACKET_STATUS  => "\x01\x00\xFB\x01",
44
        //self::PACKET_STATUS => "\x01\x00\x03\x10\x21\xFB\x01\x75\x00",
45
        self::PACKET_PLAYERS => "\x01\x00\xFB\x05",
46
    ];
47
48
    /**
49
     * Use the response flag to figure out what method to run
50
     *
51
     * @var array
52
     */
53
    protected $responses = [
54
        "\x01\x00\xFB\x01" => "processDetails",
55
        "\x01\x00\xFB\x05" => "processPlayers",
56
    ];
57
58
    /**
59
     * The query protocol used to make the call
60
     *
61
     * @var string
62
     */
63
    protected $protocol = 'cs2d';
64
65
    /**
66
     * String name of this protocol class
67
     *
68
     * @var string
69
     */
70
    protected $name = 'cs2d';
71
72
    /**
73
     * Longer string name of this protocol class
74
     *
75
     * @var string
76
     */
77
    protected $name_long = "Counter-Strike 2d";
78
79
    /**
80
     * The client join link
81
     *
82
     * @var string
83
     */
84
    protected $join_link = "cs2d://%s:%d/";
85
86
    /**
87
     * Normalize settings for this protocol
88
     *
89
     * @var array
90
     */
91
    protected $normalize = [
92
        // General
93
        'general' => [
94
            // target       => source
95
            'dedicated'  => 'dedicated',
96
            'gametype'   => 'game_mode',
97
            'hostname'   => 'hostname',
98
            'mapname'    => 'mapname',
99
            'maxplayers' => 'max_players',
100
            'mod'        => 'game_dir',
101
            'numplayers' => 'num_players',
102
            'password'   => 'password',
103
        ],
104
        // Individual
105
        'player'  => [
106
            'name'   => 'name',
107
            'deaths' => 'deaths',
108
            'score'  => 'score',
109
        ],
110
    ];
111
112
    /**
113
     * Process the response for the Tibia server
114
     *
115
     * @return array
116
     * @throws \GameQ\Exception\Protocol
117
     */
118 24
    public function processResponse()
119
    {
120
        // We have a merged packet, try to split it back up
121 24
        if (count($this->packets_response) == 1) {
122
            // Temp buffer to make string manipulation easier
123 6
            $buffer = new Buffer($this->packets_response[0]);
124
125
            // Grab the header and set the packet we need to split with
126 6
            $packet = (($buffer->lookAhead(4) === $this->packets[self::PACKET_PLAYERS]) ?
127 6
                self::PACKET_STATUS : self::PACKET_PLAYERS);
128
129
            // Explode the merged packet as the response
130 6
            $responses = explode(substr($this->packets[$packet], 2), $buffer->getData());
131
132
            // Try to rebuild the second packet to the same as if it was sent as two separate responses
133 6
            $responses[1] = $this->packets[$packet] . ((count($responses) === 2) ? $responses[1] : "");
134
135 6
            unset($buffer);
136
        } else {
137 18
            $responses = $this->packets_response;
138
        }
139
140
        // Will hold the packets after sorting
141 24
        $packets = [];
142
143
        // We need to pre-sort these for split packets so we can do extra work where needed
144 24
        foreach ($responses as $response) {
145 24
            $buffer = new Buffer($response);
146
147
            // Pull out the header
148 24
            $header = $buffer->read(4);
149
150
            // Add the packet to the proper section, we will combine later
151 24
            $packets[$header][] = $buffer->getBuffer();
152
        }
153
154 24
        unset($buffer);
155
156 24
        $results = [];
157
158
        // Now let's iterate and process
159 24
        foreach ($packets as $header => $packetGroup) {
160
            // Figure out which packet response this is
161 24
            if (!array_key_exists($header, $this->responses)) {
162 12
                throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
163
            }
164
165
            // Now we need to call the proper method
166 12
            $results = array_merge(
167 12
                $results,
168 12
                call_user_func_array([$this, $this->responses[$header]], [new Buffer(implode($packetGroup))])
169 12
            );
170
        }
171
172 12
        unset($packets);
173
174 12
        return $results;
175
    }
176
177
    /**
178
     * Handles processing the details data into a usable format
179
     *
180
     * @param Buffer $buffer
181
     *
182
     * @return array
183
     * @throws \Exception
184
     * @throws \GameQ\Exception\Protocol
185
     */
186 12
    protected function processDetails(Buffer $buffer)
187
    {
188
        // Set the result to a new result instance
189 12
        $result = new Result();
190
191
        // First int is the server flags
192 12
        $serverFlags = $buffer->readInt8();
193
194
        // Read server flags
195 12
        $result->add('password', (int)$this->readFlag($serverFlags, 0));
196 12
        $result->add('registered_only', (int)$this->readFlag($serverFlags, 1));
197 12
        $result->add('fog_of_war', (int)$this->readFlag($serverFlags, 2));
198 12
        $result->add('friendly_fire', (int)$this->readFlag($serverFlags, 3));
199 12
        $result->add('bots_enabled', (int)$this->readFlag($serverFlags, 5));
200 12
        $result->add('lua_scripts', (int)$this->readFlag($serverFlags, 6));
201
202
        // Read the rest of the buffer data
203 12
        $result->add('servername', Str::isoToUtf8($buffer->readPascalString(0)));
204 12
        $result->add('mapname', Str::isoToUtf8($buffer->readPascalString(0)));
205 12
        $result->add('num_players', $buffer->readInt8());
206 12
        $result->add('max_players', $buffer->readInt8());
207 12
        $result->add('game_mode', $buffer->readInt8());
208 12
        $result->add('num_bots', (($this->readFlag($serverFlags, 5)) ? $buffer->readInt8() : 0));
209 12
        $result->add('dedicated', 1);
210
211 12
        unset($buffer);
212
213 12
        return $result->fetch();
214
    }
215
216
    /**
217
     * Handles processing the player data into a usable format
218
     *
219
     * @param Buffer $buffer
220
     *
221
     * @return array
222
     * @throws \Exception
223
     * @throws \GameQ\Exception\Protocol
224
     */
225 12
    protected function processPlayers(Buffer $buffer)
226
    {
227
        // Set the result to a new result instance
228 12
        $result = new Result();
229
230
        // First entry is the number of players in this list.  Don't care
231 12
        $buffer->read();
232
233
        // Parse players
234 12
        while ($buffer->getLength()) {
235
            // Player id
236 6
            if (($id = $buffer->readInt8()) !== 0) {
237
                // Add the results
238 6
                $result->addPlayer('id', $id);
239 6
                $result->addPlayer('name', Str::isoToUtf8($buffer->readPascalString(0)));
240 6
                $result->addPlayer('team', $buffer->readInt8());
241 6
                $result->addPlayer('score', $buffer->readInt32());
242 6
                $result->addPlayer('deaths', $buffer->readInt32());
243
            }
244
        }
245
246 12
        unset($buffer, $id);
247
248 12
        return $result->fetch();
249
    }
250
251
    /**
252
     * Read flags from stored value
253
     *
254
     * @param $flags
255
     * @param $offset
256
     *
257
     * @return bool
258
     */
259 12
    protected function readFlag($flags, $offset)
260
    {
261 12
        return !!($flags & (1 << $offset));
262
    }
263
}
264