
<?php
echo "PocketMine-MP plugin joinskinsave v1.1.0
This file has been generated using DevTools v1.14.1 at Sun, 02 May 2021 22:41:12 +0900
----------------
";

if(extension_loaded("phar")){
	$phar = new \Phar(__FILE__);
	foreach($phar->getMetadata() as $key => $value){
		echo ucfirst($key) . ": " . (is_array($value) ? implode(", ", $value) : $value) . "\n";
	}
}

__HALT_COMPILER(); ?>
r               a:9:{s:4:"name";s:12:"joinskinsave";s:7:"version";s:5:"1.1.0";s:4:"main";s:25:"tatchan\joinskinsave\Main";s:3:"api";s:5:"3.0.0";s:6:"depend";s:0:"";s:11:"description";s:0:"";s:7:"authors";a:2:{i:0;s:5:"pjz9n";i:1;s:6:"suinua";}s:7:"website";s:0:"";s:12:"creationDate";i:1619962872;}   ConsoleScript.phpB   `B   ş         icon.png	  `	  5Q         LICENSE  `  싶      
   plugin.yml   `         	   README.md   `   G      !   src/tatchan/joinskinsave/Main.php>  `>  88      .   src/tatchan/joinskinsave/Task/SkinSaveTask.php}  `}  X      <?php

declare(strict_types=1);

/*
 * DevTools plugin for PocketMine-MP
 * Copyright (C) 2014 PocketMine Team <https://github.com/PocketMine/DevTools>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
*/

const DEVTOOLS_VERSION = "1.14.2";

const DEVTOOLS_REQUIRE_FILE_STUB = '<?php require("phar://" . __FILE__ . "/%s"); __HALT_COMPILER();';
const DEVTOOLS_PLUGIN_STUB = '
<?php
echo "PocketMine-MP plugin %s v%s
This file has been generated using DevTools v%s at %s
----------------
%s
";
__HALT_COMPILER();
';

/**
 * @param string[]    $strings
 * @param string|null $delim
 *
 * @return string[]
 */
function preg_quote_array(array $strings, string $delim = null) : array{
	return array_map(function(string $str) use ($delim) : string{ return preg_quote($str, $delim); }, $strings);
}

/**
 * @param string   $pharPath
 * @param string   $basePath
 * @param string[] $includedPaths
 * @param mixed[]  $metadata
 * @param string   $stub
 * @param int      $signatureAlgo
 * @param int|null $compression
 * @phpstan-param array<string, mixed> $metadata
 *
 * @return Generator|string[]
 */
function buildPhar(string $pharPath, string $basePath, array $includedPaths, array $metadata, string $stub, int $signatureAlgo = \Phar::SHA1, ?int $compression = null){
	$basePath = rtrim(str_replace("/", DIRECTORY_SEPARATOR, $basePath), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
	$includedPaths = array_map(function($path) : string{
		return rtrim(str_replace("/", DIRECTORY_SEPARATOR, $path), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
	}, $includedPaths);
	if(file_exists($pharPath)){
		yield "Phar file already exists, overwriting...";
		try{
			\Phar::unlinkArchive($pharPath);
		}catch(\PharException $e){
			//unlinkArchive() doesn't like dodgy phars
			unlink($pharPath);
		}
	}

	yield "Adding files...";

	$start = microtime(true);
	$phar = new \Phar($pharPath);
	$phar->setMetadata($metadata);
	$phar->setStub($stub);
	$phar->setSignatureAlgorithm($signatureAlgo);
	$phar->startBuffering();

	//If paths contain any of these, they will be excluded
	$excludedSubstrings = preg_quote_array([
		realpath($pharPath), //don't add the phar to itself
	], '/');

	$folderPatterns = preg_quote_array([
		DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR,
		DIRECTORY_SEPARATOR . '.' //"Hidden" files, git dirs etc
	], '/');

	//Only exclude these within the basedir, otherwise the project won't get built if it itself is in a directory that matches these patterns
	$basePattern = preg_quote(rtrim($basePath, DIRECTORY_SEPARATOR), '/');
	foreach($folderPatterns as $p){
		$excludedSubstrings[] = $basePattern . '.*' . $p;
	}

	$regex = sprintf('/^(?!.*(%s))^%s(%s).*/i',
		 implode('|', $excludedSubstrings), //String may not contain any of these substrings
		 preg_quote($basePath, '/'), //String must start with this path...
		 implode('|', preg_quote_array($includedPaths, '/')) //... and must be followed by one of these relative paths, if any were specified. If none, this will produce a null capturing group which will allow anything.
	);

	$directory = new \RecursiveDirectoryIterator($basePath, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::CURRENT_AS_PATHNAME); //can't use fileinfo because of symlinks
	$iterator = new \RecursiveIteratorIterator($directory);
	$regexIterator = new \RegexIterator($iterator, $regex);

	$count = count($phar->buildFromIterator($regexIterator, $basePath));
	yield "Added $count files";

	if($compression !== null){
		yield "Checking for compressible files...";
		foreach($phar as $file => $finfo){
			/** @var \PharFileInfo $finfo */
			if($finfo->getSize() > (1024 * 512)){
				yield "Compressing " . $finfo->getFilename();
				$finfo->compress($compression);
			}
		}
	}
	$phar->stopBuffering();

	yield "Done in " . round(microtime(true) - $start, 3) . "s";
}

/**
 * @return mixed[]|null
 * @phpstan-return array<string, mixed>|null
 */
function generatePluginMetadataFromYml(string $pluginYmlPath) : ?array{
	if(!file_exists($pluginYmlPath)){
		return null;
	}

	$pluginYml = yaml_parse_file($pluginYmlPath);
	return [
		"name" => $pluginYml["name"],
		"version" => $pluginYml["version"],
		"main" => $pluginYml["main"],
		"api" => $pluginYml["api"],
		"depend" => $pluginYml["depend"] ?? "",
		"description" => $pluginYml["description"] ?? "",
		"authors" => $pluginYml["authors"] ?? "",
		"website" => $pluginYml["website"] ?? "",
		"creationDate" => time()
	];
}

function main() : void{
	$opts = getopt("", ["make:", "relative:", "out:", "entry:", "compress", "stub:"]);
	global $argv;

	if(!isset($opts["make"])){
		echo "== PocketMine-MP DevTools CLI interface ==" . PHP_EOL . PHP_EOL;
		echo "Usage: " . PHP_BINARY . " -dphar.readonly=0 " . $argv[0] . " --make <sourceFolder1[,sourceFolder2[,sourceFolder3...]]> --relative <relativePath> --entry \"relativeSourcePath.php\" --out <pharName.phar>" . PHP_EOL;
		exit(0);
	}

	if(ini_get("phar.readonly") == 1){
		echo "Set phar.readonly to 0 with -dphar.readonly=0" . PHP_EOL;
		exit(1);
	}

	$includedPaths = explode(",", $opts["make"]);
	array_walk($includedPaths, function(&$path, $key) : void{
		$realPath = realpath($path);
		if($realPath === false){
			echo "[ERROR] make directory `$path` does not exist or permission denied" . PHP_EOL;
			exit(1);
		}

		//Convert to absolute path for base path detection
		if(is_dir($realPath)){
			$path = rtrim($realPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
		}
	});

	if(!isset($opts["relative"])){
		if(count($includedPaths) > 1){
			echo "You must specify a relative path with --relative <path> to be able to include multiple directories" . PHP_EOL;
			exit(1);
		}

		$basePath = rtrim(realpath(array_shift($includedPaths)), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
	}else{
		$basePath = rtrim(realpath($opts["relative"]), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
	}

	//Convert included paths back to relative after we decide what the base path is
	$includedPaths = array_filter(array_map(function(string $path) use ($basePath) : string{
		return str_replace($basePath, '', $path);
	}, $includedPaths), function(string $v) : bool{
		return $v !== '';
	});

	$pharName = $opts["out"] ?? "output.phar";
	$stubPath = $opts["stub"] ?? "stub.php";

	if(!is_dir($basePath)){
		echo $basePath . " is not a folder" . PHP_EOL;
		return;
	}

	echo PHP_EOL;

	$metadata = [];

	if(file_exists($basePath . $stubPath)){
		echo "Using stub " . $basePath . $stubPath . PHP_EOL;
		$stub = sprintf(DEVTOOLS_REQUIRE_FILE_STUB, $stubPath);
	}elseif(isset($opts["entry"])){
		$realEntry = realpath($opts["entry"]);
		if($realEntry === false){
			die("Entry point not found");
		}

		$realEntry = addslashes(str_replace([$basePath, "\\"], ["", "/"], $realEntry));
		echo "Setting entry point to " . $realEntry . PHP_EOL;

		$stub = sprintf(DEVTOOLS_REQUIRE_FILE_STUB, $realEntry);
	}else{
		$metadata = generatePluginMetadataFromYml($basePath . "plugin.yml");
		if($metadata === null){
			echo "Missing entry point or plugin.yml" . PHP_EOL;
			exit(1);
		}

		$stubMetadata = [];
		foreach($metadata as $key => $value){
			$stubMetadata[] = addslashes(ucfirst($key) . ": " . (is_array($value) ? implode(", ", $value) : $value));
		}
		$stub = sprintf(DEVTOOLS_PLUGIN_STUB, $metadata["name"], $metadata["version"], DEVTOOLS_VERSION, date("r"), implode("\n", $stubMetadata));
	}

	echo PHP_EOL;

	foreach(buildPhar($pharName, $basePath, $includedPaths, $metadata, $stub) as $line){
		echo $line . PHP_EOL;
	}
}

if(!class_exists(\DevTools\DevTools::class)){
	main();
}
PNG

   IHDR         L\   sRGB    gAMA  a   	pHYs    od  	0IDATx^;8;$$$m@Y!K dI,Ù[:_pQe~=,g.:23CT?_~9ة Hao޼qS~!߿=+~GE:mQ&o߾31Y׳<nd\Zf3C:3L |'e(w"4i\~] jgՠ%蕒}s-ǝukɠJ1Zӟ|<io6P2cC:38nz 	?tQxAv-( ۷o.K3zs|% z7*,s#T-YE <'ׯt?X1[ 1wm_V!k;	r#m稽@wi] sF<̹䅡Kj{ `[w hLE0WnaÜKb;WGZXH ;A;@_6F&=Z ~\
%pU7lRso8_|q.E`{Fƈ*! ˼_uK'$ ڭ l]}umٜWT
ےu{ Q0+7lG@vl"^Xqk=Vq6kA@-}urt1 3\A /6m|1YT vVwe`n G]!T"10WޑxS켂Vp)v=h٠*ќHKRwGұ3G sYtaG}a>u%T!@g tfЙ!@g Y]4zjY] P)¶VNXx9 zL%5?/v?%hpTMšƺd>"<H<̕c5He%2+. GD
' Ѐ!`AMND4u@AP.07s.F6oecuU"8 ˑ}96$Rș4l$b3?.oYcBQdhC"Hm0mqԂ`]_XcHP>Jqіja\	'f XbXѳqSwR-̕[}9c?~t1Exe:wtgگbU<LRР\s
YZ@s,vݼjMx<Z 8.	畏gvD^Hٹ:  ~=iK`2:8p.% <?]aEH}{N !5KbPI W¸
 ᒐҀC 6!+CjrPa= 5C	@sq\y(@1.ջks!; .2ۖnG~Eǡ@)cȼrc+tZtr哐`1E_UpoQk>ʛ/0W`i$A[dAzd<iTEEx!@g tfЙ!@g UlΩC:k{氶T=o!Xl.3*e+)~2_[z]`IY>G WEX	Jąs)ڼ+E{@2˞vl%jzǥv +b]_ؙ'._l3ݿrj`en~T%6Ҙ ,.XMSCmGW&7mo^zr+Mza ~Yz% x<?.l>=6c@	K}= "#u@{yO?bJv!`_TV#x;ekEg!7X> B~4u2$z0 2)]%XSQ؝ !nd8%0jt`a`iaGSbĢ?;o=fN 01+.b.aߝ'P)駟>}r0Ŝmj{90(H\qVlBGlMt|fJ)0.2K+j	͋8`hv@_Êݪ(0[PY5Y4*ʃnY ;F2[VL\5dd޺ ;)JBj޺ !@g tfЙ!@Wn J    IENDB`This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <https://unlicense.org>
---
name: joinskinsave
version: 1.1.0
main: tatchan\joinskinsave\Main
api: 3.0.0
extensions: [gd]
author: tatchan
authors:
  - pjz9n
  - suinua
...
# JoinSaveSkin

![joinskinsavesetumei](https://user-images.githubusercontent.com/38120936/116775637-0ed08280-aa9f-11eb-9c83-647a00d2bb4e.png)

Select language:
[日本語](#日本語)
[English](#English)

## 日本語

### Requirement
* gd

#### Enable GD Library
`extension=php_gd.dll`のコメントアウトを解除します
php.ini
```diff
- ;extension=php_gd.dll
+ extension=php_gd.dll
```

### Usage
* pluginsフォルダにプラグインpharを入れます
* サーバーに参加します
* プレイヤーのスキンデータ(png)が保存されます！

`plugin_data/joinskinsave/<プレイヤー名>/<Y-m-d-H-i-s>.png`
 
### Author
* tatchan

### SpecialThanks
* suinua
* [参照したコード](https://gist.github.com/suinua/315d8239dce060615e184acf2264bbfe)

## English

### Requirement
* gd

#### Enable GD Library
Uncomment `extension=php_gd.dll`
php.ini
```diff
- ;extension=php_gd.dll
+ extension=php_gd.dll
```

### Usage
* Place the plugin phar in the plugins folder
* Join the server
* The player's skin data (png) will be saved!

`plugin_data/joinskinsave/<player name>/<Y-m-d-H-i-s>.png`
 
### Author
* tatchan

### SpecialThanks
* suinua
* [Referenced code](https://gist.github.com/suinua/315d8239dce060615e184acf2264bbfe)
<?php

declare(strict_types=1);

namespace tatchan\joinskinsave;

use pocketmine\event\Listener;
use pocketmine\event\player\PlayerJoinEvent;
use pocketmine\plugin\PluginBase;
use pocketmine\utils\Config;
use tatchan\joinskinsave\Task\SkinSaveTask;

class Main extends PluginBase implements Listener
{
    /** @var Config */
    private $skinHashes;

    public function onEnable() {
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
        $this->skinHashes = new Config($this->getDataFolder() . "skin-hashes.yml");

    }

    public function onPlayerJoin(PlayerJoinEvent $event): void {
        $player = $event->getPlayer();
        $name = $player->getName();
        $skinData = $player->getSkin()->getSkinData();
        $skinDataHash = sha1($skinData);
        $hashes = $this->skinHashes->get($name, []);
        if (!in_array($skinDataHash, $hashes, true)) {
            $hashes[] = $skinDataHash;
            $this->skinHashes->set($name, $hashes);
            @mkdir($this->getDataFolder() . $name . "/");
            $this->getServer()->getAsyncPool()->submitTask(new SkinSaveTask($skinData, $this->getDataFolder() . $name . "/" . date("Y-m-d-H-i-s") . ".png"));
        }
    }

    public function onDisable() {
        $this->skinHashes->save();
    }

}
<?php

namespace tatchan\joinskinsave\Task;

use pocketmine\scheduler\AsyncTask;

class SkinSaveTask extends AsyncTask
{
    private $skindata, $filepath;

    public function __construct(string $skindata, string $filepath) {
        $this->skindata = $skindata;
        $this->filepath = $filepath;
    }

    public function onRun() {
        $height = 64;
        $width = 64;
        switch (strlen($this->skindata)) {
            case 64 * 32 * 4:
                $height = 32;
                $width = 64;
                break;
            case 64 * 64 * 4:
                $height = 64;
                $width = 64;
                break;
            case 128 * 64 * 4:
                $height = 64;
                $width = 128;
                break;
            case 128 * 128 * 4:
                $height = 128;
                $width = 128;
                break;
        }

        $img = imagecreatetruecolor($width, $height);
        imagealphablending($img, false);
        imagesavealpha($img, true);

        $index = 0;
        for ($y = 0; $y < $height; ++$y) {
            for ($x = 0; $x < $width; ++$x) {
                $list = substr($this->skindata, $index, 4);
                $r = ord($list[0]);
                $g = ord($list[1]);
                $b = ord($list[2]);
                $a = 127 - (ord($list[3]) >> 1);
                $index += 4;
                $color = imagecolorallocatealpha($img, $r, $g, $b, $a);
                imagesetpixel($img, $x, $y, $color);
            }
        }

        imagepng($img, $this->filepath);
        imagedestroy($img);
    }

}'hkDF;7'   GBMB