• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

OTS Status Protocol Reader

gudan garam

Advanced OT User
Joined
Feb 13, 2011
Messages
353
Solutions
17
Reaction score
173
Location
São Paulo.
Sometime ago I was roaming on otland and found this topic.

It is gesior's version of a players list reader that reads from the status protocol of TFS.

His version could only get the players list, so I finished it and it is now possible to read m more info the status protocol gives about the server (I have not implemented REQUEST_PLAYER_STATUS_INFO which is for looking if a certain player is online). (credits for the base code for Gesior, ofc).

You can change the $packets array to only get the data you want.

This is a sample output:
1587920586743.png

This script is more like a prof of conecpt, it helped me understand this protocol better and I hope it helps you aswell. I haven't been able to parse the software info response, I also haven't looked deep into it, it can probably something simple to fix idk.

Code:
PHP:
<?php
class OTStatusParser
{
    public $players = array();
    public $playersCount = 0;
    public $statusBinaryData = '';
    public $basicServerInfo = '';
    public $ownerInfo = '';
    public $miscInfo = '';
    public $mapInfo = '';
    public $softwareInfo = '';
    public $playersInfo = '';
/*
basic binary decoder from ItemAttributes of gesior acc. maker 2012
START
*/
    public function __construct($statusBinaryData)
    {
        $this->statusBinaryData = $statusBinaryData;
        $this->parseData();
    }

    private function getByte()
    {
        $ret = ord($this->statusBinaryData[0]);
        $this->statusBinaryData = substr($this->statusBinaryData, 1);
        return $ret;
    }

    private function getU16()
    {
        $ret = ord($this->statusBinaryData[0]) + ord($this->statusBinaryData[1]) * 256;
        $this->statusBinaryData = substr($this->statusBinaryData, 2);
        return $ret;
    }

    private function getU32()
    {
        $ret = ord($this->statusBinaryData[0]) + ord($this->statusBinaryData[1]) * 256 + ord($this->statusBinaryData[2]) * 65536 + ord($this->statusBinaryData[3]) * 16777216;
        $this->statusBinaryData = substr($this->statusBinaryData, 4);
        return $ret;
    }

    private function getString()
    {
        $length = $this->getU16();
        $ret = substr($this->statusBinaryData, 0, $length);
        $this->statusBinaryData = substr($this->statusBinaryData, $length);
        return $ret;
    }

    public function parseData()
    {
        if(!empty($this->statusBinaryData))
        {
            while (!empty($this->statusBinaryData)){
                $firstByte = $this->getByte(); // first byte of players list is 0x21
                switch ($firstByte) {
                    case 33: // player list
                        $this->playersCount = $this->getU32(); // then players count in U32
                        $i = 0;
                        while($i < $this->playersCount) // now read all players
                        {
                            $playerName = $this->getString(); // get string, string 'length' is in U16 before chain of letters [tfs format]
                            $level = $this->getU32(); // get level of player, its in U32
                            // add player to list
                            $this->players[] = array('name' => $playerName, 'level' => $level);
                            $i++;
                        }
                        break;

                    case 16:
                        $this->basicServerInfo .= "[Name] \t\t {$this->getString()}" . PHP_EOL;
                        $this->basicServerInfo .= "[IP] \t\t {$this->getString()}" . PHP_EOL;
                        $this->basicServerInfo .= "[Login Port] \t {$this->getString()}" . PHP_EOL;
                        break;

                    case 17:
                        $this->ownerInfo .= "[Owner Name] \t {$this->getString()}" . PHP_EOL;
                        $this->ownerInfo .= "[Owner Email] \t {$this->getString()}" . PHP_EOL;
                        break;

                    case 18:
                        $this->miscInfo .= "[MOTD] \t \t {$this->getString()}" . PHP_EOL;
                        $this->miscInfo .= "[Location] \t {$this->getString()}" . PHP_EOL;
                        $this->miscInfo .= "[URL] \t \t {$this->getString()}" . PHP_EOL;
                        $this->getU32();
                        $this->getU32();
                        break;

                    case 32:
                        $this->playersInfo .= "[Online Now] \t {$this->getU32()}" . PHP_EOL;
                        $this->playersInfo .= "[Max Players] \t {$this->getU32()}" . PHP_EOL;
                        $this->playersInfo .= "[Record] \t {$this->getU32()}" . PHP_EOL;
                        break;

                    case 48:
                        $this->mapInfo .= "[Name] \t\t {$this->getString()}" . PHP_EOL;
                        $this->mapInfo .= "[Author] \t {$this->getString()}" . PHP_EOL;
                        $this->mapInfo .= "[Size] \t\t {$this->getU16()}x{$this->getU16()}" . PHP_EOL;
                        break;

                    case 43:
                        $this->softwareInfo .= "[Name] \t\t {$this->getString()}" . PHP_EOL;
                        $this->softwareInfo .= "[Version] \t {$this->getString()}" . PHP_EOL;
                        $this->softwareInfo .= "[Version Str] \t {$this->getString()}" . PHP_EOL;
                        break;
                }
            }

        }
    }

