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\Protocol;
24: use GameQ\Result;
25: use GameQ\Server;
26:
27: /**
28: * GTA Five M Protocol Class
29: *
30: * Server base can be found at https://fivem.net/
31: *
32: * Based on code found at https://github.com/LiquidObsidian/fivereborn-query
33: *
34: * @author Austin Bischoff <austin@codebeard.com>
35: *
36: * Adding FiveM Player List by
37: * @author Jesse Lukas <eranio@g-one.org>
38: */
39: class Cfx extends Protocol
40: {
41: /**
42: * Array of packets we want to look up.
43: * Each key should correspond to a defined method in this or a parent class
44: *
45: * @var array
46: */
47: protected $packets = [
48: self::PACKET_STATUS => "\xFF\xFF\xFF\xFFgetinfo xxx",
49: ];
50:
51: /**
52: * Use the response flag to figure out what method to run
53: *
54: * @var array
55: */
56: protected $responses = [
57: "\xFF\xFF\xFF\xFFinfoResponse" => "processStatus",
58: ];
59:
60: /**
61: * The query protocol used to make the call
62: *
63: * @var string
64: */
65: protected $protocol = 'cfx';
66:
67: /**
68: * String name of this protocol class
69: *
70: * @var string
71: */
72: protected $name = 'cfx';
73:
74: /**
75: * Longer string name of this protocol class
76: *
77: * @var string
78: */
79: protected $name_long = "CitizenFX";
80:
81: /**
82: * Holds the Player list so we can overwrite it back
83: *
84: * @var string
85: */
86: protected $PlayerList = [];
87:
88: /**
89: * Normalize settings for this protocol
90: *
91: * @var array
92: */
93: protected $normalize = [
94: // General
95: 'general' => [
96: // target => source
97: 'gametype' => 'gametype',
98: 'hostname' => 'hostname',
99: 'mapname' => 'mapname',
100: 'maxplayers' => 'sv_maxclients',
101: 'mod' => 'gamename',
102: 'numplayers' => 'clients',
103: 'password' => 'privateClients',
104: ],
105: ];
106:
107: /**
108: * Get FiveM players list using a sub query
109: *
110: * @throws \Exception
111: */
112: public function beforeSend(Server $server)
113: {
114: $GameQ = new \GameQ\GameQ();
115: $GameQ->addServer([
116: 'type' => 'cfxplayers',
117: 'host' => "$server->ip:$server->port_query",
118: ]);
119: $results = $GameQ->process();
120: $this->PlayerList = isset($results[0]) && isset($results[0][0]) ? $results[0][0] : [];
121: }
122:
123: /**
124: * Process the response
125: *
126: * @return array
127: * @throws \GameQ\Exception\Protocol
128: */
129: public function processResponse()
130: {
131: // In case it comes back as multiple packets (it shouldn't)
132: $buffer = new Buffer(implode('', $this->packets_response));
133:
134: // Figure out what packet response this is for
135: $response_type = $buffer->readString(PHP_EOL);
136:
137: // Figure out which packet response this is
138: if (empty($response_type) || !array_key_exists($response_type, $this->responses)) {
139: throw new Exception(__METHOD__ . " response type '{$response_type}' is not valid");
140: }
141:
142: // Offload the call
143: $results = call_user_func_array([$this, $this->responses[$response_type]], [$buffer]);
144:
145: return $results;
146: }
147:
148: // Internal methods
149:
150: /**
151: * Handle processing the status response
152: *
153: * @param Buffer $buffer
154: *
155: * @return array
156: */
157: protected function processStatus(Buffer $buffer)
158: {
159: // Set the result to a new result instance
160: $result = new Result();
161:
162: // Lets peek and see if the data starts with a \
163: if ($buffer->lookAhead(1) == '\\') {
164: // Burn the first one
165: $buffer->skip(1);
166: }
167:
168: // Explode the data
169: $data = explode('\\', $buffer->getBuffer());
170:
171: // No longer needed
172: unset($buffer);
173:
174: $itemCount = count($data);
175:
176: // Now lets loop the array
177: for ($x = 0; $x < $itemCount; $x += 2) {
178: // Set some local vars
179: $key = $data[$x];
180: $val = $data[$x + 1];
181:
182: if (in_array($key, ['challenge'])) {
183: continue; // skip
184: }
185:
186: // Regular variable so just add the value.
187: $result->add($key, $val);
188: }
189:
190: // Add result of sub http-protocol if available
191: if ($this->PlayerList) {
192: $result->add('players', $this->PlayerList);
193: }
194:
195: return $result->fetch();
196: }
197: }
198: