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

Samp::processResponse()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 42
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 16
c 1
b 0
f 0
dl 0
loc 42
ccs 17
cts 17
cp 1
rs 9.4222
cc 5
nc 5
nop 0
crap 5
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\Server;
25
use GameQ\Exception\Protocol as Exception;
26
27
/**
28
 * San Andreas Multiplayer Protocol Class (samp)
29
 *
30
 * Note:
31
 * Player information will not be returned if player count is over 256
32
 *
33
 * @author Austin Bischoff <[email protected]>
34
 */
35
class Samp extends Protocol
36
{
37
38
    /**
39
     * Array of packets we want to look up.
40
     * Each key should correspond to a defined method in this or a parent class
41
     *
42
     * @type array
43
     */
44
    protected $packets = [
45
        self::PACKET_STATUS  => "SAMP%si",
46
        self::PACKET_PLAYERS => "SAMP%sd",
47
        self::PACKET_RULES   => "SAMP%sr",
48
    ];
49
50
    /**
51
     * Use the response flag to figure out what method to run
52
     *
53
     * @type array
54
     */
55
    protected $responses = [
56
        "\x69" => "processStatus", // i
57
        "\x64" => "processPlayers", // d
58
        "\x72" => "processRules", // r
59
    ];
60
61
    /**
62
     * The query protocol used to make the call
63
     *
64
     * @type string
65
     */
66
    protected $protocol = 'samp';
67
68
    /**
69
     * String name of this protocol class
70
     *
71
     * @type string
72
     */
73
    protected $name = 'samp';
74
75
    /**
76
     * Longer string name of this protocol class
77
     *
78
     * @type string
79
     */
80
    protected $name_long = "San Andreas Multiplayer";
81
82
    /**
83
     * Holds the calculated server code that is passed when querying for information
84
     *
85
     * @type string
86
     */
87
    protected $server_code = null;
88
89
    /**
90
     * The client join link
91
     *
92
     * @type string
93
     */
94
    protected $join_link = "samp://%s:%d/";
95
96
    /**
97
     * Normalize settings for this protocol
98
     *
99
     * @type array
100
     */
101
    protected $normalize = [
102
        // General
103
        'general' => [
104
            // target       => source
105
            'dedicated'  => 'dedicated',
106
            'hostname'   => ['hostname', 'servername'],
107
            'mapname'    => 'mapname',
108
            'maxplayers' => 'max_players',
109
            'numplayers' => 'num_players',
110
            'password'   => 'password',
111
        ],
112
        // Individual
113
        'player'  => [
114
            'name'  => 'name',
115
            'score' => 'score',
116
            'ping'  => 'ping',
117
        ],
118
    ];
119
120
    /**
121
     * Handle some work before sending the packets out to the server
122
     *
123
     * @param \GameQ\Server $server
124
     */
125 6
    public function beforeSend(Server $server)
126
    {
127
128
        // Build the server code
129 6
        $this->server_code = implode('', array_map('chr', explode('.', $server->ip()))) .
130 6
            pack("S", $server->portClient());
131
132
        // Loop over the packets and update them
133 6
        foreach ($this->packets as $packetType => $packet) {
134
            // Fill out the packet with the server info
135 6
            $this->packets[$packetType] = sprintf($packet, $this->server_code);
136
        }
137 6
    }
138
139
    /**
140
     * Process the response
141
     *
142
     * @return array
143
     * @throws \GameQ\Exception\Protocol
144
     */
145 6
    public function processResponse()
146
    {
147
148
        // Results that will be returned
149 6
        $results = [];
150
151
        // Get the length of the server code so we can figure out how much to read later
152 6
        $serverCodeLength = strlen($this->server_code);
153
154
        // We need to pre-sort these for split packets so we can do extra work where needed
155 6
        foreach ($this->packets_response as $response) {
156
            // Make new buffer
157 6
            $buffer = new Buffer($response);
158
159
            // Check the header, should be SAMP
160 6
            if (($header = $buffer->read(4)) !== 'SAMP') {
161 1
                throw new Exception(__METHOD__ . " header response '{$header}' is not valid");
162
            }
163
164
            // Check to make sure the server response code matches what we sent
165 4
            if ($buffer->read($serverCodeLength) !== $this->server_code) {
166 1
                throw new Exception(__METHOD__ . " code check failed.");
167
            }
168
169
            // Figure out what packet response this is for
170 3
            $response_type = $buffer->read(1);
171
172
            // Figure out which packet response this is
173 3
            if (!array_key_exists($response_type, $this->responses)) {
174 2
                throw new Exception(__METHOD__ . " response type '{$response_type}' is not valid");
175
            }
176
177
            // Now we need to call the proper method
178 1
            $results = array_merge(
179 1
                $results,
180 1
                call_user_func_array([$this, $this->responses[$response_type]], [$buffer])
181
            );
182
183 1
            unset($buffer);
184
        }
185
186 1
        return $results;
187
    }
188
189
    /*
190
     * Internal methods
191
     */
192
193
    /**
194
     * Handles processing the server status data
195
     *
196
     * @param \GameQ\Buffer $buffer
197
     *
198
     * @return array
199
     * @throws \GameQ\Exception\Protocol
200
     */
201 1
    protected function processStatus(Buffer $buffer)
202
    {
203
204
        // Set the result to a new result instance
205 1
        $result = new Result();
206
207
        // Always dedicated
208 1
        $result->add('dedicated', 1);
209
210
        // Pull out the server information
211 1
        $result->add('password', $buffer->readInt8());
212 1
        $result->add('num_players', $buffer->readInt16());
213 1
        $result->add('max_players', $buffer->readInt16());
214
215
        // These are read differently for these last 3
216 1
        $result->add('servername', utf8_encode($buffer->read($buffer->readInt32())));
217 1
        $result->add('gametype', $buffer->read($buffer->readInt32()));
218 1
        $result->add('language', $buffer->read($buffer->readInt32()));
219
220 1
        unset($buffer);
221
222 1
        return $result->fetch();
223
    }
224
225
    /**
226
     * Handles processing the player data into a usable format
227
     *
228
     * @param \GameQ\Buffer $buffer
229
     *
230
     * @return array
231
     */
232 1
    protected function processPlayers(Buffer $buffer)
233
    {
234
235
        // Set the result to a new result instance
236 1
        $result = new Result();
237
238
        // Number of players
239 1
        $result->add('num_players', $buffer->readInt16());
240
241
        // Run until we run out of buffer
242 1
        while ($buffer->getLength()) {
243 1
            $result->addPlayer('id', $buffer->readInt8());
244 1
            $result->addPlayer('name', utf8_encode($buffer->readPascalString()));
245 1
            $result->addPlayer('score', $buffer->readInt32());
246 1
            $result->addPlayer('ping', $buffer->readInt32());
247
        }
248
249 1
        unset($buffer);
250
251 1
        return $result->fetch();
252
    }
253
254
    /**
255
     * Handles processing the rules data into a usable format
256
     *
257
     * @param \GameQ\Buffer $buffer
258
     *
259
     * @return array
260
     */
261 1
    protected function processRules(Buffer $buffer)
262
    {
263
264
        // Set the result to a new result instance
265 1
        $result = new Result();
266
267
        // Number of rules
268 1
        $result->add('num_rules', $buffer->readInt16());
269
270
        // Run until we run out of buffer
271 1
        while ($buffer->getLength()) {
272 1
            $result->add($buffer->readPascalString(), $buffer->readPascalString());
273
        }
274
275 1
        unset($buffer);
276
277 1
        return $result->fetch();
278
    }
279
}
280