Search code examples
phpsocketsraw-sockets

PHP - Preparing a packet for TCP Socket


I'm trying to write a PHP TCP client to connect to an Unreal Engine 5 RCON server. I'm using an existing C# project on GitHub as a reference but I'm having trouble preparing packets to send. The GitHub project prepares a byte array which is then sent to the server. I've tried to re-write the function in PHP but I'm not sure I've done it correctly. But the bigger problem is socket_write() socket_send() and fwrite() all want a string and I now have an array. I believe I can format my array as a string to represent the bytes using pack() but I'm not sure how.

Here is the C# function from https://github.com/Sp1rit/ArkRcon/blob/master/Rcon/RconPacket.cs So this shows what I need to send.

    public static implicit operator byte[](RconPacket packet)
    {
        byte[] buffer = new byte[packet.Size + 4];

        packet.Size.ToLittleEndian().CopyTo(buffer, 0);
        packet.Id.ToLittleEndian().CopyTo(buffer, 4);
        ((int)packet.Type).ToLittleEndian().CopyTo(buffer, 8);
        Encoding.ASCII.GetBytes(packet.Body).CopyTo(buffer, 12);

        return buffer;
    }

Here is my attempt at converting that (See ConvertToArray() below). I did use __invoke but as there's also a reverse function and it wouldn't let me use __invoke twice. I've provided the whole class so you can see the full picture and you can see some commented code where I've tried different things.

class RconPacket
{

    private static int $idCounter = 1;

    public $Id;
    public int $Type;
    public $Body;

    public function GetSize()
    {
        return (!empty($Body) ? strlen($Body) + 10 : 0);
    }

    public function __construct($type, $body)
    {
        $this->Id = RconPacket::$idCounter++;
        $this->Type = $type;
        $this->Body = $body;
    }

    public function ConvertToArray(): array
    {
        $buffer = array(); //array_fill(0, $this->GetSize() + 4, 0);
        $buffer[] = pack('V', $this->GetSize() + 4); //->copy($buffer, 0);
        $buffer[] = pack('V', $this->Id); //->copy($buffer, 4);
        $buffer[] = pack('V', (int)$this->Type); //->copy($buffer, 8);
        $buffer = array_merge($buffer, array_map(fn($c) => ord($c), str_split($this->Body)));
    
        //$buffer[] = str_split($this->Body)->map(fn($c, $i) => ord($c)); //->copy($buffer, 12);

        return $buffer;
    }

    public static function ConvertToRconPacket(string $data): RconPacket
    {
        $size = unpack('V', $data)[1];
        $id = unpack('V', $data, 4)[1];
        $type = unpack('V', $data, 8)[1];
        $body = substr($data, 12, $size - 10);

        return new RconPacket($id, $type, $body);
    }
}

I've tried converting the array to a string using pack("a", $buffer); but pack() also requires a string


Solution

  • A basic knowledge: string in PHP is actually a binary array (e.g. $str[3] is the 4th byte), so you don't have to use a PHP array, you can just use the string packed by the pack function.

    $data = pack('V3a*',
                 $this->GetSize(),
                 $this->Id,
                 (int)$this->Type,
                 $this->Body);
    fwrite($fp, $data);