Passed
Pull Request — v3 (#594)
by
unknown
07:21
created

Arma3::readWorkshopId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 3
ccs 0
cts 3
cp 0
rs 10
cc 1
nc 1
nop 2
crap 2
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 <[email protected]>
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
     * @type string
88
     */
89
    protected $name = 'arma3';
90
91
    /**
92
     * Longer string name of this protocol class
93
     *
94
     * @type string
95
     */
96
    protected $name_long = "Arma3";
97
98
    /**
99
     * Query port = client_port + 1
100
     *
101
     * @type 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 4
    protected function processRules(Buffer $buffer)
114
    {
115
        // Total number of packets, burn it
116 4
        $buffer->readInt16();
117
118
        // Will hold the data string
119 4
        $data = '';
120
121
        // Loop until we run out of strings
122 4
        while ($buffer->getLength()) {
123
            // Burn the delimiters (i.e. \x01\x04\x00)
124 4
            $buffer->readString();
125
126
            // Add the data to the string, we are reassembling it
127 4
            $data .= $buffer->readString();
128 4
        }
129
130
        // Restore escaped sequences
131 4
        $data = str_replace(["\x01\x01", "\x01\x02", "\x01\x03"], ["\x01", "\x00", "\xFF"], $data);
132
133
        // Make a new buffer with the reassembled data
134 4
        $responseBuffer = new Buffer($data);
135
136
        // Kill the old buffer, should be empty
137 4
        unset($buffer, $data);
138
139
        // Set the result to a new result instance
140 4
        $result = new Result();
141
142
        // Get results
143 4
        $result->add('rules_protocol_version', $responseBuffer->readInt8()); // read protocol version
144 4
        $result->add('overflow', $responseBuffer->readInt8()); // Read overflow flags
145 4
        $dlcByte = $responseBuffer->readInt8(); // Grab DLC byte 1 and use it later
146 4
        $dlcByte2 = $responseBuffer->readInt8(); // Grab DLC byte 2 and use it later
147 4
        $dlcBits = ($dlcByte2 << 8) | $dlcByte; // concatenate DLC bits to 16 Bit int
148
149
        // Grab difficulty so we can man handle it...
150 4
        $difficulty = $responseBuffer->readInt8();
151
152
        // Process difficulty
153 4
        $result->add('3rd_person', $difficulty >> 7);
154 4
        $result->add('advanced_flight_mode', ($difficulty >> 6) & 1);
155 4
        $result->add('difficulty_ai', ($difficulty >> 3) & 3);
156 4
        $result->add('difficulty_level', $difficulty & 3);
157
158 4
        unset($difficulty);
159
160
        // Crosshair
161 4
        $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 4
        foreach ($this->dlcFlags as $dlcFlag => $dlcName) {
165
            // Check that the DLC bit is enabled
166 4
            if (($dlcBits & $dlcFlag) === $dlcFlag) {
167
                // Add the DLC to the list
168 4
                $result->addSub('dlcs', 'name', $dlcName);
169 4
                $result->addSub('dlcs', 'hash', dechex($responseBuffer->readInt32()));
170 4
            }
171 4
        }
172
173
        // Read the mount of mods, these include DLC as well as Creator DLC and custom modifications
174 4
        $modCount = $responseBuffer->readInt8();
175
176
        // Add mod count
177 4
        $result->add('mod_count', $modCount);
178
        
179
        // Loop over the mods
180 4
        while ($modCount) {
181
            // Read the mods hash 
182 2
            $result->addSub('mods', 'hash', dechex($responseBuffer->readInt32()));
183
184
            // Get the information byte containing DLC flag and steamId length
185 2
            $infoByte = $responseBuffer->readInt8();
186
187
            // Determine isDLC by flag, first bit in upper nibble
188 2
            $isDLC = ($dlcBits & 0b00010000) === 0b00010000;
189 2
            $result->addSub('mods', 'dlc', $isDLC);
190
            
191 2
            if ($isDLC) {
192
                // TODO no clue what to do here
193 2
                $responseBuffer->read($infoByte & 0x0F);
194 2
            } else {
195
                // Read workshop id and unpack as 32bit int
196
                $result->addSub('mods', 'steam_id', self::readWorkshopId($responseBuffer, $infoByte & 0x0F));
197
            }
198
199
            // Read the name of the mod
200 2
            $result->addSub('mods', 'name', $responseBuffer->readPascalString(0, true) ?: 'Unknown');
201
202 2
            --$modCount;
203 2
        }
204
205
        // No longer needed
206 4
        unset($dlcByte, $dlcByte2, $dlcBits);
207
208
        // Get the signatures count
209 4
        $signatureCount = $responseBuffer->readInt8();
210 4
        $result->add('signature_count', $signatureCount);
211
212
        // Make signatures array
213 4
        $signatures = [];
214
215
        // Loop until we run out of signatures
216 4
        for ($x = 0; $x < $signatureCount; $x++) {
217 3
            $signatures[] = $responseBuffer->readPascalString(0, true);
218 3
        }
219
220
        // Add as a simple array
221 4
        $result->add('signatures', $signatures);
222
223 4
        unset($responseBuffer, $signatureCount, $signatures, $x);
224
225 4
        return $result->fetch();
226
    }
227
228
    private static function readWorkshopId(Buffer $buffer, $length = 4) {
229
        $unpacked = unpack('Vint', $buffer->read($length));
230
        return array_shift($unpacked);
231
    }
232
}