1: <?php declare(strict_types=1);
2:
3: /**
4: * Clansuite Server Query
5: *
6: * SPDX-FileCopyrightText: 2003-2025 Jens A. Koch
7: * SPDX-License-Identifier: MIT
8: *
9: * For the full copyright and license information, please view
10: * the LICENSE file that was distributed with this source code.
11: */
12:
13: namespace Clansuite\ServerQuery\ServerProtocols;
14:
15: use function count;
16: use function explode;
17: use function trim;
18: use Clansuite\Capture\Protocol\ProtocolInterface;
19: use Clansuite\Capture\ServerAddress;
20: use Clansuite\Capture\ServerInfo;
21: use Clansuite\ServerQuery\CSQuery;
22: use Override;
23:
24: /**
25: * Starbound protocol implementation.
26: *
27: * Custom UDP protocol.
28: */
29: class Starbound extends CSQuery implements ProtocolInterface
30: {
31: /**
32: * Protocol name.
33: */
34: public string $name = 'Starbound';
35:
36: /**
37: * List of supported games.
38: *
39: * @var array<string>
40: */
41: public array $supportedGames = ['Starbound'];
42:
43: /**
44: * Protocol identifier.
45: */
46: public string $protocol = 'starbound';
47:
48: /**
49: * Constructor.
50: */
51: public function __construct(?string $address = null, ?int $queryport = null)
52: {
53: parent::__construct();
54: $this->address = $address;
55: $this->queryport = $queryport;
56: }
57:
58: /**
59: * query_server method.
60: */
61: #[Override]
62: public function query_server(bool $getPlayers = true, bool $getRules = true): bool
63: {
64: if ($this->online) {
65: $this->reset();
66: }
67:
68: // Starbound query packet
69: $command = "Starbound\x00query\x00";
70:
71: $address = (string) $this->address;
72: $port = (int) $this->queryport;
73:
74: $result = $this->sendCommand($address, $port, $command);
75:
76: if ($result === '' || $result === '0' || $result === false) {
77: $this->errstr = 'No reply received';
78:
79: return false;
80: }
81:
82: // Parse the response
83: // The response is a string like "Starbound <version> <servername> <players>/<maxplayers> <map>"
84: $data = trim($result);
85: $parts = explode(' ', $data);
86:
87: if (count($parts) < 5) {
88: $this->errstr = 'Invalid response format';
89:
90: return false;
91: }
92:
93: $this->gamename = 'Starbound';
94: $this->gameversion = $parts[1] ?? '';
95: $this->servertitle = $parts[2] ?? '';
96: $this->mapname = $parts[4] ?? '';
97:
98: $playerPart = $parts[3] ?? '';
99: $playerParts = explode('/', $playerPart);
100:
101: if (count($playerParts) === 2) {
102: $this->numplayers = (int) $playerParts[0];
103: $this->maxplayers = (int) $playerParts[1];
104: }
105:
106: // No player list or rules in this simple protocol
107: $this->players = [];
108: $this->rules = [];
109:
110: $this->online = true;
111:
112: return true;
113: }
114:
115: /**
116: * query method.
117: */
118: #[Override]
119: public function query(ServerAddress $addr): ServerInfo
120: {
121: $this->address = $addr->ip;
122: $this->queryport = $addr->port;
123: $this->query_server(true, true);
124:
125: return new ServerInfo(
126: address: $this->address,
127: queryport: $this->queryport,
128: online: $this->online,
129: gamename: $this->gamename,
130: gameversion: $this->gameversion,
131: servertitle: $this->servertitle,
132: mapname: $this->mapname,
133: gametype: $this->gametype,
134: numplayers: $this->numplayers,
135: maxplayers: $this->maxplayers,
136: rules: $this->rules,
137: players: $this->players,
138: errstr: $this->errstr,
139: );
140: }
141:
142: /**
143: * getProtocolName method.
144: */
145: #[Override]
146: public function getProtocolName(): string
147: {
148: return $this->protocol;
149: }
150:
151: /**
152: * getVersion method.
153: */
154: #[Override]
155: public function getVersion(ServerInfo $info): string
156: {
157: return $info->gameversion ?? 'unknown';
158: }
159: }
160: