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: * Unreal 2 Protocol class
29: *
30: * @author Austin Bischoff <austin@codebeard.com>
31: */
32: class Unreal2 extends Protocol
33: {
34: /**
35: * Array of packets we want to look up.
36: * Each key should correspond to a defined method in this or a parent class
37: *
38: * @var array
39: */
40: protected $packets = [
41: self::PACKET_DETAILS => "\x79\x00\x00\x00\x00",
42: self::PACKET_RULES => "\x79\x00\x00\x00\x01",
43: self::PACKET_PLAYERS => "\x79\x00\x00\x00\x02",
44: ];
45:
46: /**
47: * Use the response flag to figure out what method to run
48: *
49: * @var array
50: */
51: protected $responses = [
52: "\x80\x00\x00\x00\x00" => "processDetails", // 0
53: "\x80\x00\x00\x00\x01" => "processRules", // 1
54: "\x80\x00\x00\x00\x02" => "processPlayers", // 2
55: ];
56:
57: /**
58: * The query protocol used to make the call
59: *
60: * @var string
61: */
62: protected $protocol = 'unreal2';
63:
64: /**
65: * String name of this protocol class
66: *
67: * @var string
68: */
69: protected $name = 'unreal2';
70:
71: /**
72: * Longer string name of this protocol class
73: *
74: * @var string
75: */
76: protected $name_long = "Unreal 2";
77:
78: /**
79: * Normalize settings for this protocol
80: *
81: * @var array
82: */
83: protected $normalize = [
84: // General
85: 'general' => [
86: // target => source
87: 'dedicated' => 'ServerMode',
88: 'gametype' => 'gametype',
89: 'hostname' => 'servername',
90: 'mapname' => 'mapname',
91: 'maxplayers' => 'maxplayers',
92: 'numplayers' => 'numplayers',
93: 'password' => 'password',
94: ],
95: // Individual
96: 'player' => [
97: 'name' => 'name',
98: 'score' => 'score',
99: ],
100: ];
101:
102: /**
103: * Process the response
104: *
105: * @return array
106: * @throws \GameQ\Exception\Protocol
107: */
108: public function processResponse()
109: {
110: // Will hold the packets after sorting
111: $packets = [];
112:
113: // We need to pre-sort these for split packets so we can do extra work where needed
114: foreach ($this->packets_response as $response) {
115: $buffer = new Buffer($response);
116:
117: // Pull out the header
118: $header = $buffer->read(5);
119:
120: // Add the packet to the proper section, we will combine later
121: $packets[$header][] = $buffer->getBuffer();
122: }
123:
124: unset($buffer);
125:
126: $results = [];
127:
128: // Now let's iterate and process
129: foreach ($packets as $header => $packetGroup) {
130: // Figure out which packet response this is
131: if (!array_key_exists($header, $this->responses)) {
132: throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
133: }
134:
135: // Now we need to call the proper method
136: $results = array_merge(
137: $results,
138: call_user_func_array([$this, $this->responses[$header]], [new Buffer(implode($packetGroup))])
139: );
140: }
141:
142: unset($packets);
143:
144: return $results;
145: }
146:
147: // Internal methods
148:
149: /**
150: * Handles processing the details data into a usable format
151: *
152: * @param \GameQ\Buffer $buffer
153: *
154: * @return mixed
155: * @throws \GameQ\Exception\Protocol
156: */
157: protected function processDetails(Buffer $buffer)
158: {
159: // Set the result to a new result instance
160: $result = new Result();
161:
162: $result->add('serverid', $buffer->readInt32()); // 0
163: $result->add('serverip', $buffer->readPascalString(1)); // empty
164: $result->add('gameport', $buffer->readInt32());
165: $result->add('queryport', $buffer->readInt32()); // 0
166: $result->add('servername', Str::isoToUtf8($buffer->readPascalString(1)));
167: $result->add('mapname', Str::isoToUtf8($buffer->readPascalString(1)));
168: $result->add('gametype', $buffer->readPascalString(1));
169: $result->add('numplayers', $buffer->readInt32());
170: $result->add('maxplayers', $buffer->readInt32());
171: $result->add('ping', $buffer->readInt32()); // 0
172:
173: unset($buffer);
174:
175: return $result->fetch();
176: }
177:
178: /**
179: * Handles processing the player data into a usable format
180: *
181: * @param \GameQ\Buffer $buffer
182: * @return mixed
183: * @throws \GameQ\Exception\Protocol
184: */
185: protected function processPlayers(Buffer $buffer)
186: {
187: // Set the result to a new result instance
188: $result = new Result();
189:
190: // Parse players
191: while ($buffer->getLength()) {
192: // Player id
193: if (($id = $buffer->readInt32()) !== 0) {
194: // Add the results
195: $result->addPlayer('id', $id);
196: $result->addPlayer('name', Str::isoToUtf8($buffer->readPascalString(1)));
197: $result->addPlayer('ping', $buffer->readInt32());
198: $result->addPlayer('score', $buffer->readInt32());
199:
200: // Skip the next 4, unsure what they are for
201: $buffer->skip(4);
202: }
203: }
204:
205: unset($buffer, $id);
206:
207: return $result->fetch();
208: }
209:
210: /**
211: * Handles processing the rules data into a usable format
212: *
213: * @param \GameQ\Buffer $buffer
214: * @return mixed
215: * @throws \GameQ\Exception\Protocol
216: */
217: protected function processRules(Buffer $buffer)
218: {
219: // Set the result to a new result instance
220: $result = new Result();
221:
222: // Named values
223: $inc = -1;
224: while ($buffer->getLength()) {
225: // Grab the key
226: $key = $buffer->readPascalString(1);
227:
228: // Make sure mutators don't overwrite each other
229: if ($key === 'Mutator') {
230: $key .= ++$inc;
231: }
232:
233: $result->add(strtolower($key), Str::isoToUtf8($buffer->readPascalString(1)));
234: }
235:
236: unset($buffer);
237:
238: return $result->fetch();
239: }
240: }
241: