├── 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 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/f42d50a9693b4febb34fab3f68315365)](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 | [![Packagist Version](https://img.shields.io/packagist/v/nizarii/arma-rcon-class.svg)](https://packagist.org/packages/nizarii/arma-rcon-class) 4 | [![GitHub License](https://img.shields.io/github/license/nizarii/arma-rcon-class-php.svg)](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 | --------------------------------------------------------------------------------