From cd22acf846f3f2fdbb1aac66b47532fa90f4b25f Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 18:32:51 +0200 Subject: [PATCH 01/26] fix namespaces --- README.md | 2 +- src/Xash3D/Master.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0295195..60d2ee5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ PHP 8 library for Half-Life API with native IPv6 / Yggdrasil support #### Master ``` -$master = new Yggverse\Hl\Xash3d\Master('hl.ygg', 27010); +$master = new \Yggverse\Hl\Xash3D\Master('hl.ygg', 27010); var_dump( $master->getServersIPv6() diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index c748d53..9fc7d18 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Yggverse\Hl\Xash3d; +namespace Yggverse\Hl\Xash3D; class Master { From 49c5c7690886a96ca956f2b8abdfbc4b59249a5e Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 18:36:47 +0200 Subject: [PATCH 02/26] add socket validation --- src/Xash3D/Master.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 9fc7d18..541c421 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -19,10 +19,13 @@ class Master $port ); - stream_set_timeout( - $this->_socket, - $timeout - ); + if ($this->_socket) + { + stream_set_timeout( + $this->_socket, + $timeout + ); + } } public function __destruct() From 6479e4049651c80009878e1c2300ec8f0f0ef18d Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 18:40:21 +0200 Subject: [PATCH 03/26] add socket connection closers --- src/Xash3D/Master.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 541c421..e6a1d0d 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -65,6 +65,10 @@ class Master // Skip header if (!fread($this->_socket, 6)) { + fclose( + $this->_socket + ); + return null; } @@ -76,6 +80,10 @@ class Master // Get host if (false === $host = fread($this->_socket, 16)) { + fclose( + $this->_socket + ); + return null; } @@ -94,12 +102,20 @@ class Master // Decode first byte for port if (false === $byte1 = fread($this->_socket, 1)) { + fclose( + $this->_socket + ); + return null; } // Decode second byte for port if (false === $byte2 = fread($this->_socket, 1)) { + fclose( + $this->_socket + ); + return null; } @@ -123,6 +139,11 @@ class Master ]; } + // Close connection + fclose( + $this->_socket + ); + return $servers; } } \ No newline at end of file From c942dc98d0b689f1bf25807e28c31cd83e7a5c31 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 18:43:05 +0200 Subject: [PATCH 04/26] fix method type --- src/Xash3D/Master.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index e6a1d0d..a234333 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -38,7 +38,7 @@ class Master } } - public static function getServersIPv6( + public function getServersIPv6( int $limit = 100, string $region = "\xFF", string $host = "0.0.0.0:0", From d07ba9565434901b0df1b635dc29df4bd752ed69 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 18:44:56 +0200 Subject: [PATCH 05/26] fix data type --- src/Xash3D/Master.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index a234333..635b57c 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -88,7 +88,7 @@ class Master } // Is end of packet - if (true === str_starts_with($host, 0)) + if (true === str_starts_with($host, (string) 0)) { break; } From b7a5314030f50ddfe136511fe024c0a1610531db Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 18:46:34 +0200 Subject: [PATCH 06/26] remove __destruct method --- src/Xash3D/Master.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 635b57c..d44b5a7 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -28,16 +28,6 @@ class Master } } - public function __destruct() - { - if ($this->_socket) - { - fclose( - $this->_socket - ); - } - } - public function getServersIPv6( int $limit = 100, string $region = "\xFF", From 9c9625cb21a2634194e94249e2b7bc6797fa85d6 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 18:49:53 +0200 Subject: [PATCH 07/26] add resource validation --- src/Xash3D/Master.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index d44b5a7..69c5787 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -19,7 +19,7 @@ class Master $port ); - if ($this->_socket) + if (is_resource($this->_socket)) { stream_set_timeout( $this->_socket, @@ -37,7 +37,7 @@ class Master ): ?array { // Is connected - if (!$this->_socket) + if (!is_resource($this->_socket)) { return null; } From be344abd36bd10493a30851d82bb29dfdc3c0804 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 19:10:43 +0200 Subject: [PATCH 08/26] add errors debug --- README.md | 4 ++++ src/Xash3D/Master.php | 31 ++++++++++++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 60d2ee5..dbba9d4 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,8 @@ $master = new \Yggverse\Hl\Xash3D\Master('hl.ygg', 27010); var_dump( $master->getServersIPv6() ); + +var_dump( + $master->getErrors() +); ``` \ No newline at end of file diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 69c5787..5c7c58e 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -7,6 +7,7 @@ namespace Yggverse\Hl\Xash3D; class Master { private $_socket; + private $_errors = []; public function __construct( string $host, @@ -16,7 +17,10 @@ class Master { $this->_socket = fsockopen( "udp://{$host}", - $port + $port, + $code, + $message, + $timeout ); if (is_resource($this->_socket)) @@ -26,6 +30,14 @@ class Master $timeout ); } + + else + { + $this->_errors[] = sprintf( + _('Connection error: %s'), + $message + ); + } } public function getServersIPv6( @@ -39,6 +51,8 @@ class Master // Is connected if (!is_resource($this->_socket)) { + $this->_errors[] = _('Socket connection error'); + return null; } @@ -49,6 +63,8 @@ class Master $this->_socket ); + $this->_errors[] = _('Could not send socket query'); + return null; } @@ -59,6 +75,8 @@ class Master $this->_socket ); + $this->_errors[] = _('Could not init packet header'); + return null; } @@ -74,6 +92,8 @@ class Master $this->_socket ); + $this->_errors[] = _('Could not read server address'); + return null; } @@ -96,6 +116,8 @@ class Master $this->_socket ); + $this->_errors[] = _('Could not read first byte of port'); + return null; } @@ -106,6 +128,8 @@ class Master $this->_socket ); + $this->_errors[] = _('Could not read second byte of port'); + return null; } @@ -136,4 +160,9 @@ class Master return $servers; } + + public function getErrors(): array + { + return $this->_errors; + } } \ No newline at end of file From a97a2de1512b0070fef04aaf0709a3067309e771 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 19:29:57 +0200 Subject: [PATCH 09/26] add meta info --- composer.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/composer.json b/composer.json index 55c3533..cf92306 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,8 @@ { "name": "yggverse/hl", "description": "PHP 8 library for Half-Life API with native IPv6 / Yggdrasil support ", + "keywords": [ "half-life", "hl", "xash3d", "ipv6", "yggdrasil", "master" ], + "homepage": "https://github.com/YGGverse/hl-php", "type": "library", "license": "MIT", "autoload": { From 33a77a858b3d7b21f43389074389289cce4be3cf Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 19:34:06 +0200 Subject: [PATCH 10/26] add strict datatype validation --- src/Xash3D/Master.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 5c7c58e..7feadbb 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -49,7 +49,7 @@ class Master ): ?array { // Is connected - if (!is_resource($this->_socket)) + if (false === is_resource($this->_socket)) { $this->_errors[] = _('Socket connection error'); @@ -57,7 +57,7 @@ class Master } // Filter query - if (!fwrite($this->_socket, "1{$region}{$host}:{$port}\0\gamedir\t{$gamedir}\0")) + if (false === fwrite($this->_socket, "1{$region}{$host}:{$port}\0\gamedir\t{$gamedir}\0")) { fclose( $this->_socket @@ -69,7 +69,7 @@ class Master } // Skip header - if (!fread($this->_socket, 6)) + if (false === fread($this->_socket, 6)) { fclose( $this->_socket From 726463e94e88f5aa7bd1052aa7242b5650359760 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 20:06:45 +0200 Subject: [PATCH 11/26] fix packet reader --- src/Xash3D/Master.php | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 7feadbb..8442007 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -88,13 +88,7 @@ class Master // Get host if (false === $host = fread($this->_socket, 16)) { - fclose( - $this->_socket - ); - - $this->_errors[] = _('Could not read server address'); - - return null; + break; } // Is end of packet @@ -103,34 +97,28 @@ class Master break; } - // Skip invalid host + // Skip invalid host value if (false === $host = inet_ntop($host)) { + // Shift port bytes + fread($this->_socket, 2); + continue; } // Decode first byte for port if (false === $byte1 = fread($this->_socket, 1)) { - fclose( - $this->_socket - ); + // Shift port byte + fread($this->_socket, 1); - $this->_errors[] = _('Could not read first byte of port'); - - return null; + continue; } // Decode second byte for port if (false === $byte2 = fread($this->_socket, 1)) { - fclose( - $this->_socket - ); - - $this->_errors[] = _('Could not read second byte of port'); - - return null; + continue; } // Calculate port value From 27fbf92c6b0c60ddf5ed235f27a5574ed400a10d Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 20:09:20 +0200 Subject: [PATCH 12/26] add __destruct method --- src/Xash3D/Master.php | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 8442007..2909d64 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -40,6 +40,13 @@ class Master } } + public function __destruct() + { + fclose( + $this->_socket + ); + } + public function getServersIPv6( int $limit = 100, string $region = "\xFF", @@ -59,10 +66,6 @@ class Master // Filter query if (false === fwrite($this->_socket, "1{$region}{$host}:{$port}\0\gamedir\t{$gamedir}\0")) { - fclose( - $this->_socket - ); - $this->_errors[] = _('Could not send socket query'); return null; @@ -71,10 +74,6 @@ class Master // Skip header if (false === fread($this->_socket, 6)) { - fclose( - $this->_socket - ); - $this->_errors[] = _('Could not init packet header'); return null; @@ -141,11 +140,6 @@ class Master ]; } - // Close connection - fclose( - $this->_socket - ); - return $servers; } From 15035498b3a1388dcb5bc743350d188e6fd5c1c0 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 20:26:51 +0200 Subject: [PATCH 13/26] add socket check --- src/Xash3D/Master.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 2909d64..8f1dc61 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -42,9 +42,12 @@ class Master public function __destruct() { - fclose( - $this->_socket - ); + if (true === is_resource($this->_socket)) + { + fclose( + $this->_socket + ); + } } public function getServersIPv6( From 3ffb314fa4cc5986bc3108030a200bacc84b26c2 Mon Sep 17 00:00:00 2001 From: ghost Date: Fri, 12 Jan 2024 20:28:03 +0200 Subject: [PATCH 14/26] update readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dbba9d4..cd93cb0 100644 --- a/README.md +++ b/README.md @@ -22,4 +22,8 @@ var_dump( var_dump( $master->getErrors() ); -``` \ No newline at end of file +``` + +## Projects + +* [HLState](https://github.com/YGGverse/HLState) - Web Monitor for Half-Life Servers \ No newline at end of file From 550efd120fc4734bf30b2b2ce378edf6eab82c29 Mon Sep 17 00:00:00 2001 From: ghost Date: Sat, 13 Jan 2024 18:36:00 +0200 Subject: [PATCH 15/26] fix packet end condition --- src/Xash3D/Master.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 8f1dc61..70847cd 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -94,7 +94,7 @@ class Master } // Is end of packet - if (true === str_starts_with($host, (string) 0)) + if (true === str_ends_with(bin2hex($host), bin2hex("\0\0\0\0\0\0"))) { break; } From 4f8fffed17d485f8c567ee5651863d3e8f473998 Mon Sep 17 00:00:00 2001 From: ghost Date: Sat, 13 Jan 2024 20:03:23 +0200 Subject: [PATCH 16/26] make socket connections isolated --- src/Xash3D/Master.php | 99 ++++++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 39 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 70847cd..d710254 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -6,8 +6,11 @@ namespace Yggverse\Hl\Xash3D; class Master { - private $_socket; - private $_errors = []; + private string $_host; + private int $_port; + private int $_timeout; + + private array $_errors = []; public function __construct( string $host, @@ -15,37 +18,19 @@ class Master int $timeout = 5 ) { - $this->_socket = fsockopen( - "udp://{$host}", - $port, - $code, - $message, - $timeout - ); - - if (is_resource($this->_socket)) - { - stream_set_timeout( - $this->_socket, - $timeout - ); - } - - else - { - $this->_errors[] = sprintf( - _('Connection error: %s'), - $message - ); - } + $this->_host = $host; + $this->_port = $port; + $this->_timeout = $timeout; } - public function __destruct() + private function _fclose( + mixed $socket + ) { - if (true === is_resource($this->_socket)) + if (true === is_resource($socket)) { fclose( - $this->_socket + $socket ); } } @@ -58,27 +43,59 @@ class Master string $gamedir = "valve" ): ?array { + // Init connection + $socket = fsockopen( + "udp://{$this->_host}", + $this->_port, + $code, + $message, + $this->_timeout + ); + // Is connected - if (false === is_resource($this->_socket)) + if (true === is_resource($socket)) { - $this->_errors[] = _('Socket connection error'); + stream_set_timeout( + $socket, + $this->_timeout + ); + } + + else + { + $this->_errors[] = sprintf( + _('Connection error: %s'), + $message + ); + + $this->_fclose( + $socket + ); return null; } // Filter query - if (false === fwrite($this->_socket, "1{$region}{$host}:{$port}\0\gamedir\t{$gamedir}\0")) + if (false === fwrite($socket, "1{$region}{$host}:{$port}\0\gamedir\t{$gamedir}\0")) { $this->_errors[] = _('Could not send socket query'); + $this->_fclose( + $socket + ); + return null; } // Skip header - if (false === fread($this->_socket, 6)) + if (false === fread($socket, 6)) { $this->_errors[] = _('Could not init packet header'); + $this->_fclose( + $socket + ); + return null; } @@ -88,7 +105,7 @@ class Master for ($i = 0; $i < $limit; $i++) { // Get host - if (false === $host = fread($this->_socket, 16)) + if (false === $host = fread($socket, 16)) { break; } @@ -103,22 +120,22 @@ class Master if (false === $host = inet_ntop($host)) { // Shift port bytes - fread($this->_socket, 2); + fread($socket, 2); continue; } - // Decode first byte for port - if (false === $byte1 = fread($this->_socket, 1)) + // Decode first byte of port + if (false === $byte1 = fread($socket, 1)) { // Shift port byte - fread($this->_socket, 1); + fread($socket, 1); continue; } - // Decode second byte for port - if (false === $byte2 = fread($this->_socket, 1)) + // Decode second byte of port + if (false === $byte2 = fread($socket, 1)) { continue; } @@ -143,6 +160,10 @@ class Master ]; } + $this->_fclose( + $socket + ); + return $servers; } From b1fce6a668f58c884a357f706d3e0b88c447d2ef Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 4 Mar 2026 20:47:48 +0200 Subject: [PATCH 17/26] fix ipv6/xash3d-master compatibility; update method name --- src/Xash3D/Master.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index d710254..531396e 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -35,10 +35,10 @@ class Master } } - public function getServersIPv6( + public function getServers( int $limit = 100, string $region = "\xFF", - string $host = "0.0.0.0:0", + string $host = "0.0.0.0", int $port = 0, string $gamedir = "valve" ): ?array @@ -76,7 +76,7 @@ class Master } // Filter query - if (false === fwrite($socket, "1{$region}{$host}:{$port}\0\gamedir\t{$gamedir}\0")) + if (false === fwrite($socket, fwrite($socket, "1{$region}{$host}:{$port}\0\\gamedir\\{$gamedir}\0"))) { $this->_errors[] = _('Could not send socket query'); From 82c8564645da6193710d1c1fd26e8af5f9971396 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 4 Mar 2026 20:50:03 +0200 Subject: [PATCH 18/26] fix syntax error --- src/Xash3D/Master.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 531396e..08cbc2a 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -76,7 +76,7 @@ class Master } // Filter query - if (false === fwrite($socket, fwrite($socket, "1{$region}{$host}:{$port}\0\\gamedir\\{$gamedir}\0"))) + if (false === fwrite($socket, "1{$region}{$host}:{$port}\0\\gamedir\\{$gamedir}\0")) { $this->_errors[] = _('Could not send socket query'); From bacfce5f1bb057180f9350968b9b2e20066822ee Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 4 Mar 2026 21:24:12 +0200 Subject: [PATCH 19/26] reorder arguments --- src/Xash3D/Master.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 08cbc2a..42b14cc 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -37,10 +37,10 @@ class Master public function getServers( int $limit = 100, - string $region = "\xFF", string $host = "0.0.0.0", int $port = 0, - string $gamedir = "valve" + string $gamedir = "valve", + string $region = "\xFF" ): ?array { // Init connection From 167f092a28ba7523dee0f8a8f16ab76685dd78bb Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 4 Mar 2026 22:32:21 +0200 Subject: [PATCH 20/26] continue, not break --- src/Xash3D/Master.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 42b14cc..2714627 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -107,13 +107,13 @@ class Master // Get host if (false === $host = fread($socket, 16)) { - break; + continue; } // Is end of packet if (true === str_ends_with(bin2hex($host), bin2hex("\0\0\0\0\0\0"))) { - break; + continue; } // Skip invalid host value From 34390a0713fe00214df17d07e3c3f7691a006225 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 4 Mar 2026 22:56:37 +0200 Subject: [PATCH 21/26] simplify port decoding --- src/Xash3D/Master.php | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 2714627..5ef7d01 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -125,29 +125,14 @@ class Master continue; } - // Decode first byte of port - if (false === $byte1 = fread($socket, 1)) - { - // Shift port byte - fread($socket, 1); - - continue; - } - - // Decode second byte of port - if (false === $byte2 = fread($socket, 1)) - { - continue; - } - // Calculate port value - $port = ord($byte1) * 256 + ord($byte2); + $port = unpack('nport', fread($socket, 2)); // Validate IPv6 result if ( false !== strpos($host, '.') || // filter_var not always works with mixed IPv6 false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || - false === filter_var($port, FILTER_VALIDATE_INT) + false === filter_var($port, FILTER_VALIDATE_INT) // @TODO ) { continue; From 13a7c607e11dd7e1c11be59af05738fd72b58ae3 Mon Sep 17 00:00:00 2001 From: yggverse Date: Wed, 4 Mar 2026 23:19:46 +0200 Subject: [PATCH 22/26] update parsing conditions, remove hardcoded ipv6 impl --- src/Xash3D/Master.php | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 5ef7d01..fcf951c 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -107,13 +107,13 @@ class Master // Get host if (false === $host = fread($socket, 16)) { - continue; + break; } // Is end of packet if (true === str_ends_with(bin2hex($host), bin2hex("\0\0\0\0\0\0"))) { - continue; + break; } // Skip invalid host value @@ -126,22 +126,27 @@ class Master } // Calculate port value - $port = unpack('nport', fread($socket, 2)); - - // Validate IPv6 result - if ( - false !== strpos($host, '.') || // filter_var not always works with mixed IPv6 - false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || - false === filter_var($port, FILTER_VALIDATE_INT) // @TODO - ) + if (false === $port = fread($socket, 2)) { continue; } - $servers["[{$host}]:{$port}"] = // keep unique + if (false === $port = unpack('nport', $port)) + { + continue; + } + + // Validate IPv6 result + if ((false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && + false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) || empty($port['port'])) + { + continue; + } + + $servers["[{$host}]:{$port['port']}"] = // keep unique [ 'host' => $host, - 'port' => $port + 'port' => $port['port'] ]; } From a101a55715c5bdee7c865dd779994b805f91ded4 Mon Sep 17 00:00:00 2001 From: yggverse Date: Thu, 5 Mar 2026 00:42:21 +0200 Subject: [PATCH 23/26] revert getServersIPv6 method as mixed address families are not supported by legacy protocol implementation --- src/Xash3D/Master.php | 66 +++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index fcf951c..238ff4e 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -35,9 +35,11 @@ class Master } } - public function getServers( + // Legacy protocol implementation does not support mixed address families + // in the binary master socket response, use separated method for IPv4 servers. + public function getServersIPv6( int $limit = 100, - string $host = "0.0.0.0", + string $host = "[::]", int $port = 0, string $gamedir = "valve", string $region = "\xFF" @@ -52,6 +54,8 @@ class Master $this->_timeout ); + $master = "{$this->_host}:{$this->_port}"; + // Is connected if (true === is_resource($socket)) { @@ -64,7 +68,7 @@ class Master else { $this->_errors[] = sprintf( - _('Connection error: %s'), + _("Connection error for $master: %s"), $message ); @@ -78,7 +82,7 @@ class Master // Filter query if (false === fwrite($socket, "1{$region}{$host}:{$port}\0\\gamedir\\{$gamedir}\0")) { - $this->_errors[] = _('Could not send socket query'); + $this->_errors[] = _("Could not send socket query for $master"); $this->_fclose( $socket @@ -90,7 +94,7 @@ class Master // Skip header if (false === fread($socket, 6)) { - $this->_errors[] = _('Could not init packet header'); + $this->_errors[] = _("Could not init packet header for $master"); $this->_fclose( $socket @@ -104,49 +108,51 @@ class Master for ($i = 0; $i < $limit; $i++) { - // Get host - if (false === $host = fread($socket, 16)) + // Get host bytes + if (false === $h = fread($socket, 16)) + { + $this->_errors[] = _("Invalid `host` fragment in packet at $i for $master"); + break; + } + + // End of packet + if (true === str_ends_with(bin2hex($h), bin2hex("\0\0\0\0\0\0"))) { break; } - // Is end of packet - if (true === str_ends_with(bin2hex($host), bin2hex("\0\0\0\0\0\0"))) + // Get host string + if (false === $h = inet_ntop($h)) { + $this->_errors[] = _("Invalid `host` value in packet at $i for $master"); break; } - // Skip invalid host value - if (false === $host = inet_ntop($host)) + // Get port bytes + if (false === $p = fread($socket, 2)) { - // Shift port bytes - fread($socket, 2); + $this->_errors[] = _("Invalid `port` fragment in packet at $i for $master"); + break; + } + // Get port value + if (false === $p = unpack('nport', $p)) + { + $this->_errors[] = _("Invalid `port` value in packet at $i for $master"); continue; } - // Calculate port value - if (false === $port = fread($socket, 2)) + // Validate result + if (false === filter_var($h, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || empty($p['port'])) { + $this->_errors[] = _("Invalid socket address in packet at $i for $master"); continue; } - if (false === $port = unpack('nport', $port)) - { - continue; - } - - // Validate IPv6 result - if ((false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && - false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) || empty($port['port'])) - { - continue; - } - - $servers["[{$host}]:{$port['port']}"] = // keep unique + $servers["{$h}{$p['port']}"] = // keep unique [ - 'host' => $host, - 'port' => $port['port'] + 'host' => $h, + 'port' => $p['port'] ]; } From 795f3047895a875c35f426c5f9d8649967f7b3db Mon Sep 17 00:00:00 2001 From: yggverse Date: Thu, 5 Mar 2026 01:08:13 +0200 Subject: [PATCH 24/26] use enumeration types --- src/Xash3D/Master.php | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index 238ff4e..b05e201 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -4,6 +4,21 @@ declare(strict_types=1); namespace Yggverse\Hl\Xash3D; +enum Family: int { + case IPv4 = 4; + case IPv6 = 16; +} + +enum Region: string { + case Europe = "\x03"; + case US_East = "\x00"; + case World = "\xFF"; +} + +enum Game: string { + case Valve = "valve"; +} + class Master { private string $_host; @@ -14,7 +29,7 @@ class Master public function __construct( string $host, - int $port, + int $port = 27010, int $timeout = 5 ) { @@ -37,14 +52,16 @@ class Master // Legacy protocol implementation does not support mixed address families // in the binary master socket response, use separated method for IPv4 servers. - public function getServersIPv6( + public function getServers( int $limit = 100, - string $host = "[::]", + string $host = "0.0.0.0", int $port = 0, - string $gamedir = "valve", - string $region = "\xFF" + Game $game = Game::Valve, + Region $region = Region::World ): ?array { + $family = filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? Family::IPv4 : Family::IPv6; + // Init connection $socket = fsockopen( "udp://{$this->_host}", @@ -80,7 +97,7 @@ class Master } // Filter query - if (false === fwrite($socket, "1{$region}{$host}:{$port}\0\\gamedir\\{$gamedir}\0")) + if (false === fwrite($socket, "1{$region->value}{$host}:{$port}\0\\gamedir\\{$game->value}\0")) { $this->_errors[] = _("Could not send socket query for $master"); @@ -109,20 +126,20 @@ class Master for ($i = 0; $i < $limit; $i++) { // Get host bytes - if (false === $h = fread($socket, 16)) + if (false === $host = fread($socket, $family->value)) { $this->_errors[] = _("Invalid `host` fragment in packet at $i for $master"); break; } // End of packet - if (true === str_ends_with(bin2hex($h), bin2hex("\0\0\0\0\0\0"))) + if (true === str_ends_with(bin2hex($host), bin2hex("\0\0\0\0\0\0"))) { break; } // Get host string - if (false === $h = inet_ntop($h)) + if (false === $host = inet_ntop($host)) { $this->_errors[] = _("Invalid `host` value in packet at $i for $master"); break; @@ -143,15 +160,16 @@ class Master } // Validate result - if (false === filter_var($h, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || empty($p['port'])) + if (false === filter_var($host, FILTER_VALIDATE_IP, $family == Family::IPv6 ? FILTER_FLAG_IPV6 + : FILTER_FLAG_IPV4) || empty($p['port'])) { $this->_errors[] = _("Invalid socket address in packet at $i for $master"); continue; } - $servers["{$h}{$p['port']}"] = // keep unique + $servers["{$host}{$p['port']}"] = // keep unique [ - 'host' => $h, + 'host' => $host, 'port' => $p['port'] ]; } From 9f4e92459283f6775127f0bba8fad0ed1ce88dbf Mon Sep 17 00:00:00 2001 From: yggverse Date: Thu, 5 Mar 2026 01:11:24 +0200 Subject: [PATCH 25/26] use break as nothing to continue on any piece fail --- src/Xash3D/Master.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index b05e201..f0aafe4 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -156,7 +156,7 @@ class Master if (false === $p = unpack('nport', $p)) { $this->_errors[] = _("Invalid `port` value in packet at $i for $master"); - continue; + break; } // Validate result @@ -164,7 +164,7 @@ class Master : FILTER_FLAG_IPV4) || empty($p['port'])) { $this->_errors[] = _("Invalid socket address in packet at $i for $master"); - continue; + break; } $servers["{$host}{$p['port']}"] = // keep unique From 42b1195b83f10f8e6054a2c45e9181c3a8910292 Mon Sep 17 00:00:00 2001 From: yggverse Date: Thu, 5 Mar 2026 03:26:58 +0200 Subject: [PATCH 26/26] add all supported regions --- src/Xash3D/Master.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Xash3D/Master.php b/src/Xash3D/Master.php index f0aafe4..5ea743e 100644 --- a/src/Xash3D/Master.php +++ b/src/Xash3D/Master.php @@ -10,9 +10,15 @@ enum Family: int { } enum Region: string { - case Europe = "\x03"; - case US_East = "\x00"; - case World = "\xFF"; + case USEastCoast = "0x00"; + case USWestCoast = "0x01"; + case SouthAmerica = "0x02"; + case Europe = "0x03"; + case Asia = "0x04"; + case Australia = "0x05"; + case MiddleEast = "0x06"; + case Africa = "0x07"; + case World = "0xff"; } enum Game: string {