1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * Clansuite Server Query
7: *
8: * SPDX-FileCopyrightText: 2003-2025 Jens A. Koch
9: * SPDX-License-Identifier: MIT
10: *
11: * For the full copyright and license information, please view
12: * the LICENSE file that was distributed with this source code.
13: */
14:
15: namespace Clansuite\ServerQuery\ServerProtocols;
16:
17: use function count;
18: use function explode;
19: use function is_numeric;
20: use function is_scalar;
21: use function ord;
22: use function preg_replace;
23: use function strtolower;
24: use function substr;
25: use Override;
26:
27: /**
28: * This class implements the protocol used by Half-Life 2.
29: */
30: class Halflife2 extends Halflife
31: {
32: /**
33: * Protocol name.
34: */
35: public string $name = 'Half-Life 2';
36:
37: /**
38: * Protocol identifier.
39: */
40: public string $protocol = 'Halflife2';
41:
42: /**
43: * Game series.
44: *
45: * @var array<string>
46: */
47: public array $game_series_list = ['Half-Life'];
48:
49: /**
50: * List of supported games.
51: *
52: * @var array<string>
53: */
54: public array $supportedGames = ['Half-Life 2'];
55:
56: /**
57: * query_server method.
58: */
59: #[Override]
60: public function query_server(bool $getPlayers = true, bool $getRules = true): bool
61: {
62: if ($this->online) {
63: $this->reset();
64: }
65:
66: $address = is_scalar($this->address) ? $this->address : '';
67: $port = is_numeric($this->queryport) ? $this->queryport : 0;
68:
69: $command = "\xFF\xFF\xFF\xFFT";
70:
71: if (($result = $this->sendCommand($address, $port, $command)) === '' || ($result = $this->sendCommand($address, $port, $command)) === '0' || ($result = $this->sendCommand($address, $port, $command)) === false) {
72: return false;
73: }
74:
75: $i = 5; // start after header
76:
77: $this->gameversion = (string) ord($result[5]);
78: $this->hostport = $this->queryport ?? 0;
79:
80: $basic = explode("\x00", substr($result, 6));
81:
82: // XXX: Replace old code
83: $this->rules['gamedir'] = '';
84:
85: if (isset($basic[0])) {
86: $this->servertitle = $basic[0];
87: }
88:
89: if (isset($basic[1])) {
90: $this->mapname = $basic[1];
91: }
92:
93: if (isset($basic[2])) {
94: $this->rules['gamedir'] = $basic[2];
95: }
96:
97: $this->gamename = isset($basic[3]) ? preg_replace('/[ :]/', '_', strtolower($basic[3])) ?? '' : '';
98: $this->rules['steamid'] = ord($result[$i]) | (ord($result[$i + 1]) << 8);
99: $i += 2;
100: $this->numplayers = ord(substr($result, $i++, 1));
101: $this->maxplayers = ord(substr($result, $i++, 1));
102: $this->rules['botplayers'] = ord(substr($result, $i++, 1));
103: $this->rules['dedicated'] = ($result[$i++] === 'd' ? 'Yes' : 'No');
104: $this->rules['server_os'] = ($result[$i++] === 'l' ? 'Linux' : 'Windows');
105: $this->password = ord(substr($result, $i++, 1));
106: $this->rules['secure'] = ($result[$i++] === '1' ? 'Yes' : 'No');
107:
108: // Already normalized above
109:
110: // do rules
111: $command = "\xFF\xFF\xFF\xFFV";
112:
113: if (($result = $this->sendCommand($address, $port, $command)) === '' || ($result = $this->sendCommand($address, $port, $command)) === '0' || ($result = $this->sendCommand($address, $port, $command)) === false) {
114: return false;
115: }
116:
117: $exploded_data = explode("\x00", $result);
118:
119: $z = count($exploded_data);
120:
121: $i = 1;
122:
123: while ($i < $z) {
124: $key = $exploded_data[$i++] ?? '';
125: $value = $exploded_data[$i++] ?? '';
126:
127: switch ($key) {
128: case 'sv_password':
129: $this->password = (int) $value;
130:
131: break;
132:
133: case 'deathmatch':
134: if ($value === '1') {
135: $this->gametype = 'Deathmatch';
136: }
137:
138: break;
139:
140: case 'coop':
141: if ($value === '1') {
142: $this->gametype = 'Cooperative';
143: }
144:
145: break;
146:
147: default:
148: $this->rules[$key] = $value;
149: }
150: }
151:
152: if ($getPlayers) {
153: // do players
154: $command = "\xFF\xFF\xFF\xFFU";
155:
156: if (($result = $this->sendCommand($address, $port, $command)) === '' || ($result = $this->sendCommand($address, $port, $command)) === '0' || ($result = $this->sendCommand($address, $port, $command)) === false) {
157: return false;
158: }
159:
160: $this->processPlayers($result, $this->playerFormat, 8);
161:
162: $this->playerkeys['name'] = true;
163: $this->playerkeys['score'] = true;
164: $this->playerkeys['time'] = true;
165: }
166:
167: $this->online = true;
168:
169: return true;
170: }
171: }
172: