From 602e2abba3b4e55f73ebec9c14cf87a893f2c638 Mon Sep 17 00:00:00 2001 From: Thomas Debesse Date: Thu, 12 Feb 2026 21:25:09 +0100 Subject: [PATCH] Despecialize the master protocol definition and the string parsing - despecialize the master protocol definition and the string parsing - also implement World of Padman as an example --- engine.php | 136 ++++++++++++++++++++++++++++++++------------ string.php | 162 +++++++++++++++++++++++++++++------------------------ 2 files changed, 189 insertions(+), 109 deletions(-) diff --git a/engine.php b/engine.php index d4d5ded..065d12a 100644 --- a/engine.php +++ b/engine.php @@ -29,6 +29,11 @@ class Protocol public $masters = []; public $scheme = null; public $url_prefix = null; + /* game is a string used for querying the master server + like: getserversExt Xonotic 3 empty full + It is not an user interface string. */ + public $game = null; + public $protocol_num = null; public $string; /** @@ -58,33 +63,7 @@ function default_masters() } } -class Darkplaces_Protocol extends Protocol -{ - public $responses = array( - "rcon" => "n", - "srcon" => "n", - "getchallenge" => "challenge ", - "getinfo" => "infoResponse\n", - "getstatus" => "statusResponse\n", - ); - public $receive_len = 1399; - public $default_port = 26000; - - function __construct() - { - $this->string = new DarkplacesStringParser(); - } - - function normalize_status($status_array) - { - $status_array["server.name"] = $status_array["hostname"]; - $status_array["server.game"] = $status_array["gamename"]; - $status_array["server.version"] = $status_array["gameversion"]; - return $status_array; - } -} - -class Daemon_Protocol extends Protocol +class Q3_Protocol extends Protocol { public $responses = array( "rcon" => "print\n", @@ -95,16 +74,16 @@ class Daemon_Protocol extends Protocol ); public $receive_len = 32768; public $default_port = 27960; - public $scheme = "unv"; - public $url_prefix = "https://play.unvanquished.net/"; + public $protocol_num = 68; public $masters = [ - ["master.unvanquished.net", 27950], - ["master2.unvanquished.net", 27950], + ["master3.idsoftware.com", 27950], + ["master.quake3arena.com", 27950], + ["master.ioquake3.org", 27950], ]; function __construct() { - $this->string = new DaemonStringParser(); + $this->string = new Q3StringParser(); } function normalize_status($status_array) @@ -130,12 +109,17 @@ function server_list($address = null) } } - $game = "UNVANQUISHED"; - $protocol = 86; $extra_flags = "empty full"; $read_size = 1024; - $request = "{$this->header}getserversExt $game $protocol $extra_flags"; + if ( $game == null ) + { + $request = "{$this->header}getserversExt $this->game $this->protocol_num $extra_flags"; + } + else + { + $request = "{$this->header}getserversExt $this->protocol_num $extra_flags"; + } $socket = new EngineSocket(); $socket->write($address, $request); @@ -241,6 +225,80 @@ private function parse_master_response($data, &$index, &$count, &$servers) } } +class Darkplaces_Protocol extends Q3_Protocol +{ + public $responses = array( + "rcon" => "n", + "srcon" => "n", + "getchallenge" => "challenge ", + "getinfo" => "infoResponse\n", + "getstatus" => "statusResponse\n", + ); + public $receive_len = 1399; + public $default_port = 26000; + public $protocol_num = 3; + public $masters = [ + ["dpmaster.deathmask.net", 27950], + ["dpmaster.tchr.no", 27950], + ]; + + function __construct() + { + $this->string = new DarkplacesStringParser(); + } + + function normalize_status($status_array) + { + $status_array["server.name"] = $status_array["hostname"]; + $status_array["server.game"] = $status_array["gamename"]; + $status_array["server.version"] = $status_array["gameversion"]; + return $status_array; + } +} + +class Daemon_Protocol extends Q3_Protocol +{ + public $protocol_num = 86; + + function __construct() + { + $this->string = new DaemonStringParser(); + } +} + +class Xonotic_Protocol extends Darkplaces_Protocol +{ + public $game = "Xonotic"; +} + +class Unvanquished_Protocol extends Daemon_Protocol +{ + // Unvanquished sets no $game query field. + public $scheme = "unv"; + public $url_prefix = "https://play.unvanquished.net/"; + public $masters = [ + ["master.unvanquished.net", 27950], + ["master2.unvanquished.net", 27950], + ]; + + function __construct() + { + $this->string = new UnvanquishedStringParser(); + } +} + +class WoP_Protocol extends Q3_Protocol +{ + public $game = "WorldofPadman"; + public $scheme = "worldofpadman"; + public $url_prefix = "worldofpadman://connect/"; + public $protocol_num = 71; + public $masters = [ + ["master.worldofpadman.net", 27955], + ["master.worldofpadman.com", 27955], + ]; +} + class Engine_Address { public $protocol; @@ -277,7 +335,13 @@ static function parse($url) static function parse_scheme($name) { if ( $name == "unv" ) - return new Daemon_Protocol(); + return new Unvanquished_Protocol(); + if ( $name == "worldofpadman" ) + return new WoP_Protocol(); + /* This $scheme doesn't exit, but the current implementation requires + one for the [xon_master_list master_protocol="xonotic"] syntax. */ + if ( $name == "xonotic" ) + return new Xonotic_Protocol(); return new Darkplaces_Protocol(); } diff --git a/string.php b/string.php index 4497ebf..7625fe2 100644 --- a/string.php +++ b/string.php @@ -283,6 +283,30 @@ class StringParser protected $subject; protected $output; + static public $color_table_is_float = false; + static public $color_table = []; + + public function indexed_color($table_index) + { + if ( static::$color_table_is_float ) + { + $float_color = static::$color_table[$table_index]; + return new Color( + (int)($float_color[0] * 255), + (int)($float_color[1] * 255), + (int)($float_color[2] * 255), + ); + } + else + { + $color = static::$color_table[$table_index]; + return new Color( + $color[0], + $color[1], + $color[2], + ); + } + } function __construct(Color $default_color = null) { @@ -324,28 +348,6 @@ protected function append_string($string) protected function lex_caret(&$i) { - if ( $i + 1 >= strlen($this->subject) ) - { - $this->append_string("^"); - $i++; - return true; - } - if ( strtolower($this->subject[$i+1]) == "x" && $i + 4 < strlen($this->subject) ) - { - $this->push_color(new Color( - hexdec($this->subject[$i+2]) * 0x11, - hexdec($this->subject[$i+3]) * 0x11, - hexdec($this->subject[$i+4]) * 0x11 - )); - $i += 5; - return true; - } - if ( $this->subject[$i+1] == "^" ) - { - $this->append_string("^"); - $i += 2; - return true; - } return false; } @@ -390,7 +392,66 @@ function to_html($string) } } -class DaemonStringParser extends StringParser +class Q3StringParser extends StringParser +{ + static public $color_table = [ + '0' => [ 0, 0, 0], + '1' => [255, 0, 0], + '2' => [ 0, 255, 0], + '3' => [255, 255, 0], + '4' => [ 0, 0, 255], + '5' => [ 0, 255, 255], + '6' => [255, 0, 255], + '7' => [255, 255, 255], + '8' => [136, 136, 136], + '9' => [204, 204, 204], + ]; + + protected function lex_caret(&$i) + { + $index = strtoupper($this->subject[$i+1]); + if ( isset(static::$color_table[$index]) ) + { + $this->push_color($this->indexed_color($index)); + $i += 2; + return true; + } + if ( $i + 1 >= strlen($this->subject) ) + { + $this->append_string("^"); + $i++; + return true; + } + if ( $this->subject[$i+1] == "^" ) + { + $this->append_string("^"); + $i += 2; + return true; + } + return false; + } +} + +class RGBStringParser extends Q3StringParser +{ + protected function lex_caret(&$i) + { + if ( parent::lex_caret($i) ) + return true; + if ( strtolower($this->subject[$i+1]) == "x" && $i + 4 < strlen($this->subject) ) + { + $this->push_color(new Color( + hexdec($this->subject[$i+2]) * 0x11, + hexdec($this->subject[$i+3]) * 0x11, + hexdec($this->subject[$i+4]) * 0x11 + )); + $i += 5; + return true; + } + } +} + +class DaemonStringParser extends RGBStringParser { # Taken from Color.cpp, replacing # \s+\{ ([0-9.]+)f, ([0-9.]+)f, ([0-9.]+)f, 1.00f \}, // (.).* @@ -431,15 +492,7 @@ class DaemonStringParser extends StringParser 'O' => [1.00, 1.00, 0.50], ]; - private function indexed_color($table_index) - { - $float_color = self::$color_table[$table_index]; - return new Color( - (int)($float_color[0] * 255), - (int)($float_color[1] * 255), - (int)($float_color[2] * 255) - ); - } + static public $color_table_is_float = true; protected function lex_caret(&$i) { @@ -463,16 +516,11 @@ protected function lex_caret(&$i) $i += 8; return true; } - - $index = strtoupper($this->subject[$i+1]); - if ( isset(self::$color_table[$index]) ) - { - $this->push_color($this->indexed_color($index)); - $i += 2; - return true; - } } +} +class UnvanquishedStringParser extends DaemonStringParser +{ protected function lex_char(&$i) { if ( $this->subject[$i] != "[" ) @@ -492,44 +540,12 @@ protected function lex_char(&$i) return false; } - } -class DarkplacesStringParser extends StringParser +class DarkplacesStringParser extends RGBStringParser { static public $convert_qfont = true; - protected function indexed_color($index) - { - switch ( (int)$index ) - { - case 0: return new Color( 0, 0, 0); - case 1: return new Color(255, 0, 0); - case 2: return new Color( 0,255, 0); - case 3: return new Color(255,255, 0); - case 4: return new Color( 0, 0,255); - case 5: return new Color( 0,255,255); - case 6: return new Color(255, 0,255); - case 7: return new Color(255,255,255); - case 8: return new Color(136,136,136); - case 9: return new Color(204,204,204); - } - return null; - } - - protected function lex_caret(&$i) - { - if ( parent::lex_caret($i) ) - return true; - - if ( is_numeric($this->subject[$i+1]) ) - { - $this->push_color($this->indexed_color($this->subject[$i+1])); - $i += 2; - return true; - } - } - protected function lex_char(&$i) { if ( !self::$convert_qfont )