1: <?php
2:
3:
4: namespace GameQ\Protocols;
5:
6: use GameQ\Buffer;
7: use GameQ\Exception\Protocol as Exception;
8: use GameQ\Protocol;
9: use GameQ\Result;
10:
11: /**
12: * Frontlines Fuel of War Protocol Class
13: *
14: * Handles processing ffow servers
15: *
16: * Class is incomplete due to lack of players to test against.
17: * http://wiki.hlsw.net/index.php/FFOW_Protocol
18: *
19: * @package GameQ\Protocols
20: */
21: class Ffow extends Protocol
22: {
23: /**
24: * Array of packets we want to look up.
25: * Each key should correspond to a defined method in this or a parent class
26: *
27: * @var array
28: */
29: protected $packets = [
30: self::PACKET_CHALLENGE => "\xFF\xFF\xFF\xFF\x57",
31: self::PACKET_RULES => "\xFF\xFF\xFF\xFF\x56%s",
32: self::PACKET_PLAYERS => "\xFF\xFF\xFF\xFF\x55%s",
33: self::PACKET_INFO => "\xFF\xFF\xFF\xFF\x46\x4C\x53\x51",
34: ];
35:
36: /**
37: * Use the response flag to figure out what method to run
38: *
39: * @var array
40: */
41: protected $responses = [
42: "\xFF\xFF\xFF\xFF\x49\x02" => 'processInfo', // I
43: "\xFF\xFF\xFF\xFF\x45\x00" => 'processRules', // E
44: "\xFF\xFF\xFF\xFF\x44\x00" => 'processPlayers', // D
45: ];
46:
47: /**
48: * The query protocol used to make the call
49: *
50: * @var string
51: */
52: protected $protocol = 'ffow';
53:
54: /**
55: * String name of this protocol class
56: *
57: * @var string
58: */
59: protected $name = 'ffow';
60:
61: /**
62: * Longer string name of this protocol class
63: *
64: * @var string
65: */
66: protected $name_long = "Frontlines Fuel of War";
67:
68: /**
69: * query_port = client_port + 2
70: *
71: * @var int
72: */
73: protected $port_diff = 2;
74:
75: /**
76: * Normalize settings for this protocol
77: *
78: * @var array
79: */
80: protected $normalize = [
81: // General
82: 'general' => [
83: // target => source
84: 'gametype' => 'gamemode',
85: 'hostname' => 'servername',
86: 'mapname' => 'mapname',
87: 'maxplayers' => 'max_players',
88: 'mod' => 'modname',
89: 'numplayers' => 'num_players',
90: 'password' => 'password',
91: ],
92: // Individual
93: 'player' => [
94: 'name' => 'name',
95: 'ping' => 'ping',
96: 'score' => 'frags',
97: ],
98: ];
99:
100: /**
101: * Parse the challenge response and apply it to all the packet types
102: *
103: * @param \GameQ\Buffer $challenge_buffer
104: *
105: * @return bool
106: * @throws \GameQ\Exception\Protocol
107: */
108: public function challengeParseAndApply(Buffer $challenge_buffer)
109: {
110: // Burn padding
111: $challenge_buffer->skip(5);
112:
113: // Apply the challenge and return
114: return $this->challengeApply($challenge_buffer->read(4));
115: }
116:
117: /**
118: * Handle response from the server
119: *
120: * @return mixed
121: * @throws Exception
122: */
123: public function processResponse()
124: {
125: // Init results
126: $results = [];
127:
128: foreach ($this->packets_response as $response) {
129: $buffer = new Buffer($response);
130:
131: // Figure out what packet response this is for
132: $response_type = $buffer->read(6);
133:
134: // Figure out which packet response this is
135: if (!array_key_exists($response_type, $this->responses)) {
136: throw new Exception(__METHOD__ . " response type '" . bin2hex($response_type) . "' is not valid");
137: }
138:
139: // Now we need to call the proper method
140: $results = array_merge(
141: $results,
142: call_user_func_array([$this, $this->responses[$response_type]], [$buffer])
143: );
144:
145: unset($buffer);
146: }
147:
148: return $results;
149: }
150:
151: /**
152: * Handle processing the server information
153: *
154: * @param Buffer $buffer
155: *
156: * @return array
157: * @throws \GameQ\Exception\Protocol
158: */
159: protected function processInfo(Buffer $buffer)
160: {
161: // Set the result to a new result instance
162: $result = new Result();
163:
164: $result->add('servername', $buffer->readString());
165: $result->add('mapname', $buffer->readString());
166: $result->add('modname', $buffer->readString());
167: $result->add('gamemode', $buffer->readString());
168: $result->add('description', $buffer->readString());
169: $result->add('version', $buffer->readString());
170: $result->add('port', $buffer->readInt16());
171: $result->add('num_players', $buffer->readInt8());
172: $result->add('max_players', $buffer->readInt8());
173: $result->add('dedicated', $buffer->readInt8());
174: $result->add('os', $buffer->readInt8());
175: $result->add('password', $buffer->readInt8());
176: $result->add('anticheat', $buffer->readInt8());
177: $result->add('average_fps', $buffer->readInt8());
178: $result->add('round', $buffer->readInt8());
179: $result->add('max_rounds', $buffer->readInt8());
180: $result->add('time_left', $buffer->readInt16());
181:
182: unset($buffer);
183:
184: return $result->fetch();
185: }
186:
187: /**
188: * Handle processing the server rules
189: *
190: * @param Buffer $buffer
191: *
192: * @return array
193: * @throws \GameQ\Exception\Protocol
194: */
195: protected function processRules(Buffer $buffer)
196: {
197: // Set the result to a new result instance
198: $result = new Result();
199:
200: // Burn extra header
201: $buffer->skip(1);
202:
203: // Read rules until we run out of buffer
204: while ($buffer->getLength()) {
205: $key = $buffer->readString();
206: // Check for map
207: if (strstr($key, "Map:")) {
208: $result->addSub("maplist", "name", $buffer->readString());
209: } else { // Regular rule
210: $result->add($key, $buffer->readString());
211: }
212: }
213:
214: unset($buffer);
215:
216: return $result->fetch();
217: }
218:
219: /**
220: * Handle processing of player data
221: *
222: * @todo: Build this out when there is a server with players to test against
223: *
224: * @param Buffer $buffer
225: *
226: * @return array
227: */
228: protected function processPlayers(Buffer $buffer)
229: {
230: // Set the result to a new result instance
231: $result = new Result();
232:
233: unset($buffer);
234:
235: return $result->fetch();
236: }
237: }
238: