Completed
Push — v3 ( 945d13...a8fc42 )
by Austin
04:11
created

Cs2d::processDetails()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 0
cts 18
cp 0
rs 9.456
c 0
b 0
f 0
cc 2
nc 1
nop 1
crap 6
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
 * Counter-Strike 2d Protocol Class
28
 *
29
 * Note:
30
 * Unable to make player information calls work as the protocol does not like parallel requests
31
 *
32
 * @author  Austin Bischoff <[email protected]>
33
 */
34
class Cs2d 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  => "\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
     * @type 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
     * @type string
62
     */
63
    protected $protocol = 'cs2d';
64
65
    /**
66
     * String name of this protocol class
67
     *
68
     * @type string
69
     */
70
    protected $name = 'cs2d';
71
72
    /**
73
     * Longer string name of this protocol class
74
     *
75
     * @type string
76
     */
77
    protected $name_long = "Counter-Strike 2d";
78
79
    /**
80
     * The client join link
81
     *
82
     * @type string
83
     */
84
    protected $join_link = "cs2d://%s:%d/";
85
86
    /**
87
     * Normalize settings for this protocol
88
     *
89
     * @type 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 2
    public function processResponse()
119
    {
120
121
        // We have a merged packet, try to split it back up
122 2
        if (count($this->packets_response) == 1) {
123
            // Temp buffer to make string manipulation easier
124
            $buffer = new Buffer($this->packets_response[0]);
125
126
            // Grab the header and set the packet we need to split with
127
            $packet = (($buffer->lookAhead(4) === $this->packets[self::PACKET_PLAYERS]) ?
128
                self::PACKET_STATUS : self::PACKET_PLAYERS);
129
130
            // Explode the merged packet as the response
131
            $responses = explode(substr($this->packets[$packet], 2), $buffer->getData());
132
133
            // Try to rebuild the second packet to the same as if it was sent as two separate responses
134
            $responses[1] = $this->packets[$packet] . ((count($responses) === 2) ? $responses[1] : "");
135
136
            unset($buffer);
137
        } else {
138 2
            $responses = $this->packets_response;
139
        }
140
141
        // Will hold the packets after sorting
142 2
        $packets = [];
143
144
        // We need to pre-sort these for split packets so we can do extra work where needed
145 2
        foreach ($responses as $response) {
146 2
            $buffer = new Buffer($response);
147
148
            // Pull out the header
149 2
            $header = $buffer->read(4);
150
151
            // Add the packet to the proper section, we will combine later
152 2
            $packets[$header][] = $buffer->getBuffer();
153
        }
154
155 2
        unset($buffer);
156
157 2
        $results = [];
158
159
        // Now let's iterate and process
160 2
        foreach ($packets as $header => $packetGroup) {
161
            // Figure out which packet response this is
162 2
            if (!array_key_exists($header, $this->responses)) {
163 2
                throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
164
            }
165
166
            // Now we need to call the proper method
167
            $results = array_merge(
168
                $results,
169
                call_user_func_array([$this, $this->responses[$header]], [new Buffer(implode($packetGroup))])
170
            );
171
        }
172
173
        unset($packets);
174
175
        return $results;
176
    }
177
178
    /**
179
     * Handles processing the details data into a usable format
180
     *
181
     * @param Buffer $buffer
182
     *
183
     * @return array
184
     * @throws Exception
185
     */
186
    protected function processDetails(Buffer $buffer)
187
    {
188
        // Set the result to a new result instance
189
        $result = new Result();
190
191
        // First int is the server flags
192
        $serverFlags = $buffer->readInt8();
193
194
        // Read server flags
195
        $result->add('password', (int)$this->readFlag($serverFlags, 0));
196
        $result->add('registered_only', (int)$this->readFlag($serverFlags, 1));
197
        $result->add('fog_of_war', (int)$this->readFlag($serverFlags, 2));
198
        $result->add('friendly_fire', (int)$this->readFlag($serverFlags, 3));
199
        $result->add('bots_enabled', (int)$this->readFlag($serverFlags, 5));
200
        $result->add('lua_scripts', (int)$this->readFlag($serverFlags, 6));
201
202
        // Read the rest of the buffer data
203
        $result->add('servername', utf8_encode($buffer->readPascalString(0)));
204
        $result->add('mapname', utf8_encode($buffer->readPascalString(0)));
205
        $result->add('num_players', $buffer->readInt8());
206
        $result->add('max_players', $buffer->readInt8());
207
        $result->add('game_mode', $buffer->readInt8());
208
        $result->add('num_bots', (($this->readFlag($serverFlags, 5)) ? $buffer->readInt8() : 0));
209
        $result->add('dedicated', 1);
210
211
        unset($buffer);
212
213
        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
     */
224
    protected function processPlayers(Buffer $buffer)
225
    {
226
227
        // Set the result to a new result instance
228
        $result = new Result();
229
230
        // First entry is the number of players in this list.  Don't care
231
        $buffer->read();
232
233
        // Parse players
234
        while ($buffer->getLength()) {
235
            // Player id
236
            if (($id = $buffer->readInt8()) !== 0) {
237
                // Add the results
238
                $result->addPlayer('id', $id);
239
                $result->addPlayer('name', utf8_encode($buffer->readPascalString(0)));
240
                $result->addPlayer('team', $buffer->readInt8());
241
                $result->addPlayer('score', $buffer->readInt32());
242
                $result->addPlayer('deaths', $buffer->readInt32());
243
            }
244
        }
245
246
        unset($buffer, $id);
247
248
        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
    protected function readFlag($flags, $offset)
260
    {
261
        return !!($flags & (1 << $offset));
262
    }
263
}
264