Skip to content

Commit

Permalink
Just forgot to remove InvMenu
Browse files Browse the repository at this point in the history
LibSkin seems to not be an available virion so i need to include it in the code rn
  • Loading branch information
FoxWorn3365 committed Jul 23, 2023
1 parent 0abc506 commit e78292d
Show file tree
Hide file tree
Showing 54 changed files with 269 additions and 2,251 deletions.
5 changes: 1 addition & 4 deletions .poggit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,4 @@ projects:
libs:
- src: muqsit/InvMenu/InvMenu
version: ^4.6.1
branch: pm5
- src: Himbeer/LibSkin/LibSkin
version: ^2.0.0
branch: master
branch: pm5
34 changes: 34 additions & 0 deletions src/Himbeer/LibSkin/LibSkin.php
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");
}
}
}
106 changes: 106 additions & 0 deletions src/Himbeer/LibSkin/SkinConverter.php
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;
}
}
128 changes: 128 additions & 0 deletions src/Himbeer/LibSkin/SkinGatherer.php
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);
}
}
Loading

0 comments on commit e78292d

Please sign in to comment.