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\Result;
23:
24: /**
25: * Class Armed Assault 3
26: *
27: * Rules protocol reference: https://community.bistudio.com/wiki/Arma_3_ServerBrowserProtocol2
28: *
29: * @package GameQ\Protocols
30: * @author Austin Bischoff <austin@codebeard.com>
31: * @author Memphis017 <https://github.com/Memphis017>
32: */
33: class Arma3 extends Source
34: {
35: // Base DLC names
36: const BASE_DLC_KART = 'Karts';
37: const BASE_DLC_MARKSMEN = 'Marksmen';
38: const BASE_DLC_HELI = 'Helicopters';
39: const BASE_DLC_CURATOR = 'Curator';
40: const BASE_DLC_EXPANSION = 'Expansion';
41: const BASE_DLC_JETS = 'Jets';
42: const BASE_DLC_ORANGE = 'Laws of War';
43: const BASE_DLC_ARGO = 'Malden';
44: const BASE_DLC_TACOPS = 'Tac-Ops';
45: const BASE_DLC_TANKS = 'Tanks';
46: const BASE_DLC_CONTACT = 'Contact';
47: const BASE_DLC_ENOCH = 'Contact (Platform)';
48:
49: // Special
50: const BASE_DLC_AOW = 'Art of War';
51:
52: // Creator DLC names
53: const CREATOR_DLC_GM = 'Global Mobilization';
54: const CREATOR_DLC_VN = 'S.O.G. Prairie Fire';
55: const CREATOR_DLC_CSLA = 'ČSLA - Iron Curtain';
56: const CREATOR_DLC_WS = 'Western Sahara';
57:
58: /**
59: * DLC Flags/Bits as defined in the documentation.
60: *
61: * @see https://community.bistudio.com/wiki/Arma_3:_ServerBrowserProtocol3
62: *
63: * @var array
64: */
65: protected $dlcFlags = [
66: 0b0000000000000001 => self::BASE_DLC_KART,
67: 0b0000000000000010 => self::BASE_DLC_MARKSMEN,
68: 0b0000000000000100 => self::BASE_DLC_HELI,
69: 0b0000000000001000 => self::BASE_DLC_CURATOR,
70: 0b0000000000010000 => self::BASE_DLC_EXPANSION,
71: 0b0000000000100000 => self::BASE_DLC_JETS,
72: 0b0000000001000000 => self::BASE_DLC_ORANGE,
73: 0b0000000010000000 => self::BASE_DLC_ARGO,
74: 0b0000000100000000 => self::BASE_DLC_TACOPS,
75: 0b0000001000000000 => self::BASE_DLC_TANKS,
76: 0b0000010000000000 => self::BASE_DLC_CONTACT,
77: 0b0000100000000000 => self::BASE_DLC_ENOCH,
78: 0b0001000000000000 => self::BASE_DLC_AOW,
79: 0b0010000000000000 => 'Unknown',
80: 0b0100000000000000 => 'Unknown',
81: 0b1000000000000000 => 'Unknown',
82: ];
83:
84: /**
85: * String name of this protocol class
86: *
87: * @var string
88: */
89: protected $name = 'arma3';
90:
91: /**
92: * Longer string name of this protocol class
93: *
94: * @var string
95: */
96: protected $name_long = "Arma3";
97:
98: /**
99: * Query port = client_port + 1
100: *
101: * @var int
102: */
103: protected $port_diff = 1;
104:
105: /**
106: * Process the rules since Arma3 changed their response for rules
107: *
108: * @param Buffer $buffer
109: *
110: * @return array
111: * @throws \GameQ\Exception\Protocol
112: */
113: protected function processRules(Buffer $buffer)
114: {
115: // Total number of packets, burn it
116: $buffer->readInt16();
117:
118: // Will hold the data string
119: $data = '';
120:
121: // Loop until we run out of strings
122: while ($buffer->getLength()) {
123: // Burn the delimiters (i.e. \x01\x04\x00)
124: $buffer->readString();
125:
126: // Add the data to the string, we are reassembling it
127: $data .= $buffer->readString();
128: }
129:
130: // Restore escaped sequences
131: $data = str_replace(["\x01\x01", "\x01\x02", "\x01\x03"], ["\x01", "\x00", "\xFF"], $data);
132:
133: // Make a new buffer with the reassembled data
134: $responseBuffer = new Buffer($data);
135:
136: // Kill the old buffer, should be empty
137: unset($buffer, $data);
138:
139: // Set the result to a new result instance
140: $result = new Result();
141:
142: // Get results
143: $result->add('rules_protocol_version', $responseBuffer->readInt8()); // read protocol version
144: $result->add('overflow', $responseBuffer->readInt8()); // Read overflow flags
145: $dlcByte = $responseBuffer->readInt8(); // Grab DLC byte 1 and use it later
146: $dlcByte2 = $responseBuffer->readInt8(); // Grab DLC byte 2 and use it later
147: $dlcBits = ($dlcByte2 << 8) | $dlcByte; // concatenate DLC bits to 16 Bit int
148:
149: // Grab difficulty so we can man handle it...
150: $difficulty = $responseBuffer->readInt8();
151:
152: // Process difficulty
153: $result->add('3rd_person', $difficulty >> 7);
154: $result->add('advanced_flight_mode', ($difficulty >> 6) & 1);
155: $result->add('difficulty_ai', ($difficulty >> 3) & 3);
156: $result->add('difficulty_level', $difficulty & 3);
157:
158: unset($difficulty);
159:
160: // Crosshair
161: $result->add('crosshair', $responseBuffer->readInt8());
162:
163: // Loop over the base DLC bits so we can pull in the info for the DLC (if enabled)
164: foreach ($this->dlcFlags as $dlcFlag => $dlcName) {
165: // Check that the DLC bit is enabled
166: if (($dlcBits & $dlcFlag) === $dlcFlag) {
167: // Add the DLC to the list
168: $result->addSub('dlcs', 'name', $dlcName);
169: $result->addSub('dlcs', 'hash', dechex($responseBuffer->readInt32()));
170: }
171: }
172:
173: // Read the mount of mods, these include DLC as well as Creator DLC and custom modifications
174: $modCount = $responseBuffer->readInt8();
175:
176: // Add mod count
177: $result->add('mod_count', $modCount);
178:
179: // Loop over the mods
180: while ($modCount) {
181: // Read the mods hash
182: $result->addSub('mods', 'hash', dechex($responseBuffer->readInt32()));
183:
184: // Get the information byte containing DLC flag and steamId length
185: $infoByte = $responseBuffer->readInt8();
186:
187: // Determine isDLC by flag, first bit in upper nibble
188: $result->addSub('mods', 'dlc', ($infoByte & 0b00010000) === 0b00010000);
189:
190: // Read the steam id of the mod/CDLC (might be less than 4 bytes)
191: $result->addSub('mods', 'steam_id', $responseBuffer->readInt32($infoByte & 0x0F));
192:
193: // Read the name of the mod
194: $result->addSub('mods', 'name', $responseBuffer->readPascalString(0, true) ?: 'Unknown');
195:
196: --$modCount;
197: }
198:
199: // No longer needed
200: unset($dlcByte, $dlcByte2, $dlcBits);
201:
202: // Get the signatures count
203: $signatureCount = $responseBuffer->readInt8();
204: $result->add('signature_count', $signatureCount);
205:
206: // Make signatures array
207: $signatures = [];
208:
209: // Loop until we run out of signatures
210: for ($x = 0; $x < $signatureCount; $x++) {
211: $signatures[] = $responseBuffer->readPascalString(0, true);
212: }
213:
214: // Add as a simple array
215: $result->add('signatures', $signatures);
216:
217: unset($responseBuffer, $signatureCount, $signatures, $x);
218:
219: return $result->fetch();
220: }
221: }
222: