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\Protocol;
22: use GameQ\Buffer;
23: use GameQ\Result;
24: use GameQ\Exception\Protocol as Exception;
25:
26: /**
27: * Unreal 2 Protocol class
28: *
29: * @author Austin Bischoff <austin@codebeard.com>
30: */
31: class Unreal2 extends Protocol
32: {
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: * @type 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: * @type 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: * @type string
61: */
62: protected $protocol = 'unreal2';
63:
64: /**
65: * String name of this protocol class
66: *
67: * @type string
68: */
69: protected $name = 'unreal2';
70:
71: /**
72: * Longer string name of this protocol class
73: *
74: * @type string
75: */
76: protected $name_long = "Unreal 2";
77:
78: /**
79: * Normalize settings for this protocol
80: *
81: * @type 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:
111: // Will hold the packets after sorting
112: $packets = [];
113:
114: // We need to pre-sort these for split packets so we can do extra work where needed
115: foreach ($this->packets_response as $response) {
116: $buffer = new Buffer($response);
117:
118: // Pull out the header
119: $header = $buffer->read(5);
120:
121: // Add the packet to the proper section, we will combine later
122: $packets[$header][] = $buffer->getBuffer();
123: }
124:
125: unset($buffer);
126:
127: $results = [];
128:
129: // Now let's iterate and process
130: foreach ($packets as $header => $packetGroup) {
131: // Figure out which packet response this is
132: if (!array_key_exists($header, $this->responses)) {
133: throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
134: }
135:
136: // Now we need to call the proper method
137: $results = array_merge(
138: $results,
139: call_user_func_array([$this, $this->responses[$header]], [new Buffer(implode($packetGroup))])
140: );
141: }
142:
143: unset($packets);
144:
145: return $results;
146: }
147:
148: /*
149: * Internal methods
150: */
151:
152: /**
153: * Handles processing the details data into a usable format
154: *
155: * @param \GameQ\Buffer $buffer
156: *
157: * @return mixed
158: * @throws \GameQ\Exception\Protocol
159: */
160: protected function processDetails(Buffer $buffer)
161: {
162:
163: // Set the result to a new result instance
164: $result = new Result();
165:
166: $result->add('serverid', $buffer->readInt32()); // 0
167: $result->add('serverip', $buffer->readPascalString(1)); // empty
168: $result->add('gameport', $buffer->readInt32());
169: $result->add('queryport', $buffer->readInt32()); // 0
170: $result->add('servername', utf8_encode($buffer->readPascalString(1)));
171: $result->add('mapname', utf8_encode($buffer->readPascalString(1)));
172: $result->add('gametype', $buffer->readPascalString(1));
173: $result->add('numplayers', $buffer->readInt32());
174: $result->add('maxplayers', $buffer->readInt32());
175: $result->add('ping', $buffer->readInt32()); // 0
176:
177: unset($buffer);
178:
179: return $result->fetch();
180: }
181:
182: /**
183: * Handles processing the player data into a usable format
184: *
185: * @param \GameQ\Buffer $buffer
186: *
187: * @return mixed
188: */
189: protected function processPlayers(Buffer $buffer)
190: {
191:
192: // Set the result to a new result instance
193: $result = new Result();
194:
195: // Parse players
196: while ($buffer->getLength()) {
197: // Player id
198: if (($id = $buffer->readInt32()) !== 0) {
199: // Add the results
200: $result->addPlayer('id', $id);
201: $result->addPlayer('name', utf8_encode($buffer->readPascalString(1)));
202: $result->addPlayer('ping', $buffer->readInt32());
203: $result->addPlayer('score', $buffer->readInt32());
204:
205: // Skip the next 4, unsure what they are for
206: $buffer->skip(4);
207: }
208: }
209:
210: unset($buffer, $id);
211:
212: return $result->fetch();
213: }
214:
215: /**
216: * Handles processing the rules data into a usable format
217: *
218: * @param \GameQ\Buffer $buffer
219: *
220: * @return mixed
221: */
222: protected function processRules(Buffer $buffer)
223: {
224:
225: // Set the result to a new result instance
226: $result = new Result();
227:
228: // Named values
229: $inc = -1;
230: while ($buffer->getLength()) {
231: // Grab the key
232: $key = $buffer->readPascalString(1);
233:
234: // Make sure mutators don't overwrite each other
235: if ($key === 'Mutator') {
236: $key .= ++$inc;
237: }
238:
239: $result->add(strtolower($key), utf8_encode($buffer->readPascalString(1)));
240: }
241:
242: unset($buffer);
243:
244: return $result->fetch();
245: }
246: }
247: