-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
LibSkin seems to not be an available virion so i need to include it in the code rn
- Loading branch information
1 parent
0abc506
commit e78292d
Showing
54 changed files
with
269 additions
and
2,251 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Himbeer\LibSkin; | ||
|
||
use Exception; | ||
|
||
final class LibSkin { | ||
// https://github.com/pmmp/PocketMine-MP/blob/a19143cae76ad55f1bdc2f39ad007b1fc170980b/src/pocketmine/entity/Skin.php#L33-L37 | ||
public const ACCEPTED_SKIN_SIZES = [ | ||
64 * 32 * 4, | ||
64 * 64 * 4, | ||
128 * 128 * 4 | ||
]; | ||
|
||
public const SKIN_WIDTH_MAP = [ | ||
64 * 32 * 4 => 64, | ||
64 * 64 * 4 => 64, | ||
128 * 128 * 4 => 128 | ||
]; | ||
|
||
public const SKIN_HEIGHT_MAP = [ | ||
64 * 32 * 4 => 32, | ||
64 * 64 * 4 => 64, | ||
128 * 128 * 4 => 128 | ||
]; | ||
|
||
public static function validateSize(int $size) { | ||
if (!in_array($size, self::ACCEPTED_SKIN_SIZES)) { | ||
throw new Exception("Invalid skin size"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Himbeer\LibSkin; | ||
|
||
use Exception; | ||
|
||
final class SkinConverter { | ||
/** | ||
* @param string $skinData Minecraft Skin Data | ||
* @param string $savePath Path where skin PNG is saved | ||
* | ||
* @throws Exception | ||
*/ | ||
public static function skinDataToImageSave(string $skinData, string $savePath) { | ||
$image = self::skinDataToImage($skinData); | ||
imagepng($image, $savePath); | ||
imagedestroy($image); | ||
} | ||
|
||
/** | ||
* @param string $skinData | ||
* | ||
* @return resource GD image resource | ||
* @throws Exception | ||
*/ | ||
public static function skinDataToImage(string $skinData) { | ||
$size = strlen($skinData); | ||
LibSkin::validateSize($size); | ||
$width = LibSkin::SKIN_WIDTH_MAP[$size]; | ||
$height = LibSkin::SKIN_HEIGHT_MAP[$size]; | ||
$skinPos = 0; | ||
$image = imagecreatetruecolor($width, $height); | ||
if ($image === false) { | ||
throw new Exception("Couldn't create image"); | ||
} | ||
// Make background transparent | ||
imagefill($image, 0, 0, imagecolorallocatealpha($image, 0, 0, 0, 127)); | ||
for ($y = 0; $y < $height; $y++) { | ||
for ($x = 0; $x < $width; $x++) { | ||
$r = ord($skinData[$skinPos]); | ||
$skinPos++; | ||
$g = ord($skinData[$skinPos]); | ||
$skinPos++; | ||
$b = ord($skinData[$skinPos]); | ||
$skinPos++; | ||
$a = 127 - intdiv(ord($skinData[$skinPos]), 2); | ||
$skinPos++; | ||
$col = imagecolorallocatealpha($image, $r, $g, $b, $a); | ||
imagesetpixel($image, $x, $y, $col); | ||
} | ||
} | ||
imagesavealpha($image, true); | ||
return $image; | ||
} | ||
|
||
/** | ||
* @param string $imagePath Path to skin PNG | ||
* | ||
* @return string Minecraft Skin Data | ||
* @throws Exception | ||
*/ | ||
public static function imageToSkinDataFromPngPath(string $imagePath) : string { | ||
$image = imagecreatefrompng($imagePath); | ||
if ($image === false) { | ||
throw new Exception("Couldn't load image"); | ||
} | ||
return self::imageToSkinData($image, true); | ||
} | ||
|
||
/** | ||
* @param resource $image GD image resource | ||
* @param bool $destroyImage Whether to call imagedestroy on the image resource after finishing | ||
* | ||
* @return string Minecraft Skin Data | ||
* @throws Exception | ||
*/ | ||
public static function imageToSkinData($image, bool $destroyImage) : string { | ||
if (get_class($image) !== "GdImage") { | ||
throw new Exception("1st parameter must be a GD image resource"); | ||
} | ||
$size = imagesx($image) * imagesy($image) * 4; | ||
LibSkin::validateSize($size); | ||
|
||
$width = LibSkin::SKIN_WIDTH_MAP[$size]; | ||
$height = LibSkin::SKIN_HEIGHT_MAP[$size]; | ||
|
||
imagepalettetotruecolor($image); | ||
|
||
$skinData = ""; | ||
for ($y = 0; $y < $height; $y++) { | ||
for ($x = 0; $x < $width; $x++) { | ||
// https://www.php.net/manual/en/function.imagecolorat.php | ||
$rgba = imagecolorat($image, $x, $y); | ||
$a = ($rgba >> 24) & 0xff; | ||
$r = ($rgba >> 16) & 0xff; | ||
$g = ($rgba >> 8) & 0xff; | ||
$b = $rgba & 0xff; | ||
$skinData .= chr($r) . chr($g) . chr($b) . chr(~(($a << 1) | ($a >> 6)) & 0xff); | ||
} | ||
} | ||
if ($destroyImage) imagedestroy($image); | ||
return $skinData; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Himbeer\LibSkin; | ||
|
||
use Exception; | ||
use pocketmine\scheduler\BulkCurlTask; | ||
use pocketmine\scheduler\BulkCurlTaskOperation; | ||
use pocketmine\Server; | ||
use pocketmine\utils\InternetException; | ||
use pocketmine\utils\InternetRequestResult; | ||
|
||
final class SkinGatherer { | ||
public const MCJE_STATE_SUCCESS = 0; | ||
public const MCJE_STATE_ERR_UNKNOWN = 1; | ||
public const MCJE_STATE_ERR_PLAYER_NOT_FOUND = 2; | ||
public const MCJE_STATE_ERR_TOO_MANY_REQUESTS = 3; | ||
|
||
/** | ||
* @param string $playerName | ||
* | ||
* @return string|null Minecraft Skin Data or null if the player doesn't exist or doesn't have saved skin data | ||
*/ | ||
public static function getSkinDataFromOfflinePlayer(string $playerName) : ?string { | ||
$namedTag = Server::getInstance()->getOfflinePlayerData($playerName); | ||
if ($namedTag === null) { | ||
return null; | ||
} | ||
$skinTag = $namedTag->getCompoundTag("Skin"); | ||
if ($skinTag === null) { | ||
return null; | ||
} | ||
$skinData = $skinTag->getByteArray("Data"); | ||
return $skinData; | ||
} | ||
|
||
/** | ||
* @param string $userName | ||
* @param callable $callback A function which gets called when the request is finished, with the first argument being the skin data (or null) and the second the success/error state | ||
* | ||
* @throws Exception | ||
*/ | ||
public static function getJavaEditionSkinData(string $userName, callable $callback) { | ||
self::getJavaEditionSkinUrl($userName, function($skinUrl, $state) use ($callback) { | ||
$callback($skinUrl === null ? null : SkinConverter::imageToSkinDataFromPngPath($skinUrl), $state); | ||
}); | ||
} | ||
|
||
/** | ||
* @param string $userName Java Edition player name | ||
* @param callable $callback A function which gets called when the request is finished, with the first argument being the URL (or null) and the second the success/error state | ||
*/ | ||
public static function getJavaEditionSkinUrl(string $userName, callable $callback) { | ||
self::asyncHttpGetRequest("https://api.mojang.com/users/profiles/minecraft/{$userName}", function(InternetRequestResult|null $response) use ($callback) { | ||
if ($response === null) { | ||
$callback(null, self::MCJE_STATE_ERR_UNKNOWN); | ||
return; | ||
} | ||
$body = $response->getBody(); | ||
if ($body === "") { | ||
if ($response->getCode() === 204) { // Status Code 204: No Content | ||
$callback(null, self::MCJE_STATE_ERR_PLAYER_NOT_FOUND); | ||
} else { | ||
$callback(null, self::MCJE_STATE_ERR_UNKNOWN); | ||
} | ||
return; | ||
} | ||
$data = json_decode($body, true); | ||
if ($data === null || !isset($data["id"])) { | ||
$callback(null, self::MCJE_STATE_ERR_UNKNOWN); | ||
return; | ||
} | ||
self::asyncHttpGetRequest("https://sessionserver.mojang.com/session/minecraft/profile/{$data["id"]}", function(InternetRequestResult|null $response) use ($callback) { | ||
if ($response === null) { | ||
$callback(null, self::MCJE_STATE_ERR_UNKNOWN); | ||
return; | ||
} | ||
$body = $response->getBody(); | ||
if ($body === "") { | ||
$callback(null, self::MCJE_STATE_ERR_UNKNOWN); | ||
return; | ||
} | ||
$data = json_decode($body, true); | ||
if ($data === null || !isset($data["properties"][0]["name"]) || $data["properties"][0]["name"] !== "textures") { | ||
if (isset($data["error"]) && $data["error"] === "TooManyRequestsException") { | ||
$callback(null, self::MCJE_STATE_ERR_TOO_MANY_REQUESTS); | ||
} else { | ||
$callback(null, self::MCJE_STATE_ERR_UNKNOWN); | ||
} | ||
return; | ||
} | ||
if (isset($data["properties"][0]["value"]) && ($b64dec = base64_decode($data["properties"][0]["value"]))) { | ||
$textureInfo = json_decode($b64dec, true); | ||
if ($textureInfo !== null && isset($textureInfo["textures"]["SKIN"]["url"])) { | ||
$skinUrl = $textureInfo["textures"]["SKIN"]["url"]; | ||
$callback($skinUrl, self::MCJE_STATE_SUCCESS); | ||
return; | ||
} | ||
} | ||
$callback(null, self::MCJE_STATE_ERR_UNKNOWN); | ||
}); | ||
}); | ||
} | ||
|
||
/** | ||
* @param string $url | ||
* @param callable $callback | ||
*/ | ||
private static function asyncHttpGetRequest(string $url, callable $callback) { | ||
/** | ||
* @param InternetRequestResult[] $results | ||
* | ||
* @return void | ||
*/ | ||
$bulkCurlTaskCallback = function(array $results) use ($callback) { | ||
if (isset($results[0]) && !$results[0] instanceof InternetException) { | ||
$callback($results[0]); | ||
} else { | ||
$callback(null); | ||
} | ||
}; | ||
$task = new BulkCurlTask([ | ||
new BulkCurlTaskOperation($url) | ||
], $bulkCurlTaskCallback); | ||
Server::getInstance()->getAsyncPool()->submitTask($task); | ||
} | ||
} |
Oops, something went wrong.