felixms / arma-rcon-class-php

A lightweight client for sending commands easily to a BattlEye server.
MIT License
46 stars 22 forks source link

Add methods for keeping the connection alive #31

Closed felixms closed 6 years ago

felixms commented 6 years ago

Adding new methods to the class in order to keep a connection to the server alive. This is useful for receiving the chat and log stream.

Pioneering work done by @nerdalertdk,@steffalon, @0FaKE - see #30.

steffalon commented 6 years ago

I made my code more understandable for the documentation from BattlEye. Also I made many comments so editing this code much more easy.

If you notice, I made a switch right now. This makes much more sense what will happen with the hexbyte data.

I have added debug mode if some users don't want to get bothered with developer echo's or vardump.

I'm less concerned about the "reconnect if timeout" function because the connection is already constant and wont disconnect if the server socket is always reachable and now what I'm concerned about is that "multiple packages" data are abit broken in my code. Sometimes it works and sometimes it doesnt'. What I know right now is that it depends on package size that got send back from requested command by the user.

Reconnect function will coming soon.

<?php

require 'vendor/autoload.php'; //Require ReactPHP in composer route

$ip = ""; //Ip here [127.0.0.1]
$port = ; //Port here [2022]
$pass = ""; //Password here

define("debug", true); //Debug option for developers
if (debug) echo "Debug mode turned on!";

$msgseq = 0; //Do not change this variable. Important for sequence steps.

// Login package
$authCRC = crc32(chr(255).chr(00).trim($pass));
$authCRC = sprintf("%x", $authCRC);
$authCRC = array(substr($authCRC,-2,2),substr($authCRC,-4,2),substr($authCRC,-6,2),substr($authCRC,0,2));
$loginmsg = "BE".chr(hexdec($authCRC[0])).chr(hexdec($authCRC[1])).chr(hexdec($authCRC[2])).chr(hexdec($authCRC[3]));
$loginmsg .= chr(hexdec('ff')).chr(hexdec('00')).$pass;

// -- ReactPHP setup --
$loop = React\EventLoop\Factory::create();
$factory = new React\Datagram\Factory($loop);
$source = new React\Stream\ReadableResourceStream(fopen('php://stdin', 'r'), $loop); //Listen to PHP CLI
// -- End of setup for ReactPHP --

$factory->createClient($ip.":".$port)->then(function (React\Datagram\Socket $client) use($loop, $loginmsg, $source) {
    $client->send($loginmsg); //Login

    $source->on('data', function ($msg) use($client, $loop) {
        $msgCRC = crc32(chr(255).chr(01).chr(hexdec(sprintf('%01b',0))).$msg);
        $msgCRC = sprintf("%x", $msgCRC);
        $msgCRC = array(substr($msgCRC,-2,2),substr($msgCRC,-4,2),substr($msgCRC,-6,2),substr($msgCRC,0,2));
        $saymsg = "BE".chr(hexdec($msgCRC[0])).chr(hexdec($msgCRC[1])).chr(hexdec($msgCRC[2])).chr(hexdec($msgCRC[3]));
        $saymsg .= chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('%01b',0))).$msg;
        $client->send($saymsg);
    });

    $client->on('message', function($message) use ($client, $loop) {
        echo substr($message,9). PHP_EOL;
        $responseCode = unpack('H*', $message); //Make message usefull for battleye packet by unpacking it to hexbyte.
        $responseCode = str_split(substr($responseCode[1], 12), 2); //Get important hexbytes.
        global $msgseq; //Sequence varible outside this scope.
        switch ($responseCode[1]) { //See https://www.battleye.com/downloads/BERConProtocol.txt for packet info.
            case "00": //Login
                echo "Logged in as ";
                break;
            case "01": //Send commands by this client.
                if (count($responseCode) == 3) {
                    break;
                }
                if ($responseCode[3] !== "00") {
                    if (debug) echo "This is a small package. No further actions needed.".PHP_EOL; //This package is not special. Allow to be continued.
                } else {
                    if (debug) echo "Multi-packet! This part could go wrong because its not finished yet.".PHP_EOL; //This package needs support.
                    if (debug) var_dump($responseCode); //Useful developer information.
                    if ($responseCode[5] == "00") {
                        $getAmount = $responseCode[4];
                        if (debug) var_dump($getAmount);
                    }
                }
                break;
            case "02": //acknowledge as client.
                if (debug) echo "acknowledge!".PHP_EOL;  
                $needBuffer = chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $msgseq)));
                $needBuffer = hash("crc32b", $needBuffer);
                $needBuffer = str_split($needBuffer, 2);
                $needBuffer = array_reverse($needBuffer);
                $statusmsg = "BE".chr(hexdec($needBuffer[0])).chr(hexdec($needBuffer[1])).chr(hexdec($needBuffer[2])).chr(hexdec($needBuffer[3]));
                $statusmsg .= chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $msgseq)));
                $client->send($statusmsg);
                $msgseq ++; //Add +1 to sequence.
                break;
        }
    });

    $client->on('error', function() {
        echo 'Something went wront... Check your output(s) that got send to the socket with a valid format.' . PHP_EOL;
    });

    $loop->addPeriodicTimer(25, function () use($client, $loop) { //Send "keep alive" package every x seconds. Do not change it to 45 seconds or more.
        if (debug) echo '--Keep connection alive--'.PHP_EOL; //Remove this line when not debugging.
        $keepalive = "BE".chr(hexdec("be")).chr(hexdec("dc")).chr(hexdec("c2")).chr(hexdec("58"));
        $keepalive .= chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('00')));
        $client->send($keepalive);
    });
});

$loop->run(); //Start ReactPHP UDP stream/loops.
steffalon commented 6 years ago

Send a post to this repository if you are facing problems with multiple packaging. This will help alot because its hard to test it if my server doesn't have any big data to send 😆 .

Edit I think my code works after checking many times multiple packaging acknowledge. Now the reconnect function.

steffalon commented 6 years ago

I have a question. Do I have to modify the whole class to a full stream or just call a method of the class to have a streamed connection?

0FakE commented 6 years ago

@steffalon

I can help you testing and improving the script with lots of full servers to send/recive packets in any way and in any size.

The full list of players command is already very big.

We can also go up to limitations by trying to recive bans.txt file with 387 KB size (~60000 bans)

Im controlling the right servers to test that stuff. :)

Would be cool to hear from you and expanding this project to the next level. There also some things need to be changed in ARC like the player REGEX and so on.

I’m hungry :P

steffalon commented 6 years ago

@0FaKE well I'm highly motivated to make this a perfect tool. Also its good for my knowledge and portfolio work. And I am looking "very much" forward hearing from you about multiple packaging function.

0FakE commented 6 years ago

@steffalon

Sure I’m doing what I can to solve this issue.

Btw. if you have issues to generate multi packet messages you can simply fill up your bans.txt file with around 1000 bans and run command bans to recive them as text.

This will definitely need multi packets, always to test where the issue may is.

steffalon commented 6 years ago

@0FaKE well I think it works. I have tested it but I'm not sure about overloaded packets. But I'm really interested if it works for you too.

0FakE commented 6 years ago

@steffalon

I will be able to test it tomorrow. But I’m running the setup as PHP websocket to be able to let clients (admins) connect to the persistent connection whenever they need it.

But for now please zip me the reactPHP files. The setup is a temporarily one on Windows without composer where the some more test Arma servers are located additionally to the public ones.

steffalon commented 6 years ago

ReactPHP download: https://drive.google.com/open?id=1eF4yH1nRE_Y6B_J8MuP73Jl9QKtIshcz

felixms commented 6 years ago

I have a question. Do I have to modify the whole class to a full stream or just call a method of the class to have a streamed connection? @steffalon

If I understand you right: this issue should be an optional feature, the current api of the class should not be changed, so existing projects don't break. In order to get a constant connection, additional methods should be called.

steffalon commented 6 years ago

I made a "failed to login" responder. Also added if the server doesn't exist, the user will get notified.

I can try to make this script more suitable for websites. Its unloadable for websites atm because PHP won't let users get data if its not fully loaded and ReactPHP keeps in an infinite loading loop. So I have to make an option that they can set their own timeouts. For example, get every 'x' seconds data. So they can add it via AJAX get methode. Or I can make, if script received 3 messages, do timeout.

I was trying to setup a http server from ReactPHP so the script could run in the background and the stream never need to be reconnected and its possible to use websockets from javascript to get data. But I couldn't get it to work for some reason.

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

require 'vendor/autoload.php'; //Require ReactPHP in composer route

$ip = ""; //Ip here [127.0.0.1]
$port = ; //Port here [2022]
$pass = ""; //Password here

define("debug", true); //Debug option for developers
if (debug) echo "Debug mode turned on!";

$msgseq = 0; //Do not change this variable. Important for sequence steps.

// Login package
$authCRC = crc32(chr(255).chr(00).trim($pass));
$authCRC = sprintf("%x", $authCRC);
$authCRC = array(substr($authCRC,-2,2),substr($authCRC,-4,2),substr($authCRC,-6,2),substr($authCRC,0,2));
$loginmsg = "BE".chr(hexdec($authCRC[0])).chr(hexdec($authCRC[1])).chr(hexdec($authCRC[2])).chr(hexdec($authCRC[3]));
$loginmsg .= chr(hexdec('ff')).chr(hexdec('00')).$pass;

// -- ReactPHP setup --
$loop = React\EventLoop\Factory::create();
$factory = new React\Datagram\Factory($loop);
$source = new React\Stream\ReadableResourceStream(fopen('php://stdin', 'r'), $loop); //Listen to PHP CLI
// -- End of setup for ReactPHP --

$factory->createClient($ip.":".$port)->then(function (React\Datagram\Socket $client) use($loop, $loginmsg, $source) {
    $client->send($loginmsg); //Login
    $source->on('data', function ($msg) use($client, $loop) {
        $msgCRC = crc32(chr(255).chr(01).chr(hexdec(sprintf('%01b',0))).$msg);
        $msgCRC = sprintf("%x", $msgCRC);
        $msgCRC = array(substr($msgCRC,-2,2),substr($msgCRC,-4,2),substr($msgCRC,-6,2),substr($msgCRC,0,2));
        $saymsg = "BE".chr(hexdec($msgCRC[0])).chr(hexdec($msgCRC[1])).chr(hexdec($msgCRC[2])).chr(hexdec($msgCRC[3]));
        $saymsg .= chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('%01b',0))).$msg;
        $client->send($saymsg);
    });

    $client->on('message', function($message) use ($client, $loop) {
        echo substr($message,9). PHP_EOL;
        $responseCode = unpack('H*', $message); //Make message usefull for battleye packet by unpacking it to hexbyte.
        $responseCode = str_split(substr($responseCode[1], 12), 2); //Get important hexbytes.
        global $msgseq; //Sequence varible outside this scope.
        switch ($responseCode[1]) { //See https://www.battleye.com/downloads/BERConProtocol.txt for packet info.
            case "00": //Login
                if ($responseCode[2] == "01") { //Login successful.
                    echo "Accepted BERCon login.".PHP_EOL;
                } else { //Otherwise $responseCode[2] == "0x00" (Login failed)
                    echo "Invalid BERCon login details. This process is getting stopped.".PHP_EOL;
                    $loop->stop(); //Stop this loop event.
                }
                break;
            case "01": //Send commands by this client.
                if (count($responseCode) == 3) {
                    break;
                }
                if ($responseCode[3] !== "00") {
                    if (debug) echo "This is a small package. No further actions needed.".PHP_EOL; //This package is not special. Allow to be continued.
                } else {
                    if (debug) echo "Multi-packet.".PHP_EOL; //This package needs support.
                    if (debug) var_dump($responseCode); //Useful developer information.
                    if ($responseCode[5] == "00") {
                        $getAmount = $responseCode[4];
                        if (debug) var_dump($getAmount);
                    }
                }
                break;
            case "02": //acknowledge as client.
                if (debug) echo "Acknowledge!".PHP_EOL;  
                $needBuffer = chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $msgseq)));
                $needBuffer = hash("crc32b", $needBuffer);
                $needBuffer = str_split($needBuffer, 2);
                $needBuffer = array_reverse($needBuffer);
                $statusmsg = "BE".chr(hexdec($needBuffer[0])).chr(hexdec($needBuffer[1])).chr(hexdec($needBuffer[2])).chr(hexdec($needBuffer[3]));
                $statusmsg .= chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $msgseq)));
                $client->send($statusmsg);
                $msgseq ++; //Add +1 to sequence.
                break;
        }
    });

    $client->on('error', function() {
        echo 'Something went wront... Check your output(s) that got send to the socket with a valid format.' . PHP_EOL;
    });

    $loop->addPeriodicTimer(25, function () use($client, $loop) { //Send "keep alive" package every x seconds. Do not change it to 45 seconds or more.
        if (debug) echo '--Keep connection alive--'.PHP_EOL;
        $keepalive = "BE".chr(hexdec("be")).chr(hexdec("dc")).chr(hexdec("c2")).chr(hexdec("58"));
        $keepalive .= chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('00')));
        $client->send($keepalive);
    });
}, function (Throwable $error) use($ip, $port, $loop) {
    echo "Could not connect to udp://$ip:$port server.".PHP_EOL;
    die();
});

$loop->run(); //Start ReactPHP UDP stream/loops.
steffalon commented 6 years ago

I'm testing http server build in ReactPHP so this script would be able to send data to websites.

BERcon.php

<?php

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

require 'vendor/autoload.php'; //Require ReactPHP in composer route

$ip = ""; //Ip here [127.0.0.1]
$port = ; //Port here [2022]
$pass = ""; //Password here

define("debug", false); //Debug option for developers
if (debug) echo "Debug mode turned on!";

$msgseq = 0; //Do not change this variable. Important for sequence steps.
$httpData = "";
// Login package
$authCRC = crc32(chr(255).chr(00).trim($pass));
$authCRC = sprintf("%x", $authCRC);
$authCRC = array(substr($authCRC,-2,2),substr($authCRC,-4,2),substr($authCRC,-6,2),substr($authCRC,0,2));
$loginmsg = "BE".chr(hexdec($authCRC[0])).chr(hexdec($authCRC[1])).chr(hexdec($authCRC[2])).chr(hexdec($authCRC[3]));
$loginmsg .= chr(hexdec('ff')).chr(hexdec('00')).$pass;

// -- ReactPHP setup --
$loop = React\EventLoop\Factory::create();
$factory = new React\Datagram\Factory($loop);
$source = new React\Stream\ReadableResourceStream(fopen('php://stdin', 'r'), $loop); //Listen to PHP CLI
use React\Http\Response;
use React\Http\Server;
use React\Http\StreamingServer;
use Psr\Http\Message\ServerRequestInterface;
// -- End of setup for ReactPHP --

$factory->createClient($ip.":".$port)->then(function (React\Datagram\Socket $client) use($loop, $loginmsg, $source) {
    $client->send($loginmsg); //Login
    $source->on('data', function ($msg) use($client, $loop) {
        $msgCRC = crc32(chr(255).chr(01).chr(hexdec(sprintf('%01b',0))).$msg);
        $msgCRC = sprintf("%x", $msgCRC);
        $msgCRC = array(substr($msgCRC,-2,2),substr($msgCRC,-4,2),substr($msgCRC,-6,2),substr($msgCRC,0,2));
        $saymsg = "BE".chr(hexdec($msgCRC[0])).chr(hexdec($msgCRC[1])).chr(hexdec($msgCRC[2])).chr(hexdec($msgCRC[3]));
        $saymsg .= chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('%01b',0))).$msg;
        $client->send($saymsg);
    });

    $client->on('message', function($message) use ($client, $loop) {
        echo substr($message,9). PHP_EOL;
        global $httpData;
        $httpData = substr($message,9);
        $responseCode = unpack('H*', $message); //Make message usefull for battleye packet by unpacking it to hexbyte.
        $responseCode = str_split(substr($responseCode[1], 12), 2); //Get important hexbytes.
        global $msgseq; //Sequence varible outside this scope.
        switch ($responseCode[1]) { //See https://www.battleye.com/downloads/BERConProtocol.txt for packet info.
            case "00": //Login
                if ($responseCode[2] == "01") { //Login successful.
                    echo "Accepted BERCon login.".PHP_EOL;
                } else { //Otherwise $responseCode[2] == "0x00" (Login failed)
                    echo "Invalid BERCon login details. This process is getting stopped.".PHP_EOL;
                    $loop->stop(); //Stop this loop event.
                }
                break;
            case "01": //Send commands by this client.
                if (count($responseCode) == 3) {
                    break;
                }
                if ($responseCode[3] !== "00") {
                    if (debug) echo "This is a small package. No further actions needed.".PHP_EOL; //This package is not special. Allow to be continued.
                } else {
                    if (debug) echo "Multi-packet.".PHP_EOL; //This package needs support.
                    if (debug) var_dump($responseCode); //Useful developer information.
                    if ($responseCode[5] == "00") {
                        $getAmount = $responseCode[4];
                        if (debug) var_dump($getAmount);
                    }
                }
                break;
            case "02": //acknowledge as client.
                if (debug) echo "Acknowledge!".PHP_EOL;  
                $needBuffer = chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $msgseq)));
                $needBuffer = hash("crc32b", $needBuffer);
                $needBuffer = str_split($needBuffer, 2);
                $needBuffer = array_reverse($needBuffer);
                $statusmsg = "BE".chr(hexdec($needBuffer[0])).chr(hexdec($needBuffer[1])).chr(hexdec($needBuffer[2])).chr(hexdec($needBuffer[3]));
                $statusmsg .= chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $msgseq)));
                $client->send($statusmsg);
                $msgseq ++; //Add +1 to sequence.
                break;
        }
    });

    $client->on('error', function() {
        echo 'Something went wront... Check your output(s) that got send to the socket with a valid format.' . PHP_EOL;
    });

    $loop->addPeriodicTimer(25, function () use($client, $loop) { //Send "keep alive" package every x seconds. Do not change it to 45 seconds or more.
        if (debug) echo '--Keep connection alive--'.PHP_EOL;
        $keepalive = "BE".chr(hexdec("be")).chr(hexdec("dc")).chr(hexdec("c2")).chr(hexdec("58"));
        $keepalive .= chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('00')));
        $client->send($keepalive);
    });
}, function (Throwable $error) use($ip, $port, $loop) {
    echo "Could not connect to udp://$ip:$port server.".PHP_EOL;
    die();
});

$socket = new React\Socket\Server(8082, $loop);
// $socket = new React\Socket\SecureServer($socket, $loop, array(
//     'local_cert' => __DIR__ . '/localhost.pem'
// ));
$server = new StreamingServer(function (ServerRequestInterface $request) {
    global $httpData;
    return new Response(
        200,
        array(
            'Content-Type' => 'text/event-stream',
            'Transfer-Encoding' => 'chunked',
            'Cache-Control' => 'no-cache',
            'Connection' => 'keep-alive',
            'Access-Control-Allow-Origin' =>  '*',
            'Access-Control-Allow-Methods' => 'GET',
            'Access-Control-Allow-Headers' => '*',
            'retry' => '15000'
        ),
        $httpData.PHP_EOL
    );
});
$server->listen($socket);

$loop->run(); //Start ReactPHP UDP stream/loops.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>

    <script>
    $(function() {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'http://127.0.0.1:8082', true);
        xhr.send(null);
        var timer;
        timer = window.setInterval(function() {
            if (xhr.readyState == XMLHttpRequest.DONE) {
                window.clearTimeout(timer);
                $('body').append('done <br />');
            }
            $('body').append('state: ' + xhr.readyState + '<br />');
            console.log(xhr.responseText);
            $('body').append('data: ' + xhr.responseText + '<br />');
        }, 1000);
    });
    </script>
</body>
</html>

After I was building this, I read on ReactPHP documentation that they don't support "keep-alive" HTTP1.1 header stream yet. It goes automaticly on close every time that the user want to read that port.

httpheader

steffalon commented 6 years ago

Nvm about HTTP header stream. It was a good idea if it really works. Now I'm thinking about using this.

Only a huge downside is, some browsers [IE] for example, doesn't support it.

steffalon commented 6 years ago

@schaeferfelix

If I understand you right: this issue should be an optional feature, the current api of the class should not be changed, so existing projects don't break. In order to get a constant connection, additional methods should be called.

But should I make a log file when there is a constant connection? Or trying to stay as commandline interface?

I added SSE mode here but it is useless for me right now because everyone can listen to that script. It is hard to make better security on that one. But if you want to try it, its there.

<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

$ip = ""; //Ip here [127.0.0.1]
$port = ; //Port here [2022]
$pass = ""; //Password here
define("debug", false); //Debug option for developers
define("SSE", false); //SSE option. See https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events for information.

require 'vendor/autoload.php'; //Require ReactPHP in composer route

if (SSE) header('Cache-Control: no-cache');
if (SSE) header("Content-Type: text/event-stream\n\n");
if (debug) echo "Debug mode turned on!\n";
if (SSE) echo "SSE mode active!\n";

$msgseq = 0; //Do not change this variable. Important for sequence steps.

// Login package
$authCRC = crc32(chr(255).chr(00).trim($pass));
$authCRC = sprintf("%x", $authCRC);
$authCRC = array(substr($authCRC,-2,2),substr($authCRC,-4,2),substr($authCRC,-6,2),substr($authCRC,0,2));
$loginmsg = "BE".chr(hexdec($authCRC[0])).chr(hexdec($authCRC[1])).chr(hexdec($authCRC[2])).chr(hexdec($authCRC[3]));
$loginmsg .= chr(hexdec('ff')).chr(hexdec('00')).$pass;

// -- ReactPHP setup --
$loop = React\EventLoop\Factory::create();
$factory = new React\Datagram\Factory($loop);
$source = new React\Stream\ReadableResourceStream(fopen('php://stdin', 'r'), $loop); //Listen to PHP CLI
// -- End of setup for ReactPHP --

