server = $server; $this->pluginManager = $pluginManager; $this->data = new CrashDumpData(); $this->data->format_version = self::FORMAT_VERSION; $this->data->time = $now; $this->data->uptime = $now - $this->server->getStartTime(); $this->baseCrash(); $this->generalData(); $this->pluginsData(); $this->extraData(); $json = json_encode($this->data, JSON_UNESCAPED_SLASHES); if($json === false){ throw new \RuntimeException("Failed to encode crashdump JSON: " . json_last_error_msg()); } $zlibEncoded = zlib_encode($json, ZLIB_ENCODING_DEFLATE, 9); if($zlibEncoded === false) throw new AssumptionFailedError("ZLIB compression failed"); $this->encodedData = $zlibEncoded; } public function getEncodedData() : string{ return $this->encodedData; } public function getData() : CrashDumpData{ return $this->data; } public function encodeData(CrashDumpRenderer $renderer) : void{ $renderer->addLine(); $renderer->addLine("----------------------REPORT THE DATA BELOW THIS LINE-----------------------"); $renderer->addLine(); $renderer->addLine("===BEGIN CRASH DUMP==="); foreach(str_split(base64_encode($this->encodedData), 76) as $line){ $renderer->addLine($line); } $renderer->addLine("===END CRASH DUMP==="); } private function pluginsData() : void{ if($this->pluginManager !== null){ $plugins = $this->pluginManager->getPlugins(); ksort($plugins, SORT_STRING); foreach($plugins as $p){ $d = $p->getDescription(); $this->data->plugins[$d->getName()] = new CrashDumpDataPluginEntry( name: $d->getName(), version: $d->getVersion(), authors: $d->getAuthors(), api: $d->getCompatibleApis(), enabled: $p->isEnabled(), depends: $d->getDepend(), softDepends: $d->getSoftDepend(), main: $d->getMain(), load: mb_strtoupper($d->getOrder()->name()), website: $d->getWebsite() ); } } } private function extraData() : void{ global $argv; if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-settings", true)){ $this->data->parameters = (array) $argv; if(($serverDotProperties = @file_get_contents(Path::join($this->server->getDataPath(), "server.properties"))) !== false){ $this->data->serverDotProperties = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties) ?? throw new AssumptionFailedError("Pattern is valid"); } if(($pocketmineDotYml = @file_get_contents(Path::join($this->server->getDataPath(), "pocketmine.yml"))) !== false){ $this->data->pocketmineDotYml = $pocketmineDotYml; } } $extensions = []; foreach(get_loaded_extensions() as $ext){ $version = phpversion($ext); if($version === false) throw new AssumptionFailedError(); $extensions[$ext] = $version; } $this->data->extensions = $extensions; if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){ ob_start(); phpinfo(); $this->data->phpinfo = ob_get_contents(); // @phpstan-ignore-line ob_end_clean(); } } private function baseCrash() : void{ global $lastExceptionError, $lastError; if(isset($lastExceptionError)){ $error = $lastExceptionError; }else{ $error = error_get_last(); if($error === null){ throw new \RuntimeException("Crash error information missing - did something use exit()?"); } $error["trace"] = Utils::currentTrace(3); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump $error["fullFile"] = $error["file"]; $error["file"] = Filesystem::cleanPath($error["file"]); try{ $error["type"] = ErrorTypeToStringMap::get($error["type"]); }catch(\InvalidArgumentException $e){ //pass } if(($pos = strpos($error["message"], "\n")) !== false){ $error["message"] = substr($error["message"], 0, $pos); } } if(isset($lastError)){ if(isset($lastError["trace"])){ $lastError["trace"] = Utils::printableTrace($lastError["trace"]); } $this->data->lastError = $lastError; } $this->data->error = $error; unset($this->data->error["fullFile"]); unset($this->data->error["trace"]); $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_NONE; if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace foreach($error["trace"] as $frame){ if(!isset($frame["file"])){ continue; //PHP core } if($this->determinePluginFromFile($frame["file"], false)){ break; } } } if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-code", true) and file_exists($error["fullFile"])){ $file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES); if($file !== false){ for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 and isset($file[$l]); ++$l){ $this->data->code[$l + 1] = $file[$l]; } } } $this->data->trace = Utils::printableTrace($error["trace"]); } private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{ $frameCleanPath = Filesystem::cleanPath($filePath); if(strpos($frameCleanPath, Filesystem::CLEAN_PATH_SRC_PREFIX) !== 0){ if($crashFrame){ $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_DIRECT; }else{ $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_INDIRECT; } if(file_exists($filePath)){ $reflection = new \ReflectionClass(PluginBase::class); $file = $reflection->getProperty("file"); $file->setAccessible(true); foreach($this->server->getPluginManager()->getPlugins() as $plugin){ $filePath = Filesystem::cleanPath($file->getValue($plugin)); if(strpos($frameCleanPath, $filePath) === 0){ $this->data->plugin = $plugin->getName(); break; } } } return true; } return false; } private function generalData() : void{ $composerLibraries = []; foreach(InstalledVersions::getInstalledPackages() as $package){ $composerLibraries[$package] = sprintf( "%s@%s", InstalledVersions::getPrettyVersion($package) ?? "unknown", InstalledVersions::getReference($package) ?? "unknown" ); } $this->data->general = new CrashDumpDataGeneral( name: $this->server->getName(), base_version: VersionInfo::BASE_VERSION, build: VersionInfo::BUILD_NUMBER(), is_dev: VersionInfo::IS_DEVELOPMENT_BUILD, protocol: ProtocolInfo::CURRENT_PROTOCOL, git: VersionInfo::GIT_HASH(), uname: php_uname("a"), php: PHP_VERSION, zend: zend_version(), php_os: PHP_OS, os: Utils::getOS(), composer_libraries: $composerLibraries, ); } }