PocketMine-MP 5.35.1 git-e32e836dad793a3a3c8ddd8927c00e112b1e576a
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.3.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 "encoding" => "pmmp/ext-encoding",
102 "gmp" => "GMP",
103 "hash" => "Hash",
104 "igbinary" => "igbinary",
105 "json" => "JSON",
106 "leveldb" => "LevelDB",
107 "mbstring" => "Multibyte String",
108 "morton" => "morton",
109 "openssl" => "OpenSSL",
110 "pcre" => "PCRE",
111 "phar" => "Phar",
112 "pmmpthread" => "pmmpthread",
113 "reflection" => "Reflection",
114 "sockets" => "Sockets",
115 "spl" => "SPL",
116 "yaml" => "YAML",
117 "zip" => "Zip",
118 "zlib" => "Zlib"
119 ];
120
121 foreach($extensions as $ext => $name){
122 if(!extension_loaded($ext)){
123 $messages[] = "Unable to find the $name ($ext) extension.";
124 }
125 }
126
127 if(($pmmpthread_version = phpversion("pmmpthread")) !== false){
128 if(version_compare($pmmpthread_version, "6.1.0") < 0 || version_compare($pmmpthread_version, "7.0.0") >= 0){
129 $messages[] = "pmmpthread ^6.1.0 is required, while you have $pmmpthread_version.";
130 }
131 }
132
133 if(($leveldb_version = phpversion("leveldb")) !== false){
134 if(version_compare($leveldb_version, "0.2.1") < 0){
135 $messages[] = "php-leveldb >= 0.2.1 is required, while you have $leveldb_version.";
136 }
137 if(!defined('LEVELDB_ZLIB_RAW_COMPRESSION')){
138 $messages[] = "Given version of php-leveldb doesn't support ZLIB_RAW compression (use https://github.com/pmmp/php-leveldb)";
139 }
140 }
141
142 $chunkutils2_version = phpversion("chunkutils2");
143 $wantedVersionLock = "0.3";
144 $wantedVersionMin = "$wantedVersionLock.0";
145 if($chunkutils2_version !== false && (
146 version_compare($chunkutils2_version, $wantedVersionMin) < 0 ||
147 preg_match("/^" . preg_quote($wantedVersionLock, "/") . "\.\d+(?:-dev)?$/", $chunkutils2_version) === 0 //lock in at ^0.2, optionally at a patch release
148 )){
149 $messages[] = "chunkutils2 ^$wantedVersionMin is required, while you have $chunkutils2_version.";
150 }
151
152 if(($libdeflate_version = phpversion("libdeflate")) !== false){
153 //make sure level 0 compression is available
154 if(version_compare($libdeflate_version, "0.2.0") < 0 || version_compare($libdeflate_version, "0.3.0") >= 0){
155 $messages[] = "php-libdeflate ^0.2.0 is required, while you have $libdeflate_version.";
156 }
157 }
158
159 if(($encoding_version = phpversion("encoding")) !== false){
160 if(version_compare($encoding_version, "1.0.0") < 0 || version_compare($encoding_version, "2.0.0") >= 0){
161 $messages[] = "pmmp/ext-encoding ^1.0.0 is required, while you have $encoding_version.";
162 }
163 }
164
165 if(extension_loaded("pocketmine")){
166 $messages[] = "The native PocketMine extension is no longer supported.";
167 }
168
169 if(!defined('AF_INET6')){
170 $messages[] = "IPv6 support is required, but your PHP binary was built without IPv6 support.";
171 }
172
173 return $messages;
174 }
175
180 if(ZEND_DEBUG_BUILD){
181 $logger->warning("This PHP binary was compiled in debug mode. This has a major impact on performance.");
182 }
183 if(extension_loaded("xdebug") && (!function_exists('xdebug_info') || count(xdebug_info('mode')) !== 0)){
184 $logger->warning("Xdebug extension is enabled. This has a major impact on performance.");
185 }
186 if(((int) ini_get('zend.assertions')) !== -1){
187 $logger->warning("Debugging assertions are enabled. This may degrade performance. To disable them, set `zend.assertions = -1` in php.ini.");
188 }
189 if(\Phar::running(true) === ""){
190 $logger->warning("Non-packaged installation detected. This will degrade autoloading speed and make startup times longer.");
191 }
192 if(function_exists('opcache_get_status') && ($opcacheStatus = opcache_get_status(false)) !== false){
193 $jitEnabled = $opcacheStatus["jit"]["on"] ?? false;
194 if($jitEnabled !== false){
195 $logger->warning(<<<'JIT_WARNING'
196
197
198 --------------------------------------- ! WARNING ! ---------------------------------------
199 You're using PHP with JIT enabled. This provides significant performance improvements.
200 HOWEVER, it is EXPERIMENTAL, and has already been seen to cause weird and unexpected bugs.
201 Proceed with caution.
202 If you want to report any bugs, make sure to mention that you have enabled PHP JIT.
203 To turn off JIT, change `opcache.jit` to `0` in your php.ini file.
204 -------------------------------------------------------------------------------------------
205
206JIT_WARNING
207);
208 }
209 }
210 }
211
215 function set_ini_entries(){
216 ini_set("allow_url_fopen", '1');
217 ini_set("display_errors", '1');
218 ini_set("display_startup_errors", '1');
219 ini_set("default_charset", "utf-8");
220 ini_set('assert.exception', '1');
221 }
222
223 function getopt_string(string $opt) : ?string{
224 $opts = getopt("", ["$opt:"]);
225 if(isset($opts[$opt])){
226 if(is_string($opts[$opt])){
227 return $opts[$opt];
228 }
229 if(is_array($opts[$opt])){
230 critical_error("Cannot specify --$opt multiple times");
231 }else{
232 critical_error("Missing value for --$opt");
233 }
234 exit(1);
235 }
236 return null;
237 }
238
242 function server(){
243 if(count($messages = check_platform_dependencies()) > 0){
244 echo PHP_EOL;
245 $binary = version_compare(PHP_VERSION, "5.4") >= 0 ? PHP_BINARY : "unknown";
246 critical_error("Selected PHP binary does not satisfy some requirements.");
247 foreach($messages as $m){
248 echo " - $m" . PHP_EOL;
249 }
250 critical_error("PHP binary used: " . $binary);
251 critical_error("Loaded php.ini: " . (($file = php_ini_loaded_file()) !== false ? $file : "none"));
252 $phprc = getenv("PHPRC");
253 critical_error("Value of PHPRC environment variable: " . ($phprc === false ? "" : $phprc));
254 critical_error("Please recompile PHP with the needed configuration, or refer to the installation instructions at http://pmmp.rtfd.io/en/rtfd/installation.html.");
255 echo PHP_EOL;
256 exit(1);
257 }
258 unset($messages);
259
260 error_reporting(-1);
261 set_ini_entries();
262
263 $bootstrap = dirname(__FILE__, 2) . '/vendor/autoload.php';
264 if(!is_file($bootstrap)){
265 critical_error("Composer autoloader not found at " . $bootstrap);
266 critical_error("Please install/update Composer dependencies or use provided builds.");
267 exit(1);
268 }
269 require_once($bootstrap);
270
271 $composerGitHash = InstalledVersions::getReference('pocketmine/pocketmine-mp');
272 if($composerGitHash !== null){
273 //we can't verify dependency versions if we were installed without using git
274 $currentGitHash = explode("-", VersionInfo::GIT_HASH(), 2)[0];
275 if($currentGitHash !== $composerGitHash){
276 critical_error("Composer dependencies and/or autoloader are out of sync.");
277 critical_error("- Current revision is $currentGitHash");
278 critical_error("- Composer dependencies were last synchronized for revision $composerGitHash");
279 critical_error("Out-of-sync Composer dependencies may result in crashes and classes not being found.");
280 critical_error("Please synchronize Composer dependencies before running the server.");
281 exit(1);
282 }
283 }
284
285 ErrorToExceptionHandler::set();
286
287 if(count(getopt("", [BootstrapOptions::VERSION])) > 0){
288 printf("%s %s (git hash %s) for Minecraft: Bedrock Edition %s\n", VersionInfo::NAME, VersionInfo::VERSION()->getFullVersion(true), VersionInfo::GIT_HASH(), ProtocolInfo::MINECRAFT_VERSION);
289 exit(0);
290 }
291
292 if(defined('pocketmine\ORIGINAL_PHAR_PATH')){
293 //if we're inside a phar cache, \pocketmine\PATH will not include the original phar
294 Filesystem::addCleanedPath(ORIGINAL_PHAR_PATH, Filesystem::CLEAN_PATH_SRC_PREFIX);
295 }
296
297 $cwd = Utils::assumeNotFalse(realpath(Utils::assumeNotFalse(getcwd())));
298 $dataPath = getopt_string(BootstrapOptions::DATA) ?? $cwd;
299 $pluginPath = getopt_string(BootstrapOptions::PLUGINS) ?? $cwd . DIRECTORY_SEPARATOR . "plugins";
300 Filesystem::addCleanedPath($pluginPath, Filesystem::CLEAN_PATH_PLUGINS_PREFIX);
301
302 if(!@mkdir($dataPath, 0777, true) && !is_dir($dataPath)){
303 critical_error("Unable to create/access data directory at $dataPath. Check that the target location is accessible by the current user.");
304 exit(1);
305 }
306 //this has to be done after we're sure the data path exists
307 $dataPath = realpath($dataPath) . DIRECTORY_SEPARATOR;
308
309 $lockFilePath = Path::join($dataPath, 'server.lock');
310 try{
311 $pid = Filesystem::createLockFile($lockFilePath);
312 }catch(\InvalidArgumentException $e){
313 critical_error($e->getMessage());
314 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.");
315 exit(1);
316 }
317 if($pid !== null){
318 critical_error("Another " . VersionInfo::NAME . " instance (PID $pid) is already using this folder (" . realpath($dataPath) . ").");
319 critical_error("Please stop the other server first before running a new one.");
320 exit(1);
321 }
322
323 if(!@mkdir($pluginPath, 0777, true) && !is_dir($pluginPath)){
324 critical_error("Unable to create plugin directory at $pluginPath. Check that the target location is accessible by the current user.");
325 exit(1);
326 }
327 $pluginPath = realpath($pluginPath) . DIRECTORY_SEPARATOR;
328
329 //Logger has a dependency on timezone
330 Timezone::init();
331
333 if(isset($opts[BootstrapOptions::ENABLE_ANSI])){
334 Terminal::init(true);
335 }elseif(isset($opts[BootstrapOptions::DISABLE_ANSI])){
336 Terminal::init(false);
337 }else{
338 Terminal::init();
339 }
340 $logFile = isset($opts[BootstrapOptions::NO_LOG_FILE]) ? null : Path::join($dataPath, "server.log");
341
342 $logger = new MainLogger($logFile, Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()), false, Path::join($dataPath, "log_archive"));
343 if($logFile === null){
344 $logger->notice("Logging to file disabled. Ensure logs are collected by other means (e.g. Docker logs).");
345 }
346
347 \GlobalLogger::set($logger);
348
350
351 $exitCode = 0;
352 do{
353 if(!file_exists(Path::join($dataPath, "server.properties")) && !isset($opts[BootstrapOptions::NO_WIZARD])){
354 $installer = new SetupWizard($dataPath);
355 if(!$installer->run()){
356 $exitCode = -1;
357 break;
358 }
359 }
360
361 /*
362 * We now use the Composer autoloader, but this autoloader is still for loading plugins.
363 */
364 $autoloader = new ThreadSafeClassLoader();
365 $autoloader->register(false);
366
367 new Server($autoloader, $logger, $dataPath, $pluginPath);
368
369 $logger->info("Stopping other threads");
370
371 $killer = new ServerKiller(8);
372 $killer->start();
373 usleep(10000); //Fixes ServerKiller not being able to start on single-core machines
374
375 if(ThreadManager::getInstance()->stopAll() > 0){
376 $logger->debug("Some threads could not be stopped, performing a force-kill");
377 Process::kill(Process::pid());
378 }
379 }while(false);
380
381 $logger->shutdownLogWriterThread();
382
383 echo Terminal::$FORMAT_RESET . PHP_EOL;
384
385 Filesystem::releaseLockFile($lockFilePath);
386
387 exit($exitCode);
388 }
389
390 \pocketmine\server();
391}
debug($message)
notice($message)
info($message)
warning($message)
critical_error($message)
check_platform_dependencies()
emit_performance_warnings(\Logger $logger)