mirror of
https://github.com/YGGverse/hl-php.git
synced 2026-03-31 17:15:39 +00:00
Compare commits
26 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42b1195b83 | ||
|
|
9f4e924592 | ||
|
|
795f304789 | ||
|
|
a101a55715 | ||
|
|
13a7c607e1 | ||
|
|
34390a0713 | ||
|
|
167f092a28 | ||
|
|
bacfce5f1b | ||
|
|
82c8564645 | ||
|
|
b1fce6a668 | ||
|
|
4f8fffed17 | ||
|
|
550efd120f | ||
|
|
3ffb314fa4 | ||
|
|
15035498b3 | ||
|
|
27fbf92c6b | ||
|
|
726463e94e | ||
|
|
33a77a858b | ||
|
|
a97a2de151 | ||
|
|
be344abd36 | ||
|
|
9c9625cb21 | ||
|
|
b7a5314030 | ||
|
|
d07ba95654 | ||
|
|
c942dc98d0 | ||
|
|
6479e40496 | ||
|
|
49c5c76908 | ||
|
|
cd22acf846 |
3 changed files with 130 additions and 51 deletions
10
README.md
10
README.md
|
|
@ -13,9 +13,17 @@ 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()
|
||||
);
|
||||
|
||||
var_dump(
|
||||
$master->getErrors()
|
||||
);
|
||||
```
|
||||
|
||||
## Projects
|
||||
|
||||
* [HLState](https://github.com/YGGverse/HLState) - Web Monitor for Half-Life Servers
|
||||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -2,66 +2,127 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Yggverse\Hl\Xash3d;
|
||||
namespace Yggverse\Hl\Xash3D;
|
||||
|
||||
enum Family: int {
|
||||
case IPv4 = 4;
|
||||
case IPv6 = 16;
|
||||
}
|
||||
|
||||
enum Region: string {
|
||||
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 {
|
||||
case Valve = "valve";
|
||||
}
|
||||
|
||||
class Master
|
||||
{
|
||||
private $_socket;
|
||||
private string $_host;
|
||||
private int $_port;
|
||||
private int $_timeout;
|
||||
|
||||
private array $_errors = [];
|
||||
|
||||
public function __construct(
|
||||
string $host,
|
||||
int $port,
|
||||
int $port = 27010,
|
||||
int $timeout = 5
|
||||
)
|
||||
{
|
||||
$this->_socket = fsockopen(
|
||||
"udp://{$host}",
|
||||
$port
|
||||
);
|
||||
|
||||
stream_set_timeout(
|
||||
$this->_socket,
|
||||
$timeout
|
||||
);
|
||||
$this->_host = $host;
|
||||
$this->_port = $port;
|
||||
$this->_timeout = $timeout;
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
private function _fclose(
|
||||
mixed $socket
|
||||
)
|
||||
{
|
||||
if ($this->_socket)
|
||||
if (true === is_resource($socket))
|
||||
{
|
||||
fclose(
|
||||
$this->_socket
|
||||
$socket
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static function getServersIPv6(
|
||||
// Legacy protocol implementation does not support mixed address families
|
||||
// in the binary master socket response, use separated method for IPv4 servers.
|
||||
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"
|
||||
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}",
|
||||
$this->_port,
|
||||
$code,
|
||||
$message,
|
||||
$this->_timeout
|
||||
);
|
||||
|
||||
$master = "{$this->_host}:{$this->_port}";
|
||||
|
||||
// Is connected
|
||||
if (!$this->_socket)
|
||||
if (true === is_resource($socket))
|
||||
{
|
||||
stream_set_timeout(
|
||||
$socket,
|
||||
$this->_timeout
|
||||
);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
$this->_errors[] = sprintf(
|
||||
_("Connection error for $master: %s"),
|
||||
$message
|
||||
);
|
||||
|
||||
$this->_fclose(
|
||||
$socket
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Filter query
|
||||
if (!fwrite($this->_socket, "1{$region}{$host}:{$port}\0\gamedir\t{$gamedir}\0"))
|
||||
if (false === fwrite($socket, "1{$region->value}{$host}:{$port}\0\\gamedir\\{$game->value}\0"))
|
||||
{
|
||||
fclose(
|
||||
$this->_socket
|
||||
$this->_errors[] = _("Could not send socket query for $master");
|
||||
|
||||
$this->_fclose(
|
||||
$socket
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Skip header
|
||||
if (!fread($this->_socket, 6))
|
||||
if (false === fread($socket, 6))
|
||||
{
|
||||
$this->_errors[] = _("Could not init packet header for $master");
|
||||
|
||||
$this->_fclose(
|
||||
$socket
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -70,56 +131,64 @@ class Master
|
|||
|
||||
for ($i = 0; $i < $limit; $i++)
|
||||
{
|
||||
// Get host
|
||||
if (false === $host = fread($this->_socket, 16))
|
||||
// Get host bytes
|
||||
if (false === $host = fread($socket, $family->value))
|
||||
{
|
||||
return null;
|
||||
$this->_errors[] = _("Invalid `host` fragment in packet at $i for $master");
|
||||
break;
|
||||
}
|
||||
|
||||
// Is end of packet
|
||||
if (true === str_starts_with($host, 0))
|
||||
// End of packet
|
||||
if (true === str_ends_with(bin2hex($host), bin2hex("\0\0\0\0\0\0")))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip invalid host
|
||||
// Get host string
|
||||
if (false === $host = inet_ntop($host))
|
||||
{
|
||||
continue;
|
||||
$this->_errors[] = _("Invalid `host` value in packet at $i for $master");
|
||||
break;
|
||||
}
|
||||
|
||||
// Decode first byte for port
|
||||
if (false === $byte1 = fread($this->_socket, 1))
|
||||
// Get port bytes
|
||||
if (false === $p = fread($socket, 2))
|
||||
{
|
||||
return null;
|
||||
$this->_errors[] = _("Invalid `port` fragment in packet at $i for $master");
|
||||
break;
|
||||
}
|
||||
|
||||
// Decode second byte for port
|
||||
if (false === $byte2 = fread($this->_socket, 1))
|
||||
// Get port value
|
||||
if (false === $p = unpack('nport', $p))
|
||||
{
|
||||
return null;
|
||||
$this->_errors[] = _("Invalid `port` value in packet at $i for $master");
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate port value
|
||||
$port = ord($byte1) * 256 + ord($byte2);
|
||||
|
||||
// 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)
|
||||
)
|
||||
// Validate result
|
||||
if (false === filter_var($host, FILTER_VALIDATE_IP, $family == Family::IPv6 ? FILTER_FLAG_IPV6
|
||||
: FILTER_FLAG_IPV4) || empty($p['port']))
|
||||
{
|
||||
continue;
|
||||
$this->_errors[] = _("Invalid socket address in packet at $i for $master");
|
||||
break;
|
||||
}
|
||||
|
||||
$servers["[{$host}]:{$port}"] = // keep unique
|
||||
$servers["{$host}{$p['port']}"] = // keep unique
|
||||
[
|
||||
'host' => $host,
|
||||
'port' => $port
|
||||
'port' => $p['port']
|
||||
];
|
||||
}
|
||||
|
||||
$this->_fclose(
|
||||
$socket
|
||||
);
|
||||
|
||||
return $servers;
|
||||
}
|
||||
|
||||
public function getErrors(): array
|
||||
{
|
||||
return $this->_errors;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue