Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Factorio
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 6
650
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 query
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getProtocolName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getVersion
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 query_server
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 queryHTTP
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
182
1<?php declare(strict_types=1);
2
3/**
4 * Clansuite Server Query
5 *
6 * SPDX-FileCopyrightText: 2003-2025 Jens A. Koch
7 * SPDX-License-Identifier: MIT
8 *
9 * For the full copyright and license information, please view
10 * the LICENSE file that was distributed with this source code.
11 */
12
13namespace Clansuite\ServerQuery\ServerProtocols;
14
15use function count;
16use function file_get_contents;
17use function is_array;
18use function is_scalar;
19use function json_decode;
20use function stream_context_create;
21use Clansuite\Capture\Protocol\ProtocolInterface;
22use Clansuite\Capture\ServerAddress;
23use Clansuite\Capture\ServerInfo;
24use Clansuite\ServerQuery\CSQuery;
25use Override;
26
27/**
28 * Factorio server protocol implementation.
29 *
30 * Factorio uses HTTP API at https://multiplayer.factorio.com/get-game-details/{address}:{port}
31 * Returns JSON with server information.
32 *
33 * @see https://wiki.factorio.com/Multiplayer
34 */
35class Factorio extends CSQuery implements ProtocolInterface
36{
37    /**
38     * Protocol name.
39     */
40    public string $name = 'Factorio';
41
42    /**
43     * Protocol identifier.
44     */
45    public string $protocol = 'factorio';
46
47    /**
48     * Constructor.
49     */
50    public function __construct(mixed $address = null, mixed $queryport = null)
51    {
52        parent::__construct();
53        $this->address   = $address !== null ? (string) $address : null;
54        $this->queryport = $queryport !== null ? (int) $queryport : null;
55    }
56
57    /**
58     * query method.
59     */
60    #[Override]
61    public function query(ServerAddress $addr): ServerInfo
62    {
63        $info         = new ServerInfo;
64        $info->online = false;
65
66        if ($this->queryHTTP($addr, $info)) {
67            $info->online = true;
68        }
69
70        return $info;
71    }
72
73    /**
74     * getProtocolName method.
75     */
76    #[Override]
77    public function getProtocolName(): string
78    {
79        return $this->protocol;
80    }
81
82    /**
83     * getVersion method.
84     */
85    #[Override]
86    public function getVersion(ServerInfo $info): string
87    {
88        return $info->gameversion ?? 'unknown';
89    }
90
91    /**
92     * query_server method.
93     */
94    #[Override]
95    public function query_server(bool $getPlayers = true, bool $getRules = true): bool
96    {
97        // Use HTTP query
98        $addr = new ServerAddress($this->address ?? '', $this->queryport ?? 0);
99        $info = $this->query($addr);
100
101        $this->online      = $info->online;
102        $this->servertitle = $info->servertitle ?? '';
103        $this->mapname     = $info->mapname ?? '';
104        $this->numplayers  = $info->numplayers;
105        $this->maxplayers  = $info->maxplayers;
106        $this->gamename    = $info->gamename ?? '';
107        $this->gameversion = $info->gameversion ?? '';
108        $this->gametype    = $info->gametype ?? '';
109        $this->password    = (int) ($info->password ?? false);
110
111        if ($getPlayers && $info->players !== []) {
112            $this->players = $info->players;
113        }
114
115        if ($getRules && $info->rules !== []) {
116            $this->rules = $info->rules;
117        }
118
119        return $this->online;
120    }
121
122    private function queryHTTP(ServerAddress $addr, ServerInfo $info): bool
123    {
124        $host = $addr->ip;
125        $port = $addr->port;
126        $url  = "https://multiplayer.factorio.com/get-game-details/{$host}:{$port}";
127
128        $context = stream_context_create([
129            'http' => [
130                'timeout' => 5,
131            ],
132        ]);
133
134        $response = @file_get_contents($url, false, $context);
135
136        if ($response === false) {
137            return false;
138        }
139
140        $data = json_decode($response, true);
141
142        if ($data === null || !is_array($data)) {
143            return false;
144        }
145
146        // Parse the JSON data
147        $info->address     = $addr->ip . ':' . $addr->port;
148        $info->queryport   = $addr->port;
149        $info->gamename    = 'Factorio';
150        $info->servertitle = (string) ($data['name'] ?? '');
151        $info->mapname     = ''; // Factorio doesn't have maps
152        $info->gametype    = ''; // No game type
153        $playersData       = $data['players'] ?? [];
154        $info->numplayers  = is_array($playersData) ? count($playersData) : 0;
155        $info->maxplayers  = (int) ($data['max_players'] ?? 0);
156        $info->password    = (bool) ($data['has_password'] ?? false);
157        $appVer            = $data['application_version'] ?? [];
158
159        if (is_array($appVer)) {
160            $gameVer           = $appVer['game_version'] ?? '';
161            $buildVer          = $appVer['build_version'] ?? '';
162            $info->gameversion = (string) $gameVer . '.' . (string) $buildVer;
163        } else {
164            $info->gameversion = '';
165        }
166
167        // Players
168        $players = [];
169
170        if (isset($data['players']) && is_array($data['players'])) {
171            foreach ($data['players'] as $name) {
172                $players[] = ['name' => (string) $name, 'score' => 0, 'time' => 0];
173            }
174        }
175        $info->players = $players;
176
177        // Rules (server variables)
178        $rules = [];
179
180        foreach ($data as $key => $value) {
181            if (is_scalar($value)) {
182                $rules[(string) $key] = (string) $value;
183            } elseif (is_array($value) && $key === 'application_version') {
184                $rules['game_version']  = (string) ($value['game_version'] ?? '');
185                $rules['build_version'] = (string) ($value['build_version'] ?? '');
186            }
187        }
188        $info->rules = $rules;
189
190        return true;
191    }
192}