PocketMine-MP 5.15.1 git-08c6e63aac45a450fe3b437616ee8aa2500e04e2
PocketMine.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20*/
21
22declare(strict_types=1);
23
24namespace pocketmine {
25
26 use Composer\InstalledVersions;
39 use Symfony\Component\Filesystem\Path;
40 use function defined;
41 use function extension_loaded;
42 use function function_exists;
43 use function getcwd;
44 use function getopt;
45 use function is_dir;
46 use function mkdir;
47 use function phpversion;
48 use function preg_match;
49 use function preg_quote;
50 use function printf;
51 use function realpath;
52 use function version_compare;
53 use const DIRECTORY_SEPARATOR;
54 use const PHP_EOL;
55
56 require_once __DIR__ . '/VersionInfo.php';
57
58 const MIN_PHP_VERSION = "8.1.0";
59
64 function critical_error($message){
65 echo "[ERROR] $message" . PHP_EOL;
66 }
67
68 /*
69 * Startup code. Do not look at it, it may harm you.
70 * This is the only non-class based file on this project.
71 * Enjoy it as much as I did writing it. I don't want to do it again.
72 */
73
78 if(version_compare(MIN_PHP_VERSION, PHP_VERSION) > 0){
79 //If PHP version isn't high enough, anything below might break, so don't bother checking it.
80 return [
81 "PHP >= " . MIN_PHP_VERSION . " is required, but you have PHP " . PHP_VERSION . "."
82 ];
83 }
84
85 $messages = [];
86
87 if(PHP_INT_SIZE < 8){
88 $messages[] = "32-bit systems/PHP are no longer supported. Please upgrade to a 64-bit system, or use a 64-bit PHP binary if this is a 64-bit system.";
89 }
90
91 if(php_sapi_name() !== "cli"){
92 $messages[] = "Only PHP CLI is supported.";
93 }
94
95 $extensions = [
96 "chunkutils2" => "PocketMine ChunkUtils v2",
97 "curl" => "cURL",
98 "crypto" => "php-crypto",
99 "ctype" => "ctype",
100 "date" => "Date",
101 "gmp" => "GMP",
102 "hash" => "Hash",
103 "igbinary" => "igbinary",
104 "json" => "JSON",
105 "leveldb" => "LevelDB",
106 "mbstring" => "Multibyte String",
107 "morton" => "morton",
108 "openssl" => "OpenSSL",
109 "pcre" => "PCRE",
110 "phar" => "Phar",
111 "pmmpthread" => "pmmpthread",
112 "reflection" => "Reflection",
113 "sockets" => "Sockets",
114 "spl" => "SPL",
115 "yaml" => "YAML",
116 "zip" => "Zip",
117 "zlib" => "Zlib"
118 ];
119
120 foreach($extensions as $ext => $name){
121 if(!extension_loaded($ext)){
122 $messages[] = "Unable to find the $name ($ext) extension.";
123 }
124 }
125
126 if(($pmmpthread_version = phpversion("pmmpthread")) !== false){
127 if(version_compare($pmmpthread_version, "6.1.0") < 0 || version_compare($pmmpthread_version, "7.0.0") >= 0){
128 $messages[] = "pmmpthread ^6.1.0 is required, while you have $pmmpthread_version.";
129 }
130 }
131
132 if(($leveldb_version = phpversion("leveldb")) !== false){
133 if(version_compare($leveldb_version, "0.2.1") < 0){
134 $messages[] = "php-leveldb >= 0.2.1 is required, while you have $leveldb_version.";
135 }
136 if(!defined('LEVELDB_ZLIB_RAW_COMPRESSION')){
137 $messages[] = "Given version of php-leveldb doesn't support ZLIB_RAW compression (use https://github.com/pmmp/php-leveldb)";
138 }
139 }
140
141 $chunkutils2_version = phpversion("chunkutils2");
142 $wantedVersionLock = "0.3";
143 $wantedVersionMin = "$wantedVersionLock.0";
144 if($chunkutils2_version !== false && (
145 version_compare($chunkutils2_version, $wantedVersionMin) < 0 ||
146 preg_match("/^" . preg_quote($wantedVersionLock, "/") . "\.\d+(?:-dev)?$/", $chunkutils2_version) === 0 //lock in at ^0.2, optionally at a patch release
147 )){
148 $messages[] = "chunkutils2 ^$wantedVersionMin is required, while you have $chunkutils2_version.";
149 }
150
151 if(($libdeflate_version = phpversion("libdeflate")) !== false){
152 //make sure level 0 compression is available
153 if(version_compare($libdeflate_version, "0.2.0") < 0 || version_compare($libdeflate_version, "0.3.0") >= 0){
154 $messages[] = "php-libdeflate ^0.2.0 is required, while you have $libdeflate_version.";
155 }
156 }
157
158 if(extension_loaded("pocketmine")){
159 $messages[] = "The native PocketMine extension is no longer supported.";
160 }
161
162 if(!defined('AF_INET6')){
163 $messages[] = "IPv6 support is required, but your PHP binary was built without IPv6 support.";
164 }
165
166 return $messages;
167 }
168
173 if(ZEND_DEBUG_BUILD){
174 $logger->warning("This PHP binary was compiled in debug mode. This has a major impact on performance.");
175 }
176 if(extension_loaded("xdebug") && (!function_exists('xdebug_info') || count(xdebug_info('mode')) !== 0)){
177 $logger->warning("Xdebug extension is enabled. This has a major impact on performance.");
178 }
179 if(((int) ini_get('zend.assertions')) !== -1){
180 $logger->warning("Debugging assertions are enabled. This may degrade performance. To disable them, set `zend.assertions = -1` in php.ini.");
181 }
182 if(\Phar::running(true) === ""){
183 $logger->warning("Non-packaged installation detected. This will degrade autoloading speed and make startup times longer.");
184 }
185 if(function_exists('opcache_get_status') && ($opcacheStatus = opcache_get_status(false)) !== false){
186 $jitEnabled = $opcacheStatus["jit"]["on"] ?? false;
187 if($jitEnabled !== false){
188 $logger->warning(<<<'JIT_WARNING'
189
190
191 --------------------------------------- ! WARNING ! ---------------------------------------
192 You're using PHP with JIT enabled. This provides significant performance improvements.
193 HOWEVER, it is EXPERIMENTAL, and has already been seen to cause weird and unexpected bugs.
194 Proceed with caution.
195 If you want to report any bugs, make sure to mention that you have enabled PHP JIT.
196 To turn off JIT, change `opcache.jit` to `0` in your php.ini file.
197 -------------------------------------------------------------------------------------------
198
199JIT_WARNING
200);
201 }
202 }
203 }
204
208 function set_ini_entries(){
209 ini_set("allow_url_fopen", '1');
210 ini_set("display_errors", '1');
211 ini_set("display_startup_errors", '1');
212 ini_set("default_charset", "utf-8");
213 ini_set('assert.exception', '1');
214 }
215
216 function getopt_string(string $opt) : ?string{
217 $opts = getopt("", ["$opt:"]);
218 if(isset($opts[$opt])){
219 if(is_string($opts[$opt])){
220 return $opts[$opt];
221 }
222 if(is_array($opts[$opt])){
223 critical_error("Cannot specify --$opt multiple times");
224 }else{
225 critical_error("Missing value for --$opt");
226 }
227 exit(1);
228 }
229 return null;
230 }
231
235 function server(){
236 if(count($messages = check_platform_dependencies()) > 0){
237 echo PHP_EOL;
238 $binary = version_compare(PHP_VERSION, "5.4") >= 0 ? PHP_BINARY : "unknown";
239 critical_error("Selected PHP binary does not satisfy some requirements.");
240 foreach($messages as $m){
241 echo " - $m" . PHP_EOL;
242 }
243 critical_error("PHP binary used: " . $binary);
244 critical_error("Loaded php.ini: " . (($file = php_ini_loaded_file()) !== false ? $file : "none"));
245 $phprc = getenv("PHPRC");
246 critical_error("Value of PHPRC environment variable: " . ($phprc === false ? "" : $phprc));
247 critical_error("Please recompile PHP with the needed configuration, or refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
248 echo PHP_EOL;
249 exit(1);
250 }
251 unset($messages);
252
253 error_reporting(-1);
254 set_ini_entries();
255
256 $bootstrap = dirname(__FILE__, 2) . '/vendor/autoload.php';
257 if(!is_file($bootstrap)){
258 critical_error("Composer autoloader not found at " . $bootstrap);
259 critical_error("Please install/update Composer dependencies or use provided builds.");
260 exit(1);
261 }
262 require_once($bootstrap);
263
264 $composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
265 if($composerGitHash !== null){
266 //we can't verify dependency versions if we were installed without using git
267 $currentGitHash = explode("-", VersionInfo::GIT_HASH())[0];
268 if($currentGitHash !== $composerGitHash){
269 critical_error("Composer dependencies and/or autoloader are out of sync.");
270 critical_error("- Current revision is $currentGitHash");
271 critical_error("- Composer dependencies were last synchronized for revision $composerGitHash");
272 critical_error("Out-of-sync Composer dependencies may result in crashes and classes not being found.");
273 critical_error("Please synchronize Composer dependencies before running the server.");
274 exit(1);
275 }
276 }
277
279
280 if(count(getopt("", [BootstrapOptions::VERSION])) > 0){
281 printf("%s %s (git hash %s) for Minecraft: Bedrock Edition %s\n", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true), VersionInfo::GIT_HASH(), ProtocolInfo::MINECRAFT_VERSION);
282 exit(0);
283 }
284
285 $cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
286 $dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd;
287 $pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
288 Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
289
290 if(!@mkdir($dataPath, 0777, true) && !is_dir($dataPath)){
291 critical_error("Unable to create/access data directory at $dataPath. Check that the target location is accessible by the current user.");
292 exit(1);
293 }
294 //this has to be done after we're sure the data path exists
295 $dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
296
297 $lockFilePath = Path::join($dataPath, 'server.lock');
298 try{
299 $pid = Filesystem::createLockFile($lockFilePath);
300 }catch(\InvalidArgumentException $e){
301 critical_error($e->getMessage());
302 critical_error("Please ensure that there is enough space on the disk and that the current user has read/write permissions to the selected data directory $dataPath.");
303 exit(1);
304 }
305 if($pid !== null){
306 critical_error("Another " . VersionInfo::NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ").");
307 critical_error("Please stop the other server first before running a new one.");
308 exit(1);
309 }
310
311 if(!@mkdir($pluginPath, 0777, true) && !is_dir($pluginPath)){
312 critical_error("Unable to create plugin directory at $pluginPath. Check that the target location is accessible by the current user.");
313 exit(1);
314 }
315 $pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
316
317 //Logger has a dependency on timezone
318 Timezone::init();
319
321 if(isset($opts[BootstrapOptions::ENABLE_ANSI])){
322 Terminal::init(true);
323 }elseif(isset($opts[BootstrapOptions::DISABLE_ANSI])){
324 Terminal::init(false);
325 }else{
326 Terminal::init();
327 }
328 $logFile = isset($opts[BootstrapOptions::NO_LOG_FILE]) ? null : Path::join($dataPath, "server.log");
329
330 $logger = new MainLogger($logFile, Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()), false, Path::join($dataPath, "log_archive"));
331 if($logFile === null){
332 $logger->notice("Logging to file disabled. Ensure logs are collected by other means (e.g. Docker logs).");
333 }
334
335 \GlobalLogger::set($logger);
336
338
339 $exitCode = 0;
340 do{
341 if(!file_exists(Path::join($dataPath, "server.properties")) && !isset($opts[BootstrapOptions::NO_WIZARD])){
342 $installer = new SetupWizard($dataPath);
343 if(!$installer->run()){
344 $exitCode = -1;
345 break;
346 }
347 }
348
349 /*
350 * We now use the Composer autoloader, but this autoloader is still for loading plugins.
351 */
352 $autoloader = new ThreadSafeClassLoader();
353 $autoloader->register(false);
354
355 new Server($autoloader, $logger, $dataPath, $pluginPath);
356
357 $logger->info("Stopping other threads");
358
359 $killer = new ServerKiller(8);
360 $killer->start();
361 usleep(10000); //Fixes ServerKiller not being able to start on single-core machines
362
363 if(ThreadManager::getInstance()->stopAll() > 0){
364 $logger->debug("Some threads could not be stopped, performing a force-kill");
365 Process::kill(Process::pid());
366 }
367 }while(false);
368
369 $logger->shutdownLogWriterThread();
370
371 echo Terminal::$FORMAT_RESET . PHP_EOL;
372
373 Filesystem::releaseLockFile($lockFilePath);
374
375 exit($exitCode);
376 }
377
378 \pocketmine\server();
379}
static set(int $levels=E_WARNING|E_NOTICE)
static createLockFile(string $lockFilePath)
Definition: Filesystem.php:185
static releaseLockFile(string $lockFilePath)
Definition: Filesystem.php:214
static kill(int $pid, bool $subprocesses=false)
Definition: Process.php:131
static assumeNotFalse(mixed $value, \Closure|string $context="This should never be false")
Definition: Utils.php:623
debug($message)
notice($message)
info($message)
warning($message)
critical_error($message)
Definition: PocketMine.php:64
check_platform_dependencies()
Definition: PocketMine.php:77
emit_performance_warnings(\Logger $logger)
Definition: PocketMine.php:172