    public function getPlayers()
    {
        return $this->players;
    }

    public static function removeLength($data) {
        return substr($data, 2);
    }
}

$packets = [
    // 'Players list info' => chr(6).chr(0).chr(255).chr(1).chr(32).chr(0).chr(0).chr(0),  uncomment this line if you want a online playrs list (big output)
    'basic server info' => chr(6).chr(0).chr(255).chr(1).chr(1).chr(0).chr(0).chr(0),
    'server owner info' => chr(6).chr(0).chr(255).chr(1).chr(2).chr(0).chr(0).chr(0),
    'misc server info' => chr(6).chr(0).chr(255).chr(1).chr(4).chr(0).chr(0).chr(0),
    'players info' => chr(6).chr(0).chr(255).chr(1).chr(8).chr(0).chr(0).chr(0),
    'server map info' => chr(6).chr(0).chr(255).chr(1).chr(16).chr(0).chr(0).chr(0),
];


/*
---------
S T A R T
O F
C O D E
---------
*/

try {

    $ip = 'new.cronara.com';
    $port = 7171;
    $resp = '';

        foreach ($packets as $p => $toSend) {
            $sock = @fsockopen($ip, $port, $errno, $errstr, 1);
            if ($sock) {
                echo "[". date("Y/m/d h:i:sa") ."] Getting $p." . PHP_EOL;
                fwrite($sock, $toSend);
                $answer = '';
                while (!feof($sock)) // read text from server as long as there is anything to read
                {
                    $answer .= fgets($sock, 1024);
                }

                $answer = OTStatusParser::removeLength($answer);

                $resp .= $answer;
                fclose($sock);
                if ($p < count($packets) - 1) {
                    sleep(6); // We need to wait 5 seconds or else the server will disconnect us.
                }
            }
          
        }

        echo "[". date("Y/m/d h:i:sa") ."] Parsing the information received." . PHP_EOL . PHP_EOL;


      

        $spl = new OTStatusParser($resp);
        foreach($spl->getPlayers() as $player)
        {
            echo 'Name: ' . htmlspecialchars($player['name']) . ', level: ' . htmlspecialchars($player['level']) . PHP_EOL;
        }

        //if (!empty($spl->basicServerInfo)) {
            echo "> Basic Server Info: " . PHP_EOL;
            echo $spl->basicServerInfo;
            echo PHP_EOL;
        //}
      

       // if (!empty($spl->basicServerInfo)) {
            echo "> Misc Info: " . PHP_EOL;
            echo $spl->miscInfo;
               echo PHP_EOL;
       // }

      //  if (!empty($spl->basicServerInfo)) {
            echo "> Players Info: " . PHP_EOL;
            echo $spl->playersInfo;
            echo PHP_EOL;
       // }

       // if (!empty($spl->basicServerInfo)) {
            echo "> Owner Info: " . PHP_EOL;
            echo $spl->ownerInfo;
            echo PHP_EOL;
       // }


      //  if (!empty($spl->basicServerInfo)) {
            echo "> Map Info: " . PHP_EOL;
            echo $spl->mapInfo; 
            echo PHP_EOL;
       // }

       // if (!empty($spl->basicServerInfo)) {
            echo "> Software Info: " . PHP_EOL;
            echo $spl->softwareInfo; 
            echo PHP_EOL;
      //  }

} catch (\Exception $e) {
    var_dump($e->getMessage());
}

If you want to create your own otservlist, you can use this to base your system.
 

Nice!

Looking into that implementation, it uses the XML opcode, the one I shared uses the binary reponse opcode. But obviously, his code is way better.
 
Last edited:
Back
Top