diff --git a/resources/resource_packs.yml b/resources/resource_packs.yml index f236117d564..7a23a7ee0bf 100644 --- a/resources/resource_packs.yml +++ b/resources/resource_packs.yml @@ -9,5 +9,12 @@ resource_stack: #Example # - natural.zip # - vanilla.zip + # - path: file.zip + # encryptionKey: "your encrypt Key" <- is optionnal option + # - path: 'https://exemple.com/natural.zip' + # name: "name in manifest.json" + # uuid: "uuid in manifest.json" + # version: "version in manifest.json" + # encryptionKey: "your encrypt Key" <- is optionnal option #If you want to force clients to use vanilla resources, you must place a vanilla resource pack in your resources folder and add it to the stack here. #To specify a resource encryption key, put the key in the .key file alongside the resource pack. Example: vanilla.zip.key diff --git a/src/network/mcpe/handler/ResourcePacksPacketHandler.php b/src/network/mcpe/handler/ResourcePacksPacketHandler.php index 08ce53efe2c..ca64b7c70e6 100644 --- a/src/network/mcpe/handler/ResourcePacksPacketHandler.php +++ b/src/network/mcpe/handler/ResourcePacksPacketHandler.php @@ -38,6 +38,7 @@ use pocketmine\network\mcpe\protocol\types\resourcepacks\ResourcePackType; use pocketmine\resourcepacks\ResourcePack; use pocketmine\resourcepacks\ResourcePackManager; +use symply\resourcepacks\URLResourcePack; use function array_map; use function ceil; use function count; @@ -67,20 +68,19 @@ public function __construct( public function setUp() : void{ $resourcePackEntries = array_map(function(ResourcePack $pack) : ResourcePackInfoEntry{ //TODO: more stuff - $encryptionKey = $this->resourcePackManager->getPackEncryptionKey($pack->getPackId()); return new ResourcePackInfoEntry( $pack->getPackId(), $pack->getPackVersion(), $pack->getPackSize(), - $encryptionKey ?? "", + $pack->getEncryptionKey(), "", - $pack->getPackId(), + $pack->getContentId(), false ); }, $this->resourcePackManager->getResourceStack()); //TODO: support forcing server packs - $this->session->sendDataPacket(ResourcePacksInfoPacket::create($resourcePackEntries, [], $this->resourcePackManager->resourcePacksRequired(), false, false, [])); + $this->session->sendDataPacket(ResourcePacksInfoPacket::create($resourcePackEntries, [], $this->resourcePackManager->resourcePacksRequired(), false, false, $this->resourcePackManager->getPackUrl())); $this->session->getLogger()->debug("Waiting for client to accept resource packs"); } @@ -109,6 +109,10 @@ public function handleResourcePackClientResponse(ResourcePackClientResponsePacke $this->disconnectWithError("Unknown pack $uuid requested, available packs: " . implode(", ", $this->resourcePackManager->getPackIdList())); return false; } + if ($pack instanceof URLResourcePack){ + $this->disconnectWithError("Invalid URL for pack chunk in $uuid. Unable to use the getPackChunk method."); + return false; + } $this->session->sendDataPacket(ResourcePackDataInfoPacket::create( $pack->getPackId(), @@ -154,8 +158,11 @@ public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $p $this->disconnectWithError("Invalid request for chunk $packet->chunkIndex of unknown pack $packet->packId, available packs: " . implode(", ", $this->resourcePackManager->getPackIdList())); return false; } - $packId = $pack->getPackId(); //use this because case may be different + if ($pack instanceof URLResourcePack){ + $this->disconnectWithError("Invalid URL for pack chunk in $packId. Unable to use the getPackChunk method."); + return false; + } if(isset($this->downloadedChunks[$packId][$packet->chunkIndex])){ $this->disconnectWithError("Duplicate request for chunk $packet->chunkIndex of pack $packet->packId"); diff --git a/src/resourcepacks/ResourcePack.php b/src/resourcepacks/ResourcePack.php index 04feeeb3d70..ce544fe0614 100644 --- a/src/resourcepacks/ResourcePack.php +++ b/src/resourcepacks/ResourcePack.php @@ -64,4 +64,19 @@ public function getSha256() : string; * @throws \InvalidArgumentException if the chunk does not exist */ public function getPackChunk(int $start, int $length) : string; + + /** + * Returns the EncryptionKey of ResourcesPack + */ + public function getEncryptionKey() : string; + + /** + * Returns the ContentId of ResourcesPack + */ + public function getContentId() : string; + + /** + * can set the EncryptionKey without pass by new system symplu + */ + public function setEncryptionKey(string $key) : void; } diff --git a/src/resourcepacks/ResourcePackManager.php b/src/resourcepacks/ResourcePackManager.php index 2df6750def6..61dc8f5d5f9 100644 --- a/src/resourcepacks/ResourcePackManager.php +++ b/src/resourcepacks/ResourcePackManager.php @@ -26,6 +26,8 @@ use pocketmine\utils\Config; use pocketmine\utils\Filesystem; use Symfony\Component\Filesystem\Path; +use symply\resourcepacks\URLResourcePack; +use symply\utils\Utils as UtilsSymply; use function array_keys; use function copy; use function count; @@ -56,7 +58,7 @@ class ResourcePackManager{ * @var string[] * @phpstan-var array */ - private array $encryptionKeys = []; + private array $packUrl = []; /** * @param string $path Path to resource-packs directory. @@ -88,17 +90,23 @@ public function __construct(string $path, \Logger $logger){ } foreach($resourceStack as $pos => $pack){ - if(!is_string($pack) && !is_int($pack) && !is_float($pack)){ + if(!(is_string($pack) || is_array($pack)) && !is_int($pack) && !is_float($pack)){ $logger->critical("Found invalid entry in resource pack list at offset $pos of type " . gettype($pack)); continue; } - $pack = (string) $pack; try{ - $newPack = $this->loadPackFromPath(Path::join($this->path, $pack)); - + if (is_string($pack)) { + $newPack = $this->loadPackFromPath(Path::join($this->path, (string) $pack)); + }else { + $newPack = $this->loadPackFromArray((array) $pack); + } $this->resourcePacks[] = $newPack; $index = strtolower($newPack->getPackId()); $this->uuidList[$index] = $newPack; + if ($newPack instanceof URLResourcePack){ + $this->addPackUrl($newPack); + return; + } $keyPath = Path::join($this->path, $pack . ".key"); if(file_exists($keyPath)){ @@ -111,7 +119,7 @@ public function __construct(string $path, \Logger $logger){ if(strlen($key) !== 32){ throw new ResourcePackException("Invalid encryption key length, must be exactly 32 bytes"); } - $this->encryptionKeys[$index] = $key; + $this->setPackEncryptionKey($index, $key); } }catch(ResourcePackException $e){ $logger->critical("Could not load resource pack \"$pack\": " . $e->getMessage()); @@ -140,6 +148,21 @@ private function loadPackFromPath(string $packPath) : ResourcePack{ throw new ResourcePackException("Format not recognized"); } + private function loadPackFromArray(array $info) : ResourcePack{ + $packPath = $info['path']; + if (UtilsSymply::isUrl($packPath)){ + return new URLResourcePack($info['name'], $info['uuid'], $info['version'], $packPath, UtilsSymply::getSizeOfResourcesPack($packPath), $info['encryptionKey'] ?? ""); + } + $packPath = Path::join($this->path, $packPath); + if(!file_exists($packPath)){ + throw new ResourcePackException("File or directory not found"); + } + if(is_dir($packPath)){ + throw new ResourcePackException("Directory resource packs are unsupported"); + } + return new ZippedResourcePack($packPath, $info['encryptionKey'] ?? ""); + } + /** * Returns the directory which resource packs are loaded from. */ @@ -207,29 +230,34 @@ public function getPackIdList() : array{ return array_keys($this->uuidList); } - /** - * Returns the key with which the pack was encrypted, or null if the pack has no key. - */ - public function getPackEncryptionKey(string $id) : ?string{ - return $this->encryptionKeys[strtolower($id)] ?? null; - } - /** * Sets the encryption key to use for decrypting the specified resource pack. The pack will **NOT** be decrypted by * PocketMine-MP; the key is simply passed to the client to allow it to decrypt the pack after downloading it. */ public function setPackEncryptionKey(string $id, ?string $key) : void{ $id = strtolower($id); + if (!isset($this->uuidList[$id])){ + throw new ResourcePackException("Resource pack with ID $id not found."); + } if($key === null){ - //allow deprovisioning keys for resource packs that have been removed - unset($this->encryptionKeys[$id]); + $this->uuidList[$id]->setEncryptionKey(""); }elseif(isset($this->uuidList[$id])){ if(strlen($key) !== 32){ throw new \InvalidArgumentException("Encryption key must be exactly 32 bytes long"); } - $this->encryptionKeys[$id] = $key; + $this->uuidList[$id]->setEncryptionKey($key); }else{ throw new \InvalidArgumentException("Unknown pack ID $id"); } } + + public function getPackUrl() : array + { + return $this->packUrl; + } + + public function addPackUrl(URLResourcePack $packUrl) : void + { + $this->packUrl[$packUrl->getRealName()] = $packUrl->getUrl(); + } } diff --git a/src/resourcepacks/ZippedResourcePack.php b/src/resourcepacks/ZippedResourcePack.php index 7ba5c467d5f..af83c184091 100644 --- a/src/resourcepacks/ZippedResourcePack.php +++ b/src/resourcepacks/ZippedResourcePack.php @@ -52,7 +52,7 @@ class ZippedResourcePack implements ResourcePack{ * @param string $zipPath Path to the resource pack zip * @throws ResourcePackException */ - public function __construct(string $zipPath){ + public function __construct(string $zipPath, protected string $encryptionKey = ""){ $this->path = $zipPath; if(!file_exists($zipPath)){ @@ -159,4 +159,21 @@ public function getPackChunk(int $start, int $length) : string{ } return Utils::assumeNotFalse(fread($this->fileResource, $length), "Already checked that we're not at EOF"); } + + public function getEncryptionKey() : string{ + return $this->encryptionKey; + } + + public function setEncryptionKey(string $key) : void{ + $this->encryptionKey = $key; + } + + private function hasEncryptionKey() : bool{ + return $this->encryptionKey != ""; + } + + public function getContentId() : string + { + return $this->hasEncryptionKey() ? $this->getPackId() : ""; + } } diff --git a/symply/resourcepacks/InvalidPackChunkURLException.php b/symply/resourcepacks/InvalidPackChunkURLException.php new file mode 100644 index 00000000000..89f774437aa --- /dev/null +++ b/symply/resourcepacks/InvalidPackChunkURLException.php @@ -0,0 +1,32 @@ +realName = "{$this->uuid}_{$this->version}"; + } + + public function getRealName() : string + { + return $this->realName; + } + + public function getPackName() : string + { + return $this->name; + } + + public function getPackId() : string + { + return $this->uuid; + } + + public function getPackSize() : int + { + return $this->size; + } + + public function getPackVersion() : string + { + return $this->version; + } + + public function getSha256() : string + { + return ""; + } + + /** + * @throws InvalidPackChunkURLException + */ + public function getPackChunk(int $start, int $length) : string + { + throw new InvalidPackChunkURLException("Invalid URL for pack chunk in {$this->getPackId()}. Unable to use the getPackChunk method."); + } + + public function getUrl() : string{ + return $this->url; + } + + public function getEncryptionKey() : string + { + return $this->encryptionKey; + } + public function hasEncryptionKey() : bool + { + return $this->encryptionKey !== ""; + } + public function getContentId() : string + { + return $this->hasEncryptionKey() ? $this->getPackId() : ""; + } + + public function setEncryptionKey(string $key) : void + { + $this->encryptionKey = $key; + } +} diff --git a/symply/utils/Utils.php b/symply/utils/Utils.php new file mode 100644 index 00000000000..2041c5c3529 --- /dev/null +++ b/symply/utils/Utils.php @@ -0,0 +1,68 @@ +