$factory->createClient($ip.":".$port)->then(function (React\Datagram\Socket $client) use($loop, $loginmsg, $source) {
    $client->send($loginmsg); //Login

    $source->on('data', function ($msg) use($client, $loop) {
        $msgCRC = crc32(chr(255).chr(01).chr(hexdec(sprintf('%01b',0))).$msg);
        $msgCRC = sprintf("%x", $msgCRC);
        $msgCRC = array(substr($msgCRC,-2,2),substr($msgCRC,-4,2),substr($msgCRC,-6,2),substr($msgCRC,0,2));
        $saymsg = "BE".chr(hexdec($msgCRC[0])).chr(hexdec($msgCRC[1])).chr(hexdec($msgCRC[2])).chr(hexdec($msgCRC[3]));
        $saymsg .= chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('%01b',0))).$msg;
        $client->send($saymsg);
    });

    $client->on('message', function($message) use ($client, $loop) {
        echo substr($message,9). PHP_EOL;
        $responseCode = unpack('H*', $message); //Make message usefull for battleye packet by unpacking it to hexbyte.
        $responseCode = str_split(substr($responseCode[1], 12), 2); //Get important hexbytes.
        global $msgseq; //Sequence varible outside this scope.
        switch ($responseCode[1]) { //See https://www.battleye.com/downloads/BERConProtocol.txt for packet info.
            case "00": //Login
                if ($responseCode[2] == "01") { //Login successful.
                    echo "Accepted BERCon login.".PHP_EOL;
                } else { //Otherwise $responseCode[2] == "0x00" (Login failed)
                    echo "Invalid BERCon login details. This process is getting stopped.".PHP_EOL;
                    $loop->stop(); //Stop this loop event.
                }
                break;
            case "01": //Send commands by this client.
                if (count($responseCode) == 3) {
                    break;
                }
                if ($responseCode[3] !== "00") {
                    if (debug) echo "This is a small package. No further actions needed.".PHP_EOL; //This package is not special. Allow to be continued.
                } else {
                    if (debug) echo "Multi-packet.".PHP_EOL; //This package needs support.
                    // if (debug) var_dump($responseCode); //Useful developer information.
                    // if ($responseCode[5] == "00") {
                    //     $getAmount = $responseCode[4];
                    //     if (debug) var_dump($getAmount);
                    // }
                }
                break;
            case "02": //acknowledge as client.
                if (debug) echo "Acknowledge!".PHP_EOL;  
                $needBuffer = chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $msgseq)));
                $needBuffer = hash("crc32b", $needBuffer);
                $needBuffer = str_split($needBuffer, 2);
                $needBuffer = array_reverse($needBuffer);
                $statusmsg = "BE".chr(hexdec($needBuffer[0])).chr(hexdec($needBuffer[1])).chr(hexdec($needBuffer[2])).chr(hexdec($needBuffer[3]));
                $statusmsg .= chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $msgseq)));
                $client->send($statusmsg);
                $msgseq ++; //Add +1 to sequence.
                break;
        }
        if (php_sapi_name() !== "cli" && SSE) {
            echo 'data: '.substr($message,9)."\n\n";
            flush();
        }
    });

    $client->on('error', function() {
        echo 'Something went wront... Check your output(s) that got send to the socket with a valid format.' . PHP_EOL;
    });

    $loop->addPeriodicTimer(25, function () use($client, $loop) { //Send "keep alive" package every x seconds. Do not change it to 45 seconds or more.
        if (debug) echo '--Keep connection alive--'.PHP_EOL;
        $keepalive = "BE".chr(hexdec("be")).chr(hexdec("dc")).chr(hexdec("c2")).chr(hexdec("58"));
        $keepalive .= chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('00')));
        $client->send($keepalive);
    });
}, function (Throwable $error) use($ip, $port, $loop) {
    echo "Could not connect to udp://$ip:$port server.".PHP_EOL;
    die();
});

$loop->run(); //Start ReactPHP UDP stream/loops.
felixms commented 6 years ago

@steffalon Sorry for the late answer, I am currently on vacation. To answer your question: a log file should not be created by ARC. Because for such a feature, even though it’s not required, a logging library would be appropriate. But ARC should run without any dependencies, so people using ARC the old school way, without composer, don’t run into trouble. Cheers!

steffalon commented 6 years ago

Hey @schaeferfelix,

You said to me that you don't want any dependencies. So I wrote new code for that reason without ReactPHP. If this code doesn't have any issues, we could implement it to your ARC project. The downside of making this code, is that the commandline cannot send commands to the server anymore.


<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

$ip = ""; //Ip here [127.0.0.1]
$port = ; //Port here [2022]
$pass = ""; //Password here
define("debug", false); //Debug option for developers

if (debug) echo "Debug mode turned on!\n";

$msgseq = 0; //Do not change this variable. Important for sequence steps.

// Login package
$authCRC = crc32(chr(255).chr(00).trim($pass));
$authCRC = sprintf("%x", $authCRC);
$authCRC = array(substr($authCRC,-2,2),substr($authCRC,-4,2),substr($authCRC,-6,2),substr($authCRC,0,2));
$loginmsg = "BE".chr(hexdec($authCRC[0])).chr(hexdec($authCRC[1])).chr(hexdec($authCRC[2])).chr(hexdec($authCRC[3]));
$loginmsg .= chr(hexdec('ff')).chr(hexdec('00')).$pass;

if(!($sock = fsockopen("udp://$ip", $port, $errno, $errstr)))
{
    $errorcode = socket_last_error();
    $errormsg = socket_strerror($errorcode);

    die("Couldn't create socket: [$errorcode] $errormsg \n");
}

stream_set_timeout($sock, 25);
stream_set_blocking($sock, true);

echo "Socket created \n";
fwrite($sock, $loginmsg);

//Communication loop
while($sock)
{   
    //Receive some data
    $msg = fread($sock, 5000);
    echo substr($msg, 9).PHP_EOL;
    $info = stream_get_meta_data($sock);
    if ($info['timed_out']) {
        if (debug) echo '--Keep connection alive--'.PHP_EOL;
        $keepalive = "BE".chr(hexdec("be")).chr(hexdec("dc")).chr(hexdec("c2")).chr(hexdec("58"));
        $keepalive .= chr(hexdec('ff')).chr(hexdec('01')).chr(hexdec(sprintf('00')));
        fwrite($sock, $keepalive);
    } else {
        $responseCode = unpack('H*', $msg); //Make message usefull for battleye packet by unpacking it to hexbyte.
        $responseCode = str_split(substr($responseCode[1], 12), 2); //Get important hexbytes.
        switch ($responseCode[1]) { //See https://www.battleye.com/downloads/BERConProtocol.txt for packet info.
            case "00": //Login
                if ($responseCode[2] == "01") { //Login successful.
                    echo "Accepted BERCon login.".PHP_EOL;
                } else { //Otherwise $responseCode[2] == "0x00" (Login failed)
                    echo "Invalid BERCon login details. This process is getting stopped.".PHP_EOL;
                    break 2;
                }
                break;
            case "01": //Send commands by this client.
                if (count($responseCode) == 3) {
                    break;
                }
                if ($responseCode[3] !== "00") {
                    if (debug) echo "This is a small package. No further actions needed.".PHP_EOL; //This package is not special. Allow to be continued.
                } else {
                    if (debug) echo "Multi-packet.".PHP_EOL;
                    // if (debug) var_dump($responseCode); //Useful developer information.
                    // if ($responseCode[5] == "00") {
                    //     $getAmount = $responseCode[4];
                    //     if (debug) var_dump($getAmount);
                    // }
                }
                break;
            case "02": //acknowledge as client.
                if (debug) echo "Acknowledge!".PHP_EOL;  
                $needBuffer = chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $msgseq)));
                $needBuffer = hash("crc32b", $needBuffer);
                $needBuffer = str_split($needBuffer, 2);
                $needBuffer = array_reverse($needBuffer);
                $statusmsg = "BE".chr(hexdec($needBuffer[0])).chr(hexdec($needBuffer[1])).chr(hexdec($needBuffer[2])).chr(hexdec($needBuffer[3]));
                $statusmsg .= chr(hexdec('ff')).chr(hexdec('02')).chr(hexdec(sprintf('%2X', $msgseq)));
                fwrite($sock, $statusmsg);
                $msgseq ++; //Add +1 to sequence.
                break;
        }
    }
}

socket_close($sock);