Austinb / GameQ

A PHP Gameserver Status Query Library
https://austinb.github.io/GameQ/
GNU Lesser General Public License v3.0
404 stars 136 forks source link

Add ETQW #355

Closed TacTicToe66 closed 7 years ago

TacTicToe66 commented 7 years ago

`<?php /**

namespace GameQ\Protocols;

use GameQ\Protocol; use GameQ\Buffer; use GameQ\Result; use GameQ\Exception\Protocol as Exception;

class Etqw extends Protocol {

protected $packets = [
 self::PACKET_STATUS => "\xFF\xFFgetInfoEx\x00\x00\x00\x00",
 self::PACKET_STATUS => "\xFF\xFFgetInfo\x00\x00\x00\x00\x00",
];

protected $process_methods = [
    "process_status",
];

protected $responses = [
    "processResponse",
];

protected $join_link = null;

/**
 * Default port for this server type
 *
 * @var int
 */
protected $port = 27733; // Default port, used if not set when instanced

/**
 * The protocol being used
 *
 * @var string
 */
protected $protocol = 'etqw';

/**
 * String name of this protocol class
 *
 * @var string
 */
protected $name = 'etqw';

/**
 * Longer string name of this protocol class
 *
 * @var string
 */
protected $name_long = "Enemy Territory: Quake Wars";

/*
 * Internal methods
*/

protected function preProcess_status($packets)
{
    // Should only be one packet
    if (count($packets) > 1)
    {
        throw new ProtocolsException('Enemy Territory: Quake Wars status has more than 1 packet');
    }

    // Make buffer so we can check this out
    $buf = new Buffer($packets[0]);

    // Grab the header
    $header = $buf->readString();

    // Now lets verify the header
    if(!strstr($header, 'infoExResponse'))
    {
        throw new ProtocolsException('Unable to match Enemy Territory: Quake Wars response header. Header: '. $header);
        return FALSE;
    }

    // Return the data with the header stripped, ready to go.
    return $buf->getBuffer();
}

/**
 * Process the server status
 *
 * @throws ProtocolsException
 */
protected function process_status()
{
    // Make sure we have a valid response
    if(!$this->hasValidResponse(self::PACKET_STATUS))
    {
        return array();
    }

    // Set the result to a new result instance
    $result = new Result();

    // Lets pre process and make sure these things are in the proper order by id
    $data = $this->preProcess_status($this->packets_response[self::PACKET_STATUS]);

    // Make buffer
    $buf = new Buffer($data);

    // Now burn the challenge, version and size
    $buf->skip(16);

    // Key / value pairs
    while ($buf->getLength())
    {
        $var = str_replace('si_', '', $buf->readString());
        $val = $buf->readString();

        if (empty($var) && empty($val))
        {
            break;
        }

        // Add the server prop
        $result->add($var, $val);
    }

    // Now let's do the basic player info
    $this->parsePlayers($buf, $result);

    // Now grab the rest of the server info
    $result->add('osmask',     $buf->readInt32());
    $result->add('ranked',     $buf->readInt8());
    $result->add('timeleft',   $buf->readInt32());
    $result->add('gamestate',  $buf->readInt8());
    $result->add('servertype', $buf->readInt8());

    // 0: regular server
    if ($result->get('servertype') == 0)
    {
        $result->add('interested_clients', $buf->readInt8());
    }
    // 1: tv server
    else
    {
        $result->add('connected_clients', $buf->readInt32());
        $result->add('max_clients',       $buf->readInt32());
    }

    // Now let's parse the extended player info
    $this->parsePlayersExtra($buf, $result);

    // Free some memory
    unset($sections, $buf, $data);

    // Return the result
    return $result->fetch();
}

/**
 * Parse the players and add them to the return.
 *
 * @param Buffer $buf
 * @param Result $result
 */
protected function parsePlayers(Buffer &$buf, Result &$result)
{
    $players = 0;

    while (($id = $buf->readInt8()) != 32)
    {
        $result->addPlayer('id',           $id);
        $result->addPlayer('ping',         $buf->readInt16());
        $result->addPlayer('name',         $buf->readString());
        $result->addPlayer('clantag_pos',  $buf->readInt8());
        $result->addPlayer('clantag',      $buf->readString());
        $result->addPlayer('bot',          $buf->readInt8());

        $players++;
    }

    // Let's add in the current players as a result
    $result->add('numplayers', $players);

    // Free some memory
    unset($id);
}

/**
 * Parse the players extra info and add them to the return.
 *
 * @param Buffer $buf
 * @param Result $result
 */
protected function parsePlayersExtra(Buffer &$buf, Result &$result)
{
    while (($id = $buf->readInt8()) != 32)
    {
        $result->addPlayer('total_xp',     $buf->readFloat32());
        $result->addPlayer('teamname',     $buf->readString());
        $result->addPlayer('total_kills',  $buf->readInt32());
        $result->addPlayer('total_deaths', $buf->readInt32());
    }

    // @todo: Add team stuff

    // Free some memory
    unset($id);
}

} `

Not sure why it doesnt work. This is the error I am getting.

Fatal error: Class GameQ\Protocols\Etqw contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (GameQ\Protocol::processResponse) in /var/www/gameservertrack/GameQ/Protocols/Etqw.php on line 28

Kurounin commented 7 years ago

You need to implement

    /**
     * Method called to process query response data.  Each extending class has to have one of these functions.
     *
     * @return mixed
     */
    public function processResponse();
TacTicToe66 commented 7 years ago

Thank you. Can you please tell me how to get the info being queried for this function? Everything I have tried thus far results in showing the server as offline.

Thanks!

Austinb commented 7 years ago

@TacTicToe66 You are using a lot of the old functionality from v2 that doesn't really translate into v3. The v3 library was made more modular and slimmed things down a bit at the protocol class level. If you want to toy with this one I would use one of the v3 quake libraries as a starting point so you can see how the calls are made. I would not look at the v2 protocol class until you are ready to actually read the data out of the responses for server info, players, etc.. A lot of the v2 code can be reused from that point forward with some minor variable changes.

The majority of the work can be done in the processResponse() call that has to be built for every class extending the base Protocol class. The data coming back from the queries is stored in $this->packets_response which is where the work begins. I use the processResponse function to check the packets, decrypt, sort/merge them if needed and then offload the specific calls for player info, server info, etc... into other functions in the same class. This also allows specific calls for info to be overridden which comes in handy for some Source games when they decide not to follow the A2S protocol specification.