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