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 preg_match;
16: use function strlen;
17: use function substr;
18: use Override;
19:
20: /**
21: * Queries F.E.A.R. game servers.
22: *
23: * Extends the Quake 4 protocol to handle F.E.A.R. specific server queries,
24: * retrieving information about server status, players, and game settings.
25: * Enables monitoring of F.E.A.R. multiplayer servers.
26: */
27: class Fear extends Quake4
28: {
29: /**
30: * Protocol name.
31: */
32: public string $name = 'F.E.A.R.';
33:
34: /**
35: * List of supported games.
36: *
37: * @var array<string>
38: */
39: public array $supportedGames = ['F.E.A.R.'];
40:
41: /**
42: * Protocol identifier.
43: */
44: public string $protocol = 'fear';
45:
46: /**
47: * getProtocolName method.
48: */
49: #[Override]
50: public function getProtocolName(): string
51: {
52: return $this->protocol;
53: }
54:
55: /**
56: * query_server method.
57: */
58: #[Override]
59: public function query_server(mixed $getPlayers = true, mixed $getRules = true): bool
60: {
61: if ($this->online) {
62: $this->reset();
63: }
64:
65: // F.E.A.R. uses the same packet format as Doom3/Quake4
66: $command = "\xFF\xFFgetInfo\x00\x01\x00\x00\x00";
67:
68: if (($result = $this->sendCommand((string) $this->address, (int) $this->queryport, $command)) === '' || ($result = $this->sendCommand((string) $this->address, (int) $this->queryport, $command)) === '0' || ($result = $this->sendCommand((string) $this->address, (int) $this->queryport, $command)) === false) {
69: $this->errstr = 'No reply received';
70:
71: return false;
72: }
73:
74: // Parse the Doom3 packet format
75: if (strlen($result) < 16) {
76: $this->errstr = 'Invalid packet received';
77:
78: return false;
79: }
80:
81: // Skip the packet header (16 bytes: short check, 12 bytes check2, int challengeId, int protocol, char pack)
82: $data = substr($result, 16);
83:
84: // Parse server info
85: $info = $this->parseDoom3Info($data);
86:
87: // Extract basic server information
88: $this->gamename = (string) ($info['gamename'] ?? 'FEAR');
89: $this->gameversion = $this->translateProtocolVersion((string) ($info['protocol'] ?? $info['si_version'] ?? ''));
90: $this->servertitle = (string) ($info['si_name'] ?? '');
91: $this->mapname = (string) ($info['si_map'] ?? '');
92: $this->gametype = (string) ($info['si_rules'] ?? '');
93: $this->maxplayers = (int) ($info['si_maxPlayers'] ?? 0);
94: $this->numplayers = 0; // Will be calculated from players
95:
96: // Store all rules
97: $this->rules = $info;
98:
99: // Get players if requested
100: if ($getPlayers && isset($info['si_players'])) {
101: $this->parsePlayers((string) $info['si_players']);
102: }
103:
104: $this->online = true;
105:
106: return true;
107: }
108:
109: /**
110: * translateProtocolVersion method.
111: */
112: #[Override]
113: protected function translateProtocolVersion(string $protocol): string
114: {
115: // For F.E.A.R., extract version like 1.08 from the string
116: if (preg_match('/(\d+\.\d+)/', $protocol, $matches) !== false && isset($matches[1])) {
117: return 'v' . $matches[1];
118: }
119:
120: return $protocol;
121: }
122: }
123: