getName(), -strlen('{closure}')) !== '{closure}'){ //closure wraps a named function, can be done with reflection or fromCallable() //isClosure() is useless here because it just tells us if $func is reflecting a Closure object $scope = $func->getClosureScopeClass(); if($scope !== null){ //class method return $scope->getName() . ($func->getClosureThis() !== null ? "->" : "::") . $func->getName(); //name doesn't include class in this case } //non-class function return $func->getName(); } return "closure@" . self::cleanPath($func->getFileName()) . "#L" . $func->getStartLine(); } /** * Returns a readable identifier for the class of the given object. Sanitizes class names for anonymous classes. * * @throws \ReflectionException */ public static function getNiceClassName(object $obj) : string{ $reflect = new \ReflectionClass($obj); if($reflect->isAnonymous()){ return "anonymous@" . self::cleanPath($reflect->getFileName()) . "#L" . $reflect->getStartLine(); } return $reflect->getName(); } /** * Gets this machine / server instance unique ID * Returns a hash, the first 32 characters (or 16 if raw) * will be an identifier that won't change frequently. * The rest of the hash will change depending on other factors. * * @param string $extra optional, additional data to identify the machine */ public static function getMachineUniqueId(string $extra = "") : UUID{ if(self::$serverUniqueId !== null and $extra === ""){ return self::$serverUniqueId; } $machine = php_uname("a"); $machine .= file_exists("/proc/cpuinfo") ? implode(preg_grep("/(model name|Processor|Serial)/", file("/proc/cpuinfo"))) : ""; $machine .= sys_get_temp_dir(); $machine .= $extra; $os = Utils::getOS(); if($os === "win"){ @exec("ipconfig /ALL", $mac); $mac = implode("\n", $mac); if(preg_match_all("#Physical Address[. ]{1,}: ([0-9A-F\\-]{17})#", $mac, $matches)){ foreach($matches[1] as $i => $v){ if($v == "00-00-00-00-00-00"){ unset($matches[1][$i]); } } $machine .= implode(" ", $matches[1]); //Mac Addresses } }elseif($os === "linux"){ if(file_exists("/etc/machine-id")){ $machine .= file_get_contents("/etc/machine-id"); }else{ @exec("ifconfig 2>/dev/null", $mac); $mac = implode("\n", $mac); if(preg_match_all("#HWaddr[ \t]{1,}([0-9a-f:]{17})#", $mac, $matches)){ foreach($matches[1] as $i => $v){ if($v == "00:00:00:00:00:00"){ unset($matches[1][$i]); } } $machine .= implode(" ", $matches[1]); //Mac Addresses } } }elseif($os === "android"){ $machine .= @file_get_contents("/system/build.prop"); }elseif($os === "mac"){ $machine .= `system_profiler SPHardwareDataType | grep UUID`; } $data = $machine . PHP_MAXPATHLEN; $data .= PHP_INT_MAX; $data .= PHP_INT_SIZE; $data .= get_current_user(); foreach(get_loaded_extensions() as $ext){ $data .= $ext . ":" . phpversion($ext); } $uuid = UUID::fromData($machine, $data); if($extra === ""){ self::$serverUniqueId = $uuid; } return $uuid; } /** * @deprecated * @see Internet::getIP() * * @param bool $force default false, force IP check even when cached * * @return string|bool */ public static function getIP(bool $force = false){ return Internet::getIP($force); } /** * Returns the current Operating System * Windows => win * MacOS => mac * iOS => ios * Android => android * Linux => Linux * BSD => bsd * Other => other */ public static function getOS(bool $recalculate = false) : string{ if(self::$os === null or $recalculate){ $uname = php_uname("s"); if(stripos($uname, "Darwin") !== false){ if(strpos(php_uname("m"), "iP") === 0){ self::$os = "ios"; }else{ self::$os = "mac"; } }elseif(stripos($uname, "Win") !== false or $uname === "Msys"){ self::$os = "win"; }elseif(stripos($uname, "Linux") !== false){ if(@file_exists("/system/build.prop")){ self::$os = "android"; }else{ self::$os = "linux"; } }elseif(stripos($uname, "BSD") !== false or $uname === "DragonFly"){ self::$os = "bsd"; }else{ self::$os = "other"; } } return self::$os; } /** * @return int[] */ public static function getRealMemoryUsage() : array{ $stack = 0; $heap = 0; if(Utils::getOS() === "linux" or Utils::getOS() === "android"){ $mappings = file("/proc/self/maps"); foreach($mappings as $line){ if(preg_match("#([a-z0-9]+)\\-([a-z0-9]+) [rwxp\\-]{4} [a-z0-9]+ [^\\[]*\\[([a-zA-z0-9]+)\\]#", trim($line), $matches) > 0){ if(strpos($matches[3], "heap") === 0){ $heap += hexdec($matches[2]) - hexdec($matches[1]); }elseif(strpos($matches[3], "stack") === 0){ $stack += hexdec($matches[2]) - hexdec($matches[1]); } } } } return [$heap, $stack]; } /** * @return int[]|int */ public static function getMemoryUsage(bool $advanced = false){ $reserved = memory_get_usage(); $VmSize = null; $VmRSS = null; if(Utils::getOS() === "linux" or Utils::getOS() === "android"){ $status = file_get_contents("/proc/self/status"); if(preg_match("/VmRSS:[ \t]+([0-9]+) kB/", $status, $matches) > 0){ $VmRSS = $matches[1] * 1024; } if(preg_match("/VmSize:[ \t]+([0-9]+) kB/", $status, $matches) > 0){ $VmSize = $matches[1] * 1024; } } //TODO: more OS if($VmRSS === null){ $VmRSS = memory_get_usage(); } if(!$advanced){ return $VmRSS; } if($VmSize === null){ $VmSize = memory_get_usage(true); } return [$reserved, $VmRSS, $VmSize]; } public static function getThreadCount() : int{ if(Utils::getOS() === "linux" or Utils::getOS() === "android"){ if(preg_match("/Threads:[ \t]+([0-9]+)/", file_get_contents("/proc/self/status"), $matches) > 0){ return (int) $matches[1]; } } //TODO: more OS return count(ThreadManager::getInstance()->getAll()) + 3; //RakLib + MainLogger + Main Thread } public static function getCoreCount(bool $recalculate = false) : int{ static $processors = 0; if($processors > 0 and !$recalculate){ return $processors; }else{ $processors = 0; } switch(Utils::getOS()){ case "linux": case "android": if(file_exists("/proc/cpuinfo")){ foreach(file("/proc/cpuinfo") as $l){ if(preg_match('/^processor[ \t]*:[ \t]*[0-9]+$/m', $l) > 0){ ++$processors; } } }elseif(is_readable("/sys/devices/system/cpu/present")){ if(preg_match("/^([0-9]+)\\-([0-9]+)$/", trim(file_get_contents("/sys/devices/system/cpu/present")), $matches) > 0){ $processors = (int) ($matches[2] - $matches[1]); } } break; case "bsd": case "mac": $processors = (int) `sysctl -n hw.ncpu`; break; case "win": $processors = (int) getenv("NUMBER_OF_PROCESSORS"); break; } return $processors; } /** * Returns a prettified hexdump */ public static function hexdump(string $bin) : string{ $output = ""; $bin = str_split($bin, 16); foreach($bin as $counter => $line){ $hex = chunk_split(chunk_split(str_pad(bin2hex($line), 32, " ", STR_PAD_RIGHT), 2, " "), 24, " "); $ascii = preg_replace('#([^\x20-\x7E])#', ".", $line); $output .= str_pad(dechex($counter << 4), 4, "0", STR_PAD_LEFT) . " " . $hex . " " . $ascii . PHP_EOL; } return $output; } /** * Returns a string that can be printed, replaces non-printable characters * * @param mixed $str */ public static function printable($str) : string{ if(!is_string($str)){ return gettype($str); } return preg_replace('#([^\x20-\x7E])#', '.', $str); } /* public static function angle3D($pos1, $pos2){ $X = $pos1["x"] - $pos2["x"]; $Z = $pos1["z"] - $pos2["z"]; $dXZ = sqrt(pow($X, 2) + pow($Z, 2)); $Y = $pos1["y"] - $pos2["y"]; $hAngle = rad2deg(atan2($Z, $X) - M_PI_2); $vAngle = rad2deg(-atan2($Y, $dXZ)); return array("yaw" => $hAngle, "pitch" => $vAngle); }*/ /** * @deprecated * @see Internet::getURL() * * @param int $timeout default 10 * @param string[] $extraHeaders * @param string $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation. * @param string[] $headers reference parameter * @param int $httpCode reference parameter * @phpstan-param list $extraHeaders * @phpstan-param array $headers * * @return string|false */ public static function getURL(string $page, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){ return Internet::getURL($page, $timeout, $extraHeaders, $err, $headers, $httpCode); } /** * @deprecated * @see Internet::postURL() * * @param string[]|string $args * @param string[] $extraHeaders * @param string $err reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occured during the operation. * @param string[] $headers reference parameter * @param int $httpCode reference parameter * @phpstan-param string|array $args * @phpstan-param list $extraHeaders * @phpstan-param array $headers * * @return string|false */ public static function postURL(string $page, $args, int $timeout = 10, array $extraHeaders = [], &$err = null, &$headers = null, &$httpCode = null){ return Internet::postURL($page, $args, $timeout, $extraHeaders, $err, $headers, $httpCode); } /** * @deprecated * @see Internet::simpleCurl() * * @param float|int $timeout The maximum connect timeout and timeout in seconds, correct to ms. * @param string[] $extraHeaders extra headers to send as a plain string array * @param array $extraOpts extra CURLOPT_* to set as an [opt => value] map * @param callable|null $onSuccess function to be called if there is no error. Accepts a resource argument as the cURL handle. * @phpstan-param array $extraOpts * @phpstan-param list $extraHeaders * @phpstan-param (callable(resource) : void)|null $onSuccess * * @return array a plain array of three [result body : string, headers : string[][], HTTP response code : int]. Headers are grouped by requests with strtolower(header name) as keys and header value as values * @phpstan-return array{string, list>, int} * * @throws \RuntimeException if a cURL error occurs */ public static function simpleCurl(string $page, $timeout = 10, array $extraHeaders = [], array $extraOpts = [], callable $onSuccess = null){ return Internet::simpleCurl($page, $timeout, $extraHeaders, $extraOpts, $onSuccess); } public static function javaStringHash(string $string) : int{ $hash = 0; for($i = 0, $len = strlen($string); $i < $len; $i++){ $ord = ord($string[$i]); if(($ord & 0x80) !== 0){ $ord -= 0x100; } $hash = 31 * $hash + $ord; while($hash > 0x7FFFFFFF){ $hash -= 0x100000000; } while($hash < -0x80000000){ $hash += 0x100000000; } $hash &= 0xFFFFFFFF; } return $hash; } /** * @param string $command Command to execute * @param string|null $stdout Reference parameter to write stdout to * @param string|null $stderr Reference parameter to write stderr to * * @return int process exit code */ public static function execute(string $command, string &$stdout = null, string &$stderr = null) : int{ $process = proc_open($command, [ ["pipe", "r"], ["pipe", "w"], ["pipe", "w"] ], $pipes); if($process === false){ $stderr = "Failed to open process"; $stdout = ""; return -1; } $stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]); foreach($pipes as $p){ fclose($p); } return proc_close($process); } /** * @return mixed[] * @phpstan-return array */ public static function decodeJWT(string $token) : array{ list($headB64, $payloadB64, $sigB64) = explode(".", $token); return json_decode(base64_decode(strtr($payloadB64, '-_', '+/'), true), true); } /** * @param int $pid */ public static function kill($pid) : void{ if(MainLogger::isRegisteredStatic()){ MainLogger::getLogger()->syncFlushBuffer(); } switch(Utils::getOS()){ case "win": exec("taskkill.exe /F /PID $pid > NUL"); break; case "mac": case "linux": default: if(function_exists("posix_kill")){ posix_kill($pid, 9); //SIGKILL }else{ exec("kill -9 $pid > /dev/null 2>&1"); } } } /** * @param object $value * * @return int */ public static function getReferenceCount($value, bool $includeCurrent = true){ ob_start(); debug_zval_dump($value); $ret = explode("\n", ob_get_contents()); ob_end_clean(); if(count($ret) >= 1 and preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){ return ((int) $m[1]) - ($includeCurrent ? 3 : 4); //$value + zval call + extra call } return -1; } /** * @param mixed[][] $trace * @phpstan-param list> $trace * * @return string[] */ public static function printableTrace(array $trace, int $maxStringLength = 80) : array{ $messages = []; for($i = 0; isset($trace[$i]); ++$i){ $params = ""; if(isset($trace[$i]["args"]) or isset($trace[$i]["params"])){ if(isset($trace[$i]["args"])){ $args = $trace[$i]["args"]; }else{ $args = $trace[$i]["params"]; } $params = implode(", ", array_map(function($value) use($maxStringLength) : string{ if(is_object($value)){ return "object " . self::getNiceClassName($value); } if(is_array($value)){ return "array[" . count($value) . "]"; } if(is_string($value)){ return "string[" . strlen($value) . "] " . substr(Utils::printable($value), 0, $maxStringLength); } return gettype($value) . " " . Utils::printable((string) $value); }, $args)); } $messages[] = "#$i " . (isset($trace[$i]["file"]) ? self::cleanPath($trace[$i]["file"]) : "") . "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " . (isset($trace[$i]["class"]) ? $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" or $trace[$i]["type"] === "->") ? "->" : "::") : "") . $trace[$i]["function"] . "(" . Utils::printable($params) . ")"; } return $messages; } /** * @return mixed[][] * @phpstan-return list> */ public static function currentTrace(int $skipFrames = 0) : array{ ++$skipFrames; //omit this frame from trace, in addition to other skipped frames if(function_exists("xdebug_get_function_stack")){ $trace = array_reverse(xdebug_get_function_stack()); }else{ $e = new \Exception(); $trace = $e->getTrace(); } for($i = 0; $i < $skipFrames; ++$i){ unset($trace[$i]); } return array_values($trace); } /** * @return string[] */ public static function printableCurrentTrace(int $skipFrames = 0) : array{ return self::printableTrace(self::currentTrace(++$skipFrames)); } /** * @param string $path * * @return string */ public static function cleanPath($path){ $result = str_replace(["\\", ".php", "phar://"], ["/", "", ""], $path); //remove relative paths //TODO: make these paths dynamic so they can be unit-tested against static $cleanPaths = [ \pocketmine\PLUGIN_PATH => "plugins", //this has to come BEFORE \pocketmine\PATH because it's inside that by default on src installations \pocketmine\PATH => "" ]; foreach($cleanPaths as $cleanPath => $replacement){ $cleanPath = rtrim(str_replace(["\\", "phar://"], ["/", ""], $cleanPath), "/"); if(strpos($result, $cleanPath) === 0){ $result = ltrim(str_replace($cleanPath, $replacement, $result), "/"); } } return $result; } /** * Extracts one-line tags from the doc-comment * * @return string[] an array of tagName => tag value. If the tag has no value, an empty string is used as the value. */ public static function parseDocComment(string $docComment) : array{ $rawDocComment = substr($docComment, 3, -2); //remove the opening and closing markers if($rawDocComment === false){ //usually empty doc comment, but this is safer and statically analysable return []; } preg_match_all('/(*ANYCRLF)^[\t ]*(?:\* )?@([a-zA-Z]+)(?:[\t ]+(.+?))?[\t ]*$/m', $rawDocComment, $matches); return array_combine($matches[1], $matches[2]); } /** * @throws \ErrorException */ public static function errorExceptionHandler(int $severity, string $message, string $file, int $line) : bool{ if((error_reporting() & $severity) !== 0){ throw new \ErrorException($message, 0, $severity, $file, $line); } return true; //stfu operator } public static function testValidInstance(string $className, string $baseName) : void{ try{ $base = new \ReflectionClass($baseName); }catch(\ReflectionException $e){ throw new \InvalidArgumentException("Base class $baseName does not exist"); } try{ $class = new \ReflectionClass($className); }catch(\ReflectionException $e){ throw new \InvalidArgumentException("Class $className does not exist"); } if(!$class->isSubclassOf($baseName)){ throw new \InvalidArgumentException("Class $className does not " . ($base->isInterface() ? "implement" : "extend") . " " . $baseName); } if(!$class->isInstantiable()){ throw new \InvalidArgumentException("Class $className cannot be constructed"); } } /** * Verifies that the given callable is compatible with the desired signature. Throws a TypeError if they are * incompatible. * * @param callable $signature Dummy callable with the required parameters and return type * @param callable $subject Callable to check the signature of * * @throws \DaveRandom\CallbackValidator\InvalidCallbackException * @throws \TypeError */ public static function validateCallableSignature(callable $signature, callable $subject) : void{ if(!($sigType = CallbackType::createFromCallable($signature))->isSatisfiedBy($subject)){ throw new \TypeError("Declaration of callable `" . CallbackType::createFromCallable($subject) . "` must be compatible with `" . $sigType . "`"); } } }