Samp::processRules()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
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 16
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\Buffer;
22
use GameQ\Exception\Protocol as Exception;
23
use GameQ\Helpers\Str;
24
use GameQ\Protocol;
25
use GameQ\Result;
26
use GameQ\Server;
27
28
/**
29
 * San Andreas Multiplayer Protocol Class (samp)
30
 *
31
 * Note:
32
 * Player information will not be returned if player count is over 256
33
 *
34
 * @author Austin Bischoff <[email protected]>
35
 */
36
class Samp extends Protocol
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
     * @var 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
     * @var 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
     * @var string
65
     */
66
    protected $protocol = 'samp';
67
68
    /**
69
     * String name of this protocol class
70
     *
71
     * @var string
72
     */
73
    protected $name = 'samp';
74
75
    /**
76
     * Longer string name of this protocol class
77
     *
78
     * @var 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
     * @var string
86
     */
87
    protected $server_code = null;
88
89
    /**
90
     * The client join link
91
     *
92
     * @var string
93
     */
94
    protected $join_link = "samp://%s:%d/";
95
96
    /**
97
     * Normalize settings for this protocol
98
     *
99
     * @var 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 36
    public function beforeSend(Server $server)
126
    {
127
        // Build the server code
128 36
        $this->server_code = implode('', array_map('chr', explode('.', $server->ip()))) .
129 36
            pack("S", $server->portClient());
130
131
        // Loop over the packets and update them
132 36
        foreach ($this->packets as $packetType => $packet) {
133
            // Fill out the packet with the server info
134 36
            $this->packets[$packetType] = sprintf($packet, $this->server_code);
135
        }
136
    }
137
138
    /**
139
     * Process the response
140
     *
141
     * @return array
142
     * @throws \GameQ\Exception\Protocol
143
     */
144 36
    public function processResponse()
145
    {
146
        // Results that will be returned
147 36
        $results = [];
148
149
        // Get the length of the server code so we can figure out how much to read later
150 36
        $serverCodeLength = strlen($this->server_code);
151
152
        // We need to pre-sort these for split packets so we can do extra work where needed
153 36
        foreach ($this->packets_response as $response) {
154
            // Make new buffer
155 36
            $buffer = new Buffer($response);
156
157
            // Check the header, should be SAMP
158 36
            if (($header = $buffer->read(4)) !== 'SAMP') {
159 6
                throw new Exception(__METHOD__ . " header response '{$header}' is not valid");
160
            }
161
162
            // Check to make sure the server response code matches what we sent
163 24
            if ($buffer->read($serverCodeLength) !== $this->server_code) {
164 6
                throw new Exception(__METHOD__ . " code check failed.");
165
            }
166
167
            // Figure out what packet response this is for
168 18
            $response_type = $buffer->read(1);
169
170
            // Figure out which packet response this is
171 18
            if (!array_key_exists($response_type, $this->responses)) {
172 12
                throw new Exception(__METHOD__ . " response type '{$response_type}' is not valid");
173
            }
174
175
            // Now we need to call the proper method
176 6
            $results = array_merge(
177 6
                $results,
178 6
                call_user_func_array([$this, $this->responses[$response_type]], [$buffer])
179 6
            );
180
181 6
            unset($buffer);
182
        }
183
184 6
        return $results;
185
    }
186
187
    // Internal methods
188
189
    /**
190
     * Handles processing the server status data
191
     *
192
     * @param \GameQ\Buffer $buffer
193
     * @return array
194
     * @throws \GameQ\Exception\Protocol
195
     */
196 6
    protected function processStatus(Buffer $buffer)
197
    {
198
        // Set the result to a new result instance
199 6
        $result = new Result();
200
201
        // Always dedicated
202 6
        $result->add('dedicated', 1);
203
204
        // Pull out the server information
205 6
        $result->add('password', $buffer->readInt8());
206 6
        $result->add('num_players', $buffer->readInt16());
207 6
        $result->add('max_players', $buffer->readInt16());
208
209
        // These are read differently for these last 3
210 6
        $result->add('servername', Str::isoToUtf8($buffer->read($buffer->readInt32())));
211 6
        $result->add('gametype', $buffer->read($buffer->readInt32()));
212 6
        $result->add('language', $buffer->read($buffer->readInt32()));
213
214 6
        unset($buffer);
215
216 6
        return $result->fetch();
217
    }
218
219
    /**
220
     * Handles processing the player data into a usable format
221
     *
222
     * @param \GameQ\Buffer $buffer
223
     * @return array
224
     * @throws \GameQ\Exception\Protocol
225
     */
226 6
    protected function processPlayers(Buffer $buffer)
227
    {
228
        // Set the result to a new result instance
229 6
        $result = new Result();
230
231
        // Number of players
232 6
        $result->add('num_players', $buffer->readInt16());
233
234
        // Run until we run out of buffer
235 6
        while ($buffer->getLength()) {
236 6
            $result->addPlayer('id', $buffer->readInt8());
237 6
            $result->addPlayer('name', Str::isoToUtf8($buffer->readPascalString()));
238 6
            $result->addPlayer('score', $buffer->readInt32());
239 6
            $result->addPlayer('ping', $buffer->readInt32());
240
        }
241
242 6
        unset($buffer);
243
244 6
        return $result->fetch();
245
    }
246
247
    /**
248
     * Handles processing the rules data into a usable format
249
     *
250
     * @param \GameQ\Buffer $buffer
251
     * @return array
252
     * @throws \GameQ\Exception\Protocol
253
     */
254 6
    protected function processRules(Buffer $buffer)
255
    {
256
        // Set the result to a new result instance
257 6
        $result = new Result();
258
259
        // Number of rules
260 6
        $result->add('num_rules', $buffer->readInt16());
261
262
        // Run until we run out of buffer
263 6
        while ($buffer->getLength()) {
264 6
            $result->add($buffer->readPascalString(), $buffer->readPascalString());
265
        }
266
267 6
        unset($buffer);
268
269 6
        return $result->fetch();
270
    }
271
}
272