1: <?php
2: /**
3: * This file is part of GameQ.
4: *
5: * GameQ is free software; you can redistribute it and/or modify
6: * it under the terms of the GNU Lesser General Public License as published by
7: * the Free Software Foundation; either version 3 of the License, or
8: * (at your option) any later version.
9: *
10: * GameQ is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13: * GNU Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public License
16: * along with this program. If not, see <http://www.gnu.org/licenses/>.
17: */
18:
19: namespace GameQ\Protocols;
20:
21: use GameQ\Buffer;
22: use GameQ\Exception\Protocol as Exception;
23: use GameQ\Helpers\Str;
24: use GameQ\Protocol;
25: use GameQ\Result;
26:
27: /**
28: * Doom3 Protocol Class
29: *
30: * Handles processing DOOM 3 servers
31: *
32: * @package GameQ\Protocols
33: * @author Wilson Jesus <>
34: */
35: class Doom3 extends Protocol
36: {
37: /**
38: * Array of packets we want to look up.
39: * Each key should correspond to a defined method in this or a parent class
40: *
41: * @var array
42: */
43: protected $packets = [
44: self::PACKET_ALL => "\xFF\xFFgetInfo\x00PiNGPoNG\x00",
45: ];
46:
47: /**
48: * Use the response flag to figure out what method to run
49: *
50: * @var array
51: */
52: protected $responses = [
53: "\xFF\xFFinfoResponse" => 'processStatus',
54: ];
55:
56: /**
57: * The query protocol used to make the call
58: *
59: * @var string
60: */
61: protected $protocol = 'doom3';
62:
63: /**
64: * String name of this protocol class
65: *
66: * @var string
67: */
68: protected $name = 'doom3';
69:
70: /**
71: * Longer string name of this protocol class
72: *
73: * @var string
74: */
75: protected $name_long = "Doom 3";
76:
77: /**
78: * Normalize settings for this protocol
79: *
80: * @var array
81: */
82: protected $normalize = [
83: // General
84: 'general' => [
85: // target => source
86: 'hostname' => 'si_name',
87: 'gametype' => 'gamename',
88: 'mapname' => 'si_map',
89: 'maxplayers' => 'si_maxPlayers',
90: 'numplayers' => 'clients',
91: 'password' => 'si_usepass',
92: ],
93: // Individual
94: 'player' => [
95: 'name' => 'name',
96: 'ping' => 'ping',
97: ],
98: ];
99:
100: /**
101: * Handle response from the server
102: *
103: * @return mixed
104: * @throws Exception
105: * @throws \GameQ\Exception\Protocol
106: */
107: public function processResponse()
108: {
109: // Make a buffer
110: $buffer = new Buffer(implode('', $this->packets_response));
111:
112: // Grab the header
113: $header = $buffer->readString();
114:
115: // Header
116: // Figure out which packet response this is
117: if (empty($header) || !array_key_exists($header, $this->responses)) {
118: throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
119: }
120:
121: return call_user_func_array([$this, $this->responses[$header]], [$buffer]);
122: }
123:
124: /**
125: * Process the status response
126: *
127: * @param Buffer $buffer
128: *
129: * @return array
130: * @throws \GameQ\Exception\Protocol
131: */
132: protected function processStatus(Buffer $buffer)
133: {
134: // We need to split the data and offload
135: $results = $this->processServerInfo($buffer);
136:
137: $results = array_merge_recursive(
138: $results,
139: $this->processPlayers($buffer)
140: );
141:
142: unset($buffer);
143:
144: // Return results
145: return $results;
146: }
147:
148: /**
149: * Handle processing the server information
150: *
151: * @param Buffer $buffer
152: *
153: * @return array
154: * @throws \GameQ\Exception\Protocol
155: */
156: protected function processServerInfo(Buffer $buffer)
157: {
158: // Set the result to a new result instance
159: $result = new Result();
160:
161: $result->add('version', $buffer->readInt8() . '.' . $buffer->readInt8());
162:
163: // Key / value pairs, delimited by an empty pair
164: while ($buffer->getLength()) {
165: $key = trim($buffer->readString());
166: $val = Str::isoToUtf8(trim($buffer->readString()));
167:
168: // Something is empty so we are done
169: if (empty($key) && empty($val)) {
170: break;
171: }
172:
173: $result->add($key, $val);
174: }
175:
176: unset($buffer);
177:
178: return $result->fetch();
179: }
180:
181: /**
182: * Handle processing of player data
183: *
184: * @param Buffer $buffer
185: *
186: * @return array
187: * @throws \GameQ\Exception\Protocol
188: */
189: protected function processPlayers(Buffer $buffer)
190: {
191: // Some games do not have a number of current players
192: $playerCount = 0;
193:
194: // Set the result to a new result instance
195: $result = new Result();
196:
197: // Parse players
198: // Loop thru the buffer until we run out of data
199: while (($id = $buffer->readInt8()) != 32) {
200: // Add player info results
201: $result->addPlayer('id', $id);
202: $result->addPlayer('ping', $buffer->readInt16());
203: $result->addPlayer('rate', $buffer->readInt32());
204: // Add player name, encoded
205: $result->addPlayer('name', Str::isoToUtf8(trim($buffer->readString())));
206:
207: // Increment
208: $playerCount++;
209: }
210:
211: // Add the number of players to the result
212: $result->add('clients', $playerCount);
213:
214: // Clear
215: unset($buffer, $playerCount);
216:
217: return $result->fetch();
218: }
219: }
220: