PocketMine-MP 5.23.3 git-f7687af337d001ddbcc47b8e773f014a33faa662
Loading...
Searching...
No Matches
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
278 ErrorToExceptionHandler::set();
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 if(defined('pocketmine\ORIGINAL_PHAR_PATH')){
286 //if we're inside a phar cache, \pocketmine\PATH will not include the original phar
287 Filesystem::addCleanedPath(ORIGINAL_PHAR_PATH, Filesystem::CLEAN_PATH_SRC_PREFIX);
288 }
289
290 $cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
291 $dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd;
292 $pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
293 Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
294
295 if(!@mkdir($dataPath, 0777, true) && !is_dir($dataPath)){
296 critical_error("Unable to create/access data directory at $dataPath. Check that the target location is accessible by the current user.");
297 exit(1);
298 }
299 //this has to be done after we're sure the data path exists
300 $dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
301
302 $lockFilePath = Path::join($dataPath, 'server.lock');
303 try{
304 $pid = Filesystem::createLockFile($lockFilePath);
305 }catch(\InvalidArgumentException $e){
306 critical_error($e->getMessage());
307 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.");
308 exit(1);
309 }
310 if($pid !== null){
311 critical_error("Another " . VersionInfo::NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ").");
312 critical_error("Please stop the other server first before running a new one.");
313 exit(1);
314 }
315
316 if(!@mkdir($pluginPath, 0777, true) && !is_dir($pluginPath)){
317 critical_error("Unable to create plugin directory at $pluginPath. Check that the target location is accessible by the current user.");
318 exit(1);
319 }
320 $pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
321
322 //Logger has a dependency on timezone
323 Timezone::init();
324
326 if(isset($opts[BootstrapOptions::ENABLE_ANSI])){
327 Terminal::init(true);
328 }elseif(isset($opts[BootstrapOptions::DISABLE_ANSI])){
329 Terminal::init(false);
330 }else{
331 Terminal::init();
332 }
333 $logFile = isset($opts[BootstrapOptions::NO_LOG_FILE]) ? null : Path::join($dataPath, "server.log");
334
335 $logger = new MainLogger($logFile, Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()), false, Path::join($dataPath, "log_archive"));
336 if($logFile === null){
337 $logger->notice("Logging to file disabled. Ensure logs are collected by other means (e.g. Docker logs).");
338 }
339
340 \GlobalLogger::set($logger);
341
343
344 $exitCode = 0;
345 do{
346 if(!file_exists(Path::join($dataPath, "server.properties")) && !isset($opts[BootstrapOptions::NO_WIZARD])){
347 $installer = new SetupWizard($dataPath);
348 if(!$installer->run()){
349 $exitCode = -1;
350 break;
351 }
352 }
353
354 /*
355 * We now use the Composer autoloader, but this autoloader is still for loading plugins.
356 */
357 $autoloader = new ThreadSafeClassLoader();
358 $autoloader->register(false);
359
360 new Server($autoloader, $logger, $dataPath, $pluginPath);
361
362 $logger->info("Stopping other threads");
363
364 $killer = new ServerKiller(8);
365 $killer->start();
366 usleep(10000); //Fixes ServerKiller not being able to start on single-core machines
367
368 if(ThreadManager::getInstance()->stopAll() > 0){
369 $logger->debug("Some threads could not be stopped, performing a force-kill");
370 Process::kill(Process::pid());
371 }
372 }while(false);
373
374 $logger->shutdownLogWriterThread();
375
376 echo Terminal::$FORMAT_RESET . PHP_EOL;
377
378 Filesystem::releaseLockFile($lockFilePath);
379
380 exit($exitCode);
381 }
382
383 \pocketmine\server();
384}
debug($message)
notice($message)
info($message)
warning($message)
critical_error($message)
check_platform_dependencies()
emit_performance_warnings(\Logger $logger)