├── LICENSE
├── README.md
├── arc.php
└── composer.json
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Felix Schäfer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Arma RCon Class (ARC) 2.2 for PHP
2 | [](https://www.codacy.com/app/nizari/arma-rcon-class-php?utm_source=github.com&utm_medium=referral&utm_content=Nizarii/arma-rcon-class-php&utm_campaign=Badge_Grade)
3 | [](https://packagist.org/packages/nizarii/arma-rcon-class)
4 | [](https://github.com/Nizarii/arma-rcon-class-php/)
5 |
6 | ARC is a lightweight PHP class, that allows you connecting and sending commands easily to your **BattlEye** server, including **Arma 3**, **Arma 2** and **Arma 2: OA** game servers.
7 |
8 |
9 | ## Requirements
10 | ARC 2.2 only requires **PHP 5.4** or higher!
11 |
12 |
13 | ## Installation
14 | #### Via Composer *(recommended)*
15 | If you haven't already, download Composer.
16 | ```shell
17 | $ curl -s http://getcomposer.org/installer | php
18 | ```
19 | Now require and install ARC.
20 | ```shell
21 | $ composer require nizarii/arma-rcon-class
22 | $ composer install
23 | ```
24 |
25 | #### Without Composer
26 | Choose a [release](https://github.com/Nizarii/arma-rcon-class-php/releases) and include ARC in your project: `require_once 'arc.php';`.
27 |
28 |
29 | ## Examples
30 | #### Getting started
31 | After installing ARC, you can easily use ARC as shown below. It will *automatically* establish a new connection and authenticate.
32 | ```php
33 | use \Nizarii\ARC;
34 |
35 | $rcon = new ARC(string $ServerIP, string $RConPassword [, int $Port = 2302 [, array $Options = array()]]);
36 | ```
37 | You are able to send commands with the `command()` function.
38 | ```php
39 | //...
40 | $rcon->command('YourCommand');
41 | ```
42 | ARC will throw an `Exception` if anything goes wrong. You might want to wrap your code in a *try catch block*.
43 | ```php
44 | use \Nizarii\ARC;
45 |
46 | try {
47 | $rcon = new ARC('127.0.0.1', 'password');
48 |
49 | $array = $rcon->getPlayersArray();
50 |
51 | $rcon
52 | ->sayGlobal('example')
53 | ->kickPlayer(1, 'example')
54 | ->sayPlayer(0, 'example')
55 | ->disconnect()
56 | ;
57 |
58 | $rcon->getBans(); // Throws exception, because the connection was closed
59 | } catch (Exception $e) {
60 | echo "Ups! Something went wrong: {$e->getMessage()}";
61 | }
62 | ```
63 | *Please consider that ARC only checks whether the command has been successfully sent via the socket to the server. It does not check if the command has been executed by the server.*
64 |
65 | #### Options
66 | Options can be passed to ARC as an array via the fourth parameter of the constructor. The following options are currently available:
67 | * `int timeoutSec = 1`: Sets a timeout value on the connection.
68 | * `bool autosavebans = false`: Automatically saves bans.txt after a player is banned or unbanned.
69 | * `bool debug = false` : turns on debug mode, only works on "socketLoop()" and "socketLoopClose()"
70 |
71 | *Suggestions for new options are always welcome!* :+1:
72 |
73 | **Basic usage:**
74 | ```php
75 | use \Nizarii\ARC;
76 |
77 | $rcon = new ARC('127.0.0.1', 'RConPassword', 2322, [
78 | 'timeoutSec' => 2
79 | ]);
80 |
81 | //...
82 | ```
83 | ## Functions
84 | ARC features many functions to send BattlEye commands easier. After creating a new connections as explained above, you are able to use any of these functions:
85 | * `string command(string $command)`: Sends any command to the BattlEye server.
86 | * `string getPlayers()`: Returns a list with all players, which are currently on the server.
87 | * `array getPlayersArray()`: Same as "getPlayers()", but formats the list to an array.
88 | * `string getMissions()`: Gets a list of the available missions on the server.
89 | * `string getBans()`: Gets a list of all BE server bans.
90 | * `array getBansArray()`: Gets an array of all bans.
91 | * `object kickPlayer(int $player [, string $reason = 'Admin Kick'])`: Kicks a player who is currently on the server.
92 | * `object sayGlobal(string $message)`: Sends a global message to all players.
93 | * `object sayPlayer(int $player, string $message)`: Sends a message to a specific player.
94 | * `object loadScripts()`: Loads the "scripts.txt" file without the need to restart the server.
95 | * `object maxPing(int $ping)`: Changes the MaxPing value. If a player has a higher ping, he will be kicked from the server.
96 | * `object changePassword(string $password)`: Changes RCon password.
97 | * `object loadBans()`: (Re)loads the BE ban list from "bans.txt".
98 | * `object banPlayer(string $player [, string $reason = 'Banned' [, int $time = 0]])`: Ban a player's BE GUID from the server (If the time is 0, the ban will be permanent).
99 | * `object addBan(string $player [, string $reason = 'Banned' [, int $time = 0]])`: Same as "banPlayer()", but allows to ban a player that is not currently on the server.
100 | * `object removeBan(int $banid)`: Removes a ban.
101 | * `object writeBans()`: Removes expired bans from the bans file.
102 | * `object getBEServerVersion()`: Gets the current version of the BE server.
103 | * `disconnect()`: Closes the current connection.
104 | * `object reconnect()`: Closes the current connection & creates a new one.
105 | * `resource getSocket()`: Get the socket, which is used by ARC to send commands to the server.
106 | * `boolean socketLoop(int $loop = -1)`: Get constant socket stream. $loop is the number of loops to be run until exiting the method. Note that the sequence will be reset. Set to infinite looping by default.
107 | * `boolean socketLoopClose(int $loop = -1)`: Similar as "socketLoop()", but disconnects after looping. Set to infinite looping by default.
108 | * `array readPackageRaw(string $msg)`: Make BettlEye format package readable for your program. Use $msg with unreadable header and unmodified string. Array will start as [0] => "FF" when having correct header. Array value [1] has important information what kind of care it needs for your connection. For more information, click [here](https://www.battleye.com/downloads/BERConProtocol.txt "BattlEye RCon Protocol Specification").
109 |
110 | *See [here](https://community.bistudio.com/wiki/BattlEye#RCon_commands "BattlEye Wiki") for more information about BattlEye commands*
111 |
112 |
113 | ## Contributors
114 | Thanks to all contributors for submitting issues and contributing code, including:
115 | * @nerdalertdk
116 | * @steffalon
117 |
118 | *New contributors are always welcome :heart:*
119 |
120 |
121 | ## License
122 |
123 | ARC is licensed under the **MIT License**. View `LICENSE` file for more information.
124 |
--------------------------------------------------------------------------------
/arc.php:
--------------------------------------------------------------------------------
1 | 1,
25 | 'autosaveBans' => false,
26 | 'debug' => false
27 | ];
28 |
29 | /**
30 | * @var string Server IP of the BattlEye server
31 | */
32 | private $serverIP;
33 |
34 | /**
35 | * @var int Specific port of the BattlEye server
36 | */
37 | private $serverPort;
38 |
39 | /**
40 | * @var string Required password for authenticating
41 | */
42 | private $rconPassword;
43 |
44 | /**
45 | * @var resource Socket for sending commands
46 | */
47 | private $socket;
48 |
49 | /**
50 | * @var bool Status of the connection
51 | */
52 | private $disconnected = true;
53 |
54 | /**
55 | * @var string Head of the message, which was sent to the server
56 | */
57 | private $head;
58 |
59 | /**
60 | * @var int Sequence number and also a helper to end loops.
61 | */
62 | private $end = 0; // required to remember the sequence.
63 |
64 | /**
65 | * Class constructor
66 | *
67 | * @param string $serverIP IP of the Arma server
68 | * @param integer $serverPort Port of the Arma server
69 | * @param string $RConPassword RCon password required by BattlEye
70 | * @param array $options Options array of ARC
71 | *
72 | * @throws \Exception if wrong parameter types were passed to the function
73 | */
74 | public function __construct($serverIP, $RConPassword, $serverPort = 2302, array $options = array())
75 | {
76 | if (!is_int($serverPort) || !is_string($RConPassword) || !is_string($serverIP)) {
77 | throw new \Exception('Wrong constructor parameter type(s)!');
78 | }
79 |
80 | $this->serverIP = $serverIP;
81 | $this->serverPort = $serverPort;
82 | $this->rconPassword = $RConPassword;
83 | $this->options = array_merge($this->options, $options);
84 |
85 | $this->checkOptionTypes();
86 | $this->checkForDeprecatedOptions();
87 |
88 | $this->connect();
89 | }
90 |
91 | /**
92 | * Class destructor
93 | */
94 | public function __destruct()
95 | {
96 | $this->disconnect();
97 | }
98 |
99 | /**
100 | * Closes the connection
101 | */
102 | public function disconnect()
103 | {
104 | if ($this->disconnected) {
105 | return;
106 | }
107 | fclose($this->socket);
108 |
109 | $this->socket = null;
110 | $this->disconnected = true;
111 | }
112 |
113 | /**
114 | * Creates a connection to the server
115 | *
116 | * @throws \Exception if creating the socket fails
117 | */
118 | private function connect()
119 | {
120 | if (!$this->disconnected) {
121 | $this->disconnect();
122 | }
123 |
124 | $this->socket = @fsockopen("udp://$this->serverIP", $this->serverPort, $errno, $errstr, $this->options['timeoutSec']);
125 | if (!$this->socket) {
126 | throw new \Exception('Failed to create socket!');
127 | }
128 |
129 | stream_set_timeout($this->socket, $this->options['timeoutSec']);
130 | stream_set_blocking($this->socket, true);
131 |
132 | $this->authorize();
133 | $this->disconnected = false;
134 | }
135 |
136 | /**
137 | * Closes the current connection and creates a new one
138 | */
139 | public function reconnect()
140 | {
141 | if (!$this->disconnected) {
142 | $this->disconnect();
143 | }
144 |
145 | $this->connect();
146 | return $this;
147 | }
148 |
149 | /**
150 | * Checks if ARC's option array contains any deprecated options
151 | */
152 | private function checkForDeprecatedOptions()
153 | {
154 | if (array_key_exists('timeout_sec', $this->options)) {
155 | @trigger_error("The 'timeout_sec' option is deprecated since version 2.1.2 and will be removed in 3.0. Use 'timeoutSec' instead.", E_USER_DEPRECATED);
156 | $this->options['timeoutSec'] = $this->options['timeout_sec'];
157 | }
158 | if (array_key_exists('heartbeat', $this->options) || array_key_exists('sendHeartbeat', $this->options)) {
159 | @trigger_error("Sending a heartbeat packet is deprecated since version 2.2.", E_USER_DEPRECATED);
160 | }
161 | }
162 |
163 | /**
164 | * Validate all option types
165 | */
166 | private function checkOptionTypes()
167 | {
168 | if (!is_int($this->options['timeoutSec'])) {
169 | throw new \Exception(
170 | sprintf("Expected option 'timeoutSec' to be integer, got %s", gettype($this->options['timeoutSec']))
171 | );
172 | }
173 | if (!is_bool($this->options['autosaveBans'])) {
174 | throw new \Exception(
175 | sprintf("Expected option 'autosaveBans' to be boolean, got %s", gettype($this->options['autosaveBans']))
176 | );
177 | }
178 | if (!is_bool($this->options['debug'])) {
179 | throw new \Exception(
180 | sprintf("Expected option 'debug' to be boolean, got %s", gettype($this->options['debug']))
181 | );
182 | }
183 | }
184 |
185 | /**
186 | * Sends the login data to the server in order to send commands later
187 | *
188 | * @throws \Exception if login fails (due to a wrong password or port)
189 | */
190 | private function authorize()
191 | {
192 | $sent = $this->writeToSocket($this->getLoginMessage());
193 | if ($sent === false) {
194 | throw new \Exception('Failed to send login!');
195 | }
196 |
197 | $result = fread($this->socket, 16);
198 | if (@ord($result[strlen($result)-1]) == 0) {
199 | throw new \Exception('Login failed, wrong password or wrong port!');
200 | }
201 | }
202 |
203 | /**
204 | * Receives the answer form the server
205 | *
206 | * @return string Any answer from the server, except the log-in message
207 | */
208 | protected function getResponse()
209 | {
210 | $output = '';
211 |
212 | $temp = fread($this->socket, 102400);
213 | while ($temp)
214 | {
215 | $output .= $this->splitPacket($temp);
216 | $temp = fread($this->socket, 102400);
217 | }
218 |
219 | return $output;
220 | }
221 |
222 | /**
223 | * The heart of this class - this function actually sends the RCon command
224 | *
225 | * @param string $command The command sent to the server
226 | *
227 | * @throws \Exception if the connection is closed
228 | * @throws \Exception if sending the command failed
229 | *
230 | * @return bool Whether sending the command was successful or not
231 | */
232 | protected function send($command)
233 | {
234 | if ($this->disconnected) {
235 | throw new \Exception('Failed to send command, because the connection is closed!');
236 | }
237 | $msgCRC = $this->getMsgCRC($command);
238 | $head = 'BE'.chr(hexdec($msgCRC[0])).chr(hexdec($msgCRC[1])).chr(hexdec($msgCRC[2])).chr(hexdec($msgCRC[3])).chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('%01b', 0)));
239 |
240 | $msg = $head.$command;
241 | $this->head = $head;
242 |
243 | if ($this->writeToSocket($msg) === false) {
244 | throw new \Exception('Failed to send command!');
245 | }
246 | }
247 |
248 | /**
249 | * Writes the given message to the socket
250 | *
251 | * @param string $message Message which will be written to the socket
252 | *
253 | * @return int
254 | */
255 | private function writeToSocket($message)
256 | {
257 | return fwrite($this->socket, $message);
258 | }
259 |
260 | /**
261 | * Generates the password's CRC32 data
262 | *
263 | * @return string
264 | */
265 | private function getAuthCRC()
266 | {
267 | $authCRC = hash('crc32b', chr(255).chr(00).trim($this->rconPassword));
268 | $authCRC = array(substr($authCRC,-2,2), substr($authCRC,-4,2), substr($authCRC,-6,2), substr($authCRC,0,2));
269 |
270 | return $authCRC;
271 | }
272 |
273 | /**
274 | * Generates the message's CRC32 data
275 | *
276 | * @param string $command The message which will be prepared for being sent to the server
277 | *
278 | * @return string Message which can be sent to the server
279 | */
280 | private function getMsgCRC($command)
281 | {
282 | $msgCRC = hash('crc32b', chr(255).chr(01).chr(hexdec(sprintf('%01b', 0))).$command);
283 | $msgCRC = array(substr($msgCRC,-2,2),substr($msgCRC,-4,2),substr($msgCRC,-6,2),substr($msgCRC,0,2));
284 |
285 | return $msgCRC;
286 | }
287 |
288 | /**
289 | * Generates the login message
290 | *
291 | * @return string The message for authenticating in, containing the RCon password
292 | */
293 | private function getLoginMessage()
294 | {
295 | $authCRC = $this->getAuthCRC();
296 |
297 | $loginMsg = 'BE'.chr(hexdec($authCRC[0])).chr(hexdec($authCRC[1])).chr(hexdec($authCRC[2])).chr(hexdec($authCRC[3]));
298 | $loginMsg .= chr(hexdec('ff')).chr(hexdec('00')).$this->rconPassword;
299 |
300 | return $loginMsg;
301 | }
302 |
303 | /**
304 | * Returns the socket used by ARC, might be null if connection is closed
305 | *
306 | * @return resource
307 | */
308 | public function getSocket()
309 | {
310 | return $this->socket;
311 | }
312 |
313 | /**
314 | * Sends a custom command to the server
315 | *
316 | * @param string $command Command which will be sent to the server
317 | *
318 | * @throws \Exception if wrong parameter types were passed to the function
319 | *
320 | * @return string Response from the server
321 | */
322 | public function command($command)
323 | {
324 | if (!is_string($command)) {
325 | throw new \Exception('Wrong parameter type!');
326 | }
327 |
328 | $this->send($command);
329 | return $this->getResponse();
330 | }
331 |
332 | /**
333 | * Executes multiple commands
334 | *
335 | * @param array $commands Commands to be executed
336 | */
337 | public function commands(array $commands)
338 | {
339 | foreach ($commands as $command) {
340 | if (!is_string($command)) {
341 | continue;
342 | }
343 | $this->command($command);
344 | }
345 | }
346 |
347 | /**
348 | * Kicks a player who is currently on the server
349 | *
350 | * @param string $reason Message displayed why the player is kicked
351 | * @param integer $player The player who should be kicked
352 |
353 | * @throws \Exception if wrong parameter types were passed to the function
354 | *
355 | * @return ARC
356 | */
357 | public function kickPlayer($player, $reason = 'Admin Kick')
358 | {
359 | if (!is_int($player) && !is_string($player)) {
360 | throw new \Exception(
361 | sprintf('Expected parameter 1 to be string or integer, got %s', gettype($player))
362 | );
363 | }
364 | if (!is_string($reason)) {
365 | throw new \Exception(
366 | sprintf('Expected parameter 2 to be string, got %s', gettype($reason))
367 | );
368 | }
369 |
370 | $this->send("kick $player $reason");
371 | $this->reconnect();
372 |
373 | return $this;
374 | }
375 |
376 | /**
377 | * Sends a global message to all players
378 | *
379 | * @param string $message The message which will be shown to all players
380 |
381 | * @throws \Exception if wrong parameter types were passed to the function
382 | *
383 | * @return ARC
384 | */
385 | public function sayGlobal($message)
386 | {
387 | if (!is_string($message)) {
388 | throw new \Exception(
389 | sprintf('Expected parameter 1 to be string, got %s', gettype($message))
390 | );
391 | }
392 |
393 | $this->send("Say -1 $message");
394 | return $this;
395 | }
396 |
397 | /**
398 | * Sends a message to a specific player
399 | *
400 | * @param integer $player Player who will be sent the message to
401 | * @param string $message Message for the player
402 |
403 | * @throws \Exception if wrong parameter types were passed to the function
404 | *
405 | * @return ARC
406 | */
407 | public function sayPlayer($player, $message)
408 | {
409 | if (!is_int($player) || !is_string($message)) {
410 | throw new \Exception('Wrong parameter type(s)!');
411 | }
412 |
413 | $this->send("Say $player $message");
414 | return $this;
415 | }
416 |
417 | /**
418 | * Loads the "scripts.txt" file without the need to restart the server
419 | *
420 | * @return ARC
421 | */
422 | public function loadScripts()
423 | {
424 | $this->send('loadScripts');
425 | return $this;
426 | }
427 |
428 | /**
429 | * Changes the MaxPing value. If a player has a higher ping, he will be kicked from the server
430 | *
431 | * @param integer $ping The value for the 'MaxPing' BattlEye server setting
432 | *
433 | * @throws \Exception if wrong parameter types were passed to the function
434 | *
435 | * @return ARC
436 | */
437 | public function maxPing($ping)
438 | {
439 | if (!is_int($ping)) {
440 | throw new \Exception(
441 | sprintf('Expected parameter 1 to be integer, got %s', gettype($ping))
442 | );
443 | }
444 |
445 | $this->send("MaxPing $ping");
446 | return $this;
447 | }
448 |
449 | /**
450 | * Changes the RCon password
451 | *
452 | * @param string $password The new password
453 | *
454 | * @throws \Exception if wrong parameter types were passed to the function
455 | *
456 | * @return ARC
457 | */
458 | public function changePassword($password)
459 | {
460 | if (!is_string($password)) {
461 | throw new \Exception(
462 | sprintf('Expected parameter 1 to be string, got %s', gettype($password))
463 | );
464 | }
465 |
466 | $this->send("RConPassword $password");
467 | return $this;
468 | }
469 |
470 | /**
471 | * (Re)load the BE ban list from bans.txt
472 | *
473 | * @return ARC
474 | */
475 | public function loadBans()
476 | {
477 | $this->send('loadBans');
478 | return $this;
479 | }
480 |
481 | /**
482 | * Gets a list of all players currently on the server
483 | *
484 | * @return string The list of all players on the server
485 | */
486 | public function getPlayers()
487 | {
488 | $this->send('players');
489 | $result = $this->getResponse();
490 |
491 | $this->reconnect();
492 | return $result;
493 | }
494 |
495 | /**
496 | * Gets a list of all players currently on the server as an array
497 | *
498 | * @author nerdalertdk (https://github.com/nerdalertdk)
499 | * @link https://github.com/Nizarii/arma-rcon-class-php/issues/4 The related GitHub Issue
500 | *
501 | * @throws \Exception if sending the command failed
502 | *
503 | * @return array The array containing all players being currently on the server
504 | */
505 | public function getPlayersArray()
506 | {
507 | $playersRaw = $this->getPlayers();
508 |
509 | $players = $this->cleanList($playersRaw);
510 | preg_match_all("#(\d+)\s+(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+\b)\s+\-*(\d+)\s+([0-9a-fA-F]+)\(\w+\)\s([\S ]+)$#im", $players, $str);
511 |
512 | return $this->formatList($str);
513 | }
514 |
515 | /**
516 | * Gets a list of all bans
517 | *
518 | * @throws \Exception if sending the command failed
519 | *
520 | * @return string List containing the missions
521 | */
522 | public function getMissions()
523 | {
524 | $this->send('missions');
525 | return $this->getResponse();
526 | }
527 |
528 | /**
529 | * Ban a player's BE GUID from the server. If time is not specified or 0, the ban will be permanent;.
530 | * If reason is not specified the player will be kicked with the message "Banned".
531 | *
532 | * @param integer $player Player who will be banned
533 | * @param string $reason Reason why the player is banned
534 | * @param integer $time How long the player is banned in minutes (0 = permanent)
535 |
536 | * @throws \Exception if wrong parameter types were passed to the function
537 | *
538 | * @return ARC
539 | */
540 | public function banPlayer($player, $reason = 'Banned', $time = 0)
541 | {
542 | if (!is_string($player) && !is_int($player)) {
543 | throw new \Exception(
544 | sprintf('Expected parameter 1 to be integer or string, got %s', gettype($player))
545 | );
546 | }
547 |
548 | if (!is_string($reason) || !is_int($time)) {
549 | throw new \Exception('Wrong parameter type(s)!');
550 | }
551 |
552 | $this->send("ban $player $time $reason");
553 | $this->reconnect();
554 |
555 | if ($this->options['autosaveBans']) {
556 | $this->writeBans();
557 | }
558 |
559 | return $this;
560 | }
561 |
562 | /**
563 | * Same as "banPlayer", but allows to ban a player that is not currently on the server
564 | *
565 | * @param integer $player Player who will be banned
566 | * @param string $reason Reason why the player is banned
567 | * @param integer $time How long the player is banned in minutes (0 = permanent)
568 | *
569 | * @throws \Exception if wrong parameter types were passed to the function
570 | *
571 | * @return ARC
572 | */
573 | public function addBan($player, $reason = 'Banned', $time = 0)
574 | {
575 | if (!is_string($player) || !is_string($reason) || !is_int($time)) {
576 | throw new \Exception('Wrong parameter type(s)!');
577 | }
578 |
579 | $this->send("addBan $player $time $reason");
580 |
581 | if ($this->options['autosaveBans']) {
582 | $this->writeBans();
583 | }
584 |
585 | return $this;
586 | }
587 |
588 | /**
589 | * Removes a ban
590 | *
591 | * @param integer $banId Ban who will be removed
592 | *
593 | * @throws \Exception if wrong parameter types were passed to the function
594 | *
595 | * @return ARC
596 | */
597 | public function removeBan($banId)
598 | {
599 | if (!is_int($banId)) {
600 | throw new \Exception(
601 | sprintf('Expected parameter 1 to be integer, got %s', gettype($banId))
602 | );
603 | }
604 |
605 | $this->send("removeBan $banId");
606 |
607 | if ($this->options['autosaveBans']) {
608 | $this->writeBans();
609 | }
610 |
611 | return $this;
612 | }
613 |
614 | /**
615 | * Gets an array of all bans
616 | *
617 | * @author nerdalertdk (https://github.com/nerdalertdk)
618 | * @link https://github.com/Nizarii/arma-rcon-class-php/issues/4
619 | *
620 | * @return array The array containing all bans
621 | */
622 | public function getBansArray()
623 | {
624 | $bansRaw = $this->getBans();
625 | $bans = $this->cleanList($bansRaw);
626 |
627 | preg_match_all("#(\d+)\s+([0-9a-fA-F]+)\s([perm|\d]+)\s([\S ]+)$#im", $bans, $str);
628 | return $this->formatList($str);
629 | }
630 |
631 | /**
632 | * Gets a list of all bans
633 | *
634 | * @return string The response from the server
635 | */
636 | public function getBans()
637 | {
638 | $this->send('bans');
639 | return $this->getResponse();
640 | }
641 |
642 | /**
643 | * Removes expired bans from bans file
644 | *
645 | * @return ARC
646 | */
647 | public function writeBans()
648 | {
649 | $this->send('writeBans');
650 | return $this;
651 | }
652 |
653 | /**
654 | * Gets the current version of the BE server
655 | *
656 | * @return string The BE server version
657 | */
658 | public function getBEServerVersion()
659 | {
660 | $this->send('version');
661 | return $this->getResponse();
662 | }
663 |
664 | /**
665 | * Get socket and continue streaming and disconnect after looping.
666 | *
667 | * @author steffalon (https://github.com/steffalon)
668 | * @link https://github.com/schaeferfelix/arma-rcon-class-php/issues/30 issue part 1
669 | * @link https://github.com/schaeferfelix/arma-rcon-class-php/issues/31 issue part 2
670 | *
671 | * @param integer $loop Number of loops through this funtion. By default, (-1) for no ending.
672 | *
673 | * @return boolean
674 | */
675 | public function socketLoopClose($loop = -1)
676 | {
677 | if ($this->end !== null) {
678 | $loop = $this->end + $loop;
679 | }
680 | while ($this->end !== $loop) {
681 | $msg = fread($this->socket, 9000);
682 | if ($this->options['debug']) {
683 | echo preg_replace("/\r|\n/", "", substr($msg, 9)).PHP_EOL;
684 | }
685 | $timeout = stream_get_meta_data($this->socket);
686 | if ($timeout['timed_out']) {
687 | $this->keepAlive();
688 | } else {
689 | $this->end = $this->readPackage($msg);
690 | }
691 | }
692 | $this->end = 0;
693 | $this->disconnect();
694 | return true; // Completed
695 | }
696 |
697 | /**
698 | * Get socket and continue streaming and don't disconnect after looping.
699 | *
700 | * @author steffalon (https://github.com/steffalon)
701 | * @link https://github.com/schaeferfelix/arma-rcon-class-php/issues/30 issue part 1
702 | * @link https://github.com/schaeferfelix/arma-rcon-class-php/issues/31 issue part 2
703 | *
704 | * @param integer $loop Number of loops through this funtion. By default, (-1) for no ending.
705 | *
706 | * @return boolean
707 | */
708 | public function socketLoop($loop = -1)
709 | {
710 | if ($this->end !== null) {
711 | $loop = $this->end + $loop;
712 | }
713 | while ($this->end !== $loop) {
714 | $msg = fread($this->socket, 9000);
715 | if ($this->options['debug']) {
716 | echo preg_replace("/\r|\n/", "", substr($msg, 9)).PHP_EOL;
717 | }
718 | $timeout = stream_get_meta_data($this->socket);
719 | if ($timeout['timed_out']) {
720 | $this->keepAlive();
721 | } else {
722 | $this->end = $this->readPackage($msg);
723 | }
724 | }
725 | return true; // Completed
726 | }
727 |
728 | /**
729 | * Reads what kind of package it is. This method is also a helper for sequence.
730 | *
731 | * @author steffalon (https://github.com/steffalon)
732 | * @link https://github.com/schaeferfelix/arma-rcon-class-php/issues/30 issue part 1
733 | * @link https://github.com/schaeferfelix/arma-rcon-class-php/issues/31 issue part 2
734 | *
735 | * @param string $msg message received from BE with unreadable header.
736 | *
737 | * @throws \Exception by invalid BERCon login details.
738 | *
739 | * @return integer
740 | */
741 | private function readPackage($msg)
742 | {
743 | $responseCode = unpack('H*', $msg); // Make message usefull for battleye packet by unpacking it to bytes.
744 | $responseCode = str_split(substr($responseCode[1], 12), 2); // Get important bytes.
745 | switch ($responseCode[1]) { // See https://www.battleye.com/downloads/BERConProtocol.txt for packet info.
746 | case "00": // Login WILL ONLY HAPPEN IF socketLoopClose() got called and is done.
747 | if ($responseCode[2] == "01") { // Login successful.
748 | if ($this->options['debug']) {
749 | echo "Accepted BERCon login.".PHP_EOL;
750 | }
751 | $this->authorize();
752 | } else { // Otherwise $responseCode[2] == "0x00" (Login failed)
753 | throw new \Exception('Invalid BERCon login details. This process is getting stopped!');
754 | }
755 | break;
756 | case "01": // Send commands by this client.
757 | if (count($responseCode) == 3) {
758 | break;
759 | }
760 | if ($responseCode[3] !== "00") { // This package is small.
761 | if ($this->options['debug']) {
762 | echo "This is a small package.".PHP_EOL;
763 | }
764 | } else {
765 | if ($this->options['debug']) { // This package is multi-packet.
766 | echo "Multi-packet.".PHP_EOL;
767 | }
768 | // if ($this->options['debug']) var_dump($responseCode); //Useful developer information.
769 | // if ($responseCode[5] == "00") {
770 | // $getAmount = $responseCode[4];
771 | // if ($this->options['debug']) var_dump($getAmount);
772 | // }
773 | }
774 | break;
775 | case "02": // Acknowledge as client.
776 | return $this->acknowledge($this->end);
777 | break;
778 | }
779 | }
780 |
781 | /**
782 | * Read package format and return converted to usable bytes. Array starts at 0x[FF].
783 | *
784 | * @author steffalon (https://github.com/steffalon)
785 | *
786 | * @param string $msg message received from BE with unreadable header. Do not modify or strip the original header and use this function.
787 | *
788 | * @throws \Exception by invalid BERCon login details.
789 | *
790 | * @return array
791 | */
792 | public function readPackageRaw($msg)
793 | {
794 | $responseCode = unpack('H*', $msg); // Make message usefull for battleye packet by unpacking it to bytes.
795 | $responseCode = str_split(substr($responseCode[1], 12), 2); // Get important bytes.
796 | return $responseCode;
797 | }
798 |
799 | /**
800 | * Acknowledge the data and add +1 to sequence.
801 | *
802 | * @author steffalon (https://github.com/steffalon)
803 | * @link https://github.com/schaeferfelix/arma-rcon-class-php/issues/30 issue part 1
804 | * @link https://github.com/schaeferfelix/arma-rcon-class-php/issues/31 issue part 2
805 | *
806 | * @param integer $int Sequence number. Makes a new header with that number.
807 | *
808 | * @throws \Exception if failed to send a command
809 | *
810 | * @return integer
811 | */
812 | private function acknowledge($int)
813 | {
814 | if ($this->options['debug']) {
815 | echo "Acknowledge!".PHP_EOL;
816 | }
817 | $needBuffer = chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $int)));
818 | $needBuffer = hash("crc32b", $needBuffer);
819 | $needBuffer = str_split($needBuffer, 2);
820 | $needBuffer = array_reverse($needBuffer);
821 | $statusmsg = "BE".chr(hexdec($needBuffer[0])).chr(hexdec($needBuffer[1])).chr(hexdec($needBuffer[2])).chr(hexdec($needBuffer[3]));
822 | $statusmsg .= chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $int)));
823 | if ($this->writeToSocket($statusmsg) === false) {
824 | throw new \Exception('Failed to send command!');
825 | }
826 | return ++$int; // Sequence +1
827 | }
828 |
829 | /**
830 | * Keep the stream alive. Send package to BE server. Use this function before 45 seconds.
831 | *
832 | * @author steffalon (https://github.com/steffalon)
833 | * @link https://github.com/schaeferfelix/arma-rcon-class-php/issues/30 issue part 1
834 | * @link https://github.com/schaeferfelix/arma-rcon-class-php/issues/31 issue part 2
835 | *
836 | * @throws \Exception if failed to send a command
837 | *
838 | * @return boolean
839 | */
840 | private function keepAlive()
841 | {
842 | if ($this->options['debug']) {
843 | echo '--Keep connection alive--'.PHP_EOL;
844 | }
845 | $keepalive = "BE".chr(hexdec("be")).chr(hexdec("dc")).chr(hexdec("c2")).chr(hexdec("58"));
846 | $keepalive .= chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('00')));
847 | if ($this->writeToSocket($keepalive) === false) {
848 | throw new \Exception('Failed to send command!');
849 | return false; // Failed
850 | }
851 | return true; // Completed
852 | }
853 |
854 | /**
855 | * Converts BE text "array" list to array
856 | *
857 | * @author nerdalertdk (https://github.com/nerdalertdk)
858 | * @link https://github.com/Nizarii/arma-rcon-class-php/issues/4 The related Github issue
859 | *
860 | * @param $str array
861 | *
862 | * @return array
863 | */
864 | private function formatList($str)
865 | {
866 | // Remove first array
867 | array_shift($str);
868 | // Create return array
869 | $result = array();
870 |
871 | // Loop true the main arrays, each holding a value
872 | foreach($str as $key => $value) {
873 | // Combines each main value into new array
874 | foreach($value as $keyLine => $line) {
875 | $result[$keyLine][$key] = trim($line);
876 | }
877 | }
878 |
879 | return $result;
880 | }
881 |
882 | /**
883 | * Remove control characters
884 | *
885 | * @author nerdalertdk (https://github.com/nerdalertdk)
886 | * @link https://github.com/Nizarii/arma-rcon-class-php/issues/4 The related GitHub issue
887 | *
888 | * @param $str string
889 | *
890 | * @return string
891 | */
892 | private function cleanList($str)
893 | {
894 | return preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $str);
895 | }
896 |
897 | public function splitPacket( $data )
898 | {
899 | $responseCode = $this->readPackageRaw($data);
900 |
901 | if ($responseCode[1] == "01") {
902 |
903 | if (sizeof($responseCode) < 4) {
904 | return "true";
905 | }
906 | if (sizeof($responseCode) >= 4 && $responseCode[3] !== "00") {
907 | return substr($data, 9);
908 | } else {
909 | return substr($data, 12);
910 | }
911 | }
912 |
913 | return '';
914 | }
915 | }
916 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nizarii/arma-rcon-class",
3 | "homepage": "https://github.com/Nizarii/arma-rcon-class-php/",
4 | "type": "library",
5 | "description": "ARC is a lightweight PHP class, that allows you connecting and sending commands easily to your BattlEye server, including Arma 3, Arma 2 and Arma 2: OA game servers.",
6 | "require": {
7 | "php": ">=5.4.0"
8 | },
9 | "license": "MIT",
10 | "authors": [
11 | {
12 | "name": "Felix Schäfer",
13 | "role": "Developer"
14 | }
15 | ],
16 | "keywords": [
17 | "rcon",
18 | "battleye",
19 | "client",
20 | "arma3",
21 | "arma2",
22 | "dayz",
23 | "lightweight",
24 | "class"
25 | ],
26 | "autoload": {
27 | "files": [
28 | "arc.php"
29 | ]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------