Added resource packs support

This commit is contained in:
Dylan K. Taylor 2017-03-10 20:00:31 +00:00
parent 1f2b584400
commit d41bdfc31c
12 changed files with 370 additions and 40 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ timings/*
server.properties
/pocketmine.yml
memoryDump_*/*
resource_packs/
# Common IDEs
.idea/

View File

@ -189,6 +189,7 @@ use pocketmine\network\SourceInterface;
use pocketmine\permission\PermissibleBase;
use pocketmine\permission\PermissionAttachment;
use pocketmine\plugin\Plugin;
use pocketmine\resourcepacks\ResourcePack;
use pocketmine\tile\ItemFrame;
use pocketmine\tile\Spawnable;
use pocketmine\utils\TextFormat;
@ -1934,7 +1935,10 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
$this->sendPlayStatus(PlayStatusPacket::LOGIN_SUCCESS);
$pk = new ResourcePacksInfoPacket();
$this->dataPacket($pk); //TODO: add resource packs stuff
$manager = $this->server->getResourceManager();
$pk->resourcePackEntries = $manager->getResourceStack();
$pk->mustAccept = $manager->resourcePacksRequired();
$this->dataPacket($pk);
return true;
}
@ -1984,11 +1988,31 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
$this->close("", "must accept resource packs to join", true);
break;
case ResourcePackClientResponsePacket::STATUS_SEND_PACKS:
//TODO
$manager = $this->server->getResourceManager();
foreach($packet->packIds as $uuid){
$pack = $manager->getPackById($uuid);
if(!($pack instanceof ResourcePack)){
//Client requested a resource pack but we don't have it available on the server
$this->close("", "disconnectionScreen.resourcePack", true); //TODO: add strings to lang files
break;
}
$pk = new ResourcePackDataInfoPacket();
$pk->packId = $pack->getPackId();
$pk->maxChunkSize = 1048576; //1MB
$pk->chunkCount = $pack->getPackSize() / $pk->maxChunkSize;
$pk->compressedPackSize = $pack->getPackSize();
$pk->sha256 = $pack->getSha256();
$this->dataPacket($pk);
}
break;
case ResourcePackClientResponsePacket::STATUS_HAVE_ALL_PACKS:
$pk = new ResourcePackStackPacket();
$this->dataPacket($pk); //TODO: send resource stack
$manager = $this->server->getResourceManager();
$pk->resourcePackStack = $manager->getResourceStack();
$pk->mustAccept = $manager->resourcePacksRequired();
$this->dataPacket($pk);
break;
case ResourcePackClientResponsePacket::STATUS_COMPLETED:
$this->processLogin();
@ -3292,7 +3316,20 @@ class Player extends Human implements CommandSender, InventoryHolder, ChunkLoade
}
public function handleResourcePackChunkRequest(ResourcePackChunkRequestPacket $packet) : bool{
return false;
$manager = $this->server->getResourceManager();
$pack = $manager->getPackById($packet->packId);
if(!($pack instanceof ResourcePack)){
$this->close("", "disconnectionScreen.resourcePack", true);
return true;
}
$pk = new ResourcePackChunkDataPacket();
$pk->packId = $pack->getPackId();
$pk->chunkIndex = $packet->chunkIndex;
$pk->data = $pack->getPackChunk(1048576 * $packet->chunkIndex, 1048576);
$pk->progress = (1048576 * $packet->chunkIndex);
$this->dataPacket($pk);
return true;
}
public function handleTransfer(TransferPacket $packet) : bool{

View File

@ -446,6 +446,11 @@ namespace pocketmine {
++$errors;
}
if(!extension_loaded("openssl")){
$logger->critical("Unable to find the OpenSSL extension.");
++$errors;
}
if($errors > 0){
$logger->critical("Please use the installer provided on the homepage, or recompile PHP again.");
$logger->shutdown();

View File

@ -90,6 +90,7 @@ use pocketmine\plugin\Plugin;
use pocketmine\plugin\PluginLoadOrder;
use pocketmine\plugin\PluginManager;
use pocketmine\plugin\ScriptPluginLoader;
use pocketmine\resourcepacks\ResourcePackManager;
use pocketmine\scheduler\FileWriteTask;
use pocketmine\scheduler\SendUsageTask;
use pocketmine\scheduler\ServerScheduler;
@ -176,6 +177,9 @@ class Server{
/** @var CraftingManager */
private $craftingManager;
/** @var ResourcePackManager */
private $resourceManager;
/** @var ConsoleCommandSender */
private $consoleSender;
@ -594,6 +598,13 @@ class Server{
return $this->craftingManager;
}
/**
* @return ResourcePackManager
*/
public function getResourceManager() : ResourcePackManager{
return $this->resourceManager;
}
/**
* @return ServerScheduler
*/
@ -1510,6 +1521,8 @@ class Server{
Attribute::init();
$this->craftingManager = new CraftingManager();
$this->resourceManager = new ResourcePackManager($this, \pocketmine\PATH . "resource_packs" . DIRECTORY_SEPARATOR);
$this->pluginManager = new PluginManager($this, $this->commandMap);
$this->pluginManager->subscribeToPermission(Server::BROADCAST_CHANNEL_ADMINISTRATIVE, $this->consoleSender);
$this->pluginManager->setUseTimings($this->getProperty("settings.enable-profiling", false));
@ -2165,9 +2178,7 @@ class Server{
private function checkTickUpdates($currentTick, $tickTime){
foreach($this->players as $p){
if(!$p->loggedIn and ($tickTime - $p->creationTime) >= 10){
$p->close("", "Login timeout");
}elseif($this->alwaysTickPlayers){
if($this->alwaysTickPlayers){
$p->onUpdate($currentTick);
}
}

View File

@ -31,21 +31,22 @@ class ResourcePackChunkDataPacket extends DataPacket{
const NETWORK_ID = ProtocolInfo::RESOURCE_PACK_CHUNK_DATA_PACKET;
public $packId;
public $unknown1;
public $unknown2;
public $chunkIndex;
public $progress;
public $data;
public function decode(){
$this->packId = $this->getString();
$this->unknown1 = $this->getLInt();
$this->unknown2 = $this->getLLong();
$this->chunkIndex = $this->getLInt();
$this->progress = $this->getLLong();
$this->data = $this->get($this->getLInt());
}
public function encode(){
$this->reset();
$this->putString($this->packId);
$this->putLInt($this->unknown1);
$this->putLLong($this->unknown2);
$this->putLInt($this->chunkIndex);
$this->putLLong($this->progress);
$this->putLInt(strlen($this->data));
$this->put($this->data);
}

View File

@ -31,17 +31,17 @@ class ResourcePackChunkRequestPacket extends DataPacket{
const NETWORK_ID = ProtocolInfo::RESOURCE_PACK_CHUNK_REQUEST_PACKET;
public $packId;
public $unknown;
public $chunkIndex;
public function decode(){
$this->packId = $this->getString();
$this->unknown = $this->getLInt();
$this->chunkIndex = $this->getLInt();
}
public function encode(){
$this->reset();
$this->putString($this->packId);
$this->putLInt($this->unknown);
$this->putLInt($this->chunkIndex);
}
public function handle(NetworkSession $session) : bool{

View File

@ -31,26 +31,26 @@ class ResourcePackDataInfoPacket extends DataPacket{
const NETWORK_ID = ProtocolInfo::RESOURCE_PACK_DATA_INFO_PACKET;
public $packId;
public $int1;
public $int2;
public $packSize;
public $unknown;
public $maxChunkSize;
public $chunkCount;
public $compressedPackSize;
public $sha256;
public function decode(){
$this->packId = $this->getString();
$this->int1 = $this->getLInt();
$this->int2 = $this->getLInt();
$this->packSize = $this->getLLong();
$this->unknown = $this->getString();
$this->maxChunkSize = $this->getLInt();
$this->chunkCount = $this->getLInt();
$this->compressedPackSize = $this->getLLong();
$this->sha256 = $this->getString();
}
public function encode(){
$this->reset();
$this->putString($this->packId);
$this->putLInt($this->int1);
$this->putLInt($this->int2);
$this->putLLong($this->packSize);
$this->putString($this->unknown);
$this->putLInt($this->maxChunkSize);
$this->putLInt($this->chunkCount);
$this->putLLong($this->compressedPackSize);
$this->putString($this->sha256);
}
public function handle(NetworkSession $session) : bool{

View File

@ -26,6 +26,7 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\resourcepacks\ResourcePack;
use pocketmine\resourcepacks\ResourcePackInfoEntry;
class ResourcePackStackPacket extends DataPacket{
@ -33,13 +34,13 @@ class ResourcePackStackPacket extends DataPacket{
public $mustAccept = false;
/** @var ResourcePackInfoEntry[] */
/** @var ResourcePack[] */
public $behaviorPackStack = [];
/** @var ResourcePackInfoEntry[] */
/** @var ResourcePack[] */
public $resourcePackStack = [];
public function decode(){
$this->mustAccept = $this->getBool();
/*$this->mustAccept = $this->getBool();
$behaviorPackCount = $this->getLShort();
while($behaviorPackCount-- > 0){
$packId = $this->getString();
@ -52,7 +53,7 @@ class ResourcePackStackPacket extends DataPacket{
$packId = $this->getString();
$version = $this->getString();
$this->resourcePackStack[] = new ResourcePackInfoEntry($packId, $version);
}
}*/
}
public function encode(){
@ -62,13 +63,13 @@ class ResourcePackStackPacket extends DataPacket{
$this->putLShort(count($this->behaviorPackStack));
foreach($this->behaviorPackStack as $entry){
$this->putString($entry->getPackId());
$this->putString($entry->getVersion());
$this->putString($entry->getPackVersion());
}
$this->putLShort(count($this->resourcePackStack));
foreach($this->resourcePackStack as $entry){
$this->putString($entry->getPackId());
$this->putString($entry->getVersion());
$this->putString($entry->getPackVersion());
}
}

View File

@ -25,19 +25,20 @@ namespace pocketmine\network\mcpe\protocol;
use pocketmine\network\mcpe\NetworkSession;
use pocketmine\resourcepacks\ResourcePack;
use pocketmine\resourcepacks\ResourcePackInfoEntry;
class ResourcePacksInfoPacket extends DataPacket{
const NETWORK_ID = ProtocolInfo::RESOURCE_PACKS_INFO_PACKET;
public $mustAccept = false; //if true, forces client to use selected resource packs
/** @var ResourcePackInfoEntry[] */
/** @var ResourcePack[] */
public $behaviorPackEntries = [];
/** @var ResourcePackInfoEntry[] */
/** @var ResourcePack[] */
public $resourcePackEntries = [];
public function decode(){
$this->mustAccept = $this->getBool();
/*$this->mustAccept = $this->getBool();
$behaviorPackCount = $this->getLShort();
while($behaviorPackCount-- > 0){
$id = $this->getString();
@ -52,7 +53,7 @@ class ResourcePacksInfoPacket extends DataPacket{
$version = $this->getString();
$size = $this->getLLong();
$this->resourcePackEntries[] = new ResourcePackInfoEntry($id, $version, $size);
}
}*/
}
public function encode(){
@ -62,13 +63,13 @@ class ResourcePacksInfoPacket extends DataPacket{
$this->putLShort(count($this->behaviorPackEntries));
foreach($this->behaviorPackEntries as $entry){
$this->putString($entry->getPackId());
$this->putString($entry->getVersion());
$this->putString($entry->getPackVersion());
$this->putLLong($entry->getPackSize());
}
$this->putLShort(count($this->resourcePackEntries));
foreach($this->resourcePackEntries as $entry){
$this->putString($entry->getPackId());
$this->putString($entry->getVersion());
$this->putString($entry->getPackVersion());
$this->putLLong($entry->getPackSize());
}
}

View File

@ -0,0 +1,39 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* 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.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
namespace pocketmine\resourcepacks;
interface ResourcePack{
public function getPackName() : string;
public function getPackId() : string;
public function getPackSize() : int;
public function getPackVersion() : string;
public function getSha256() : string;
public function getPackChunk(int $start, int $length) : string;
}

View File

@ -0,0 +1,129 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* 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.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
namespace pocketmine\resourcepacks;
use pocketmine\Server;
use pocketmine\utils\Config;
class ResourcePackManager{
/** @var Server */
private $server;
/** @var string */
private $path;
/** @var Config */
private $resourcePacksConfig;
/** @var bool */
private $serverForceResources = false;
/** @var ResourcePack[] */
private $resourcePacks = [];
/** @var ResourcePack[] */
private $uuidList = [];
public function __construct(Server $server, string $path){
$this->server = $server;
$this->path = $path;
if(!file_exists($this->path)){
$this->server->getLogger()->debug("Resource packs path $path does not exist, creating directory");
mkdir($this->path);
}elseif(!is_dir($this->path)){
throw new \InvalidArgumentException("Resource packs path $path exists and is not a directory");
}
$this->resourcePacksConfig = new Config($this->path . "resource_packs.yml", Config::YAML, []);
if(count($this->resourcePacksConfig->getAll()) === 0){
$this->resourcePacksConfig->set("force_resources", false);
$this->resourcePacksConfig->set("resource_stack", []);
$this->resourcePacksConfig->save();
}
$this->serverForceResources = (bool) $this->resourcePacksConfig->get("force_resources", false);
$this->server->getLogger()->info("Loading resource packs...");
foreach($this->resourcePacksConfig->get("resource_stack", []) as $pos => $pack){
try{
$packPath = $this->path . DIRECTORY_SEPARATOR . $pack;
if(file_exists($packPath)){
$newPack = null;
//Detect the type of resource pack.
if(is_dir($packPath)){
$this->server->getLogger()->warning("Skipped resource entry $pack due to directory resource packs currently unsupported");
}else{
$info = new \SplFileInfo($packPath);
switch($info->getExtension()){
case "zip":
$newPack = new ZippedResourcePack($packPath);
break;
default:
$this->server->getLogger()->warning("Skipped resource entry $pack due to format not recognized");
break;
}
}
if($newPack instanceof ResourcePack){
$this->resourcePacks[] = $newPack;
$this->uuidList[$newPack->getPackId()] = $newPack;
}
}else{
$this->server->getLogger()->warning("Skipped resource entry $pack due to file or directory not found");
}
}catch(\Throwable $e){
$this->server->getLogger()->logException($e);
}
}
$this->server->getLogger()->debug("Successfully loaded " . count($this->resourcePacks) . " resource packs");
}
/**
* @return bool
*/
public function resourcePacksRequired() : bool{
return $this->serverForceResources;
}
/**
* @return ResourcePack[]
*/
public function getResourceStack() : array{
return $this->resourcePacks;
}
/**
* @param string $id
*
* @return ResourcePack|null
*/
public function getPackById(string $id){
return $this->uuidList[$id] ?? null;
}
}

View File

@ -0,0 +1,105 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* 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.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
namespace pocketmine\resourcepacks;
class ZippedResourcePack implements ResourcePack{
public static function verifyManifest(\stdClass $manifest){
if(!isset($manifest->format_version) or !isset($manifest->header) or !isset($manifest->modules)){
return false;
}
//Right now we don't care about anything else, only the stuff we're sending to clients.
//TODO: add more manifest validation
return
isset($manifest->header->description) and
isset($manifest->header->name) and
isset($manifest->header->uuid) and
isset($manifest->header->version) and
count($manifest->header->version) === 3;
}
/** @var string */
protected $path;
/** @var \stdClass */
protected $manifest;
/** @var string */
protected $sha256 = null;
public function __construct(string $zipPath){
$this->path = $zipPath;
if(!file_exists($zipPath)){
throw new \InvalidArgumentException("Could not open resource pack $zipPath: file not found");
}
$archive = new \ZipArchive();
if(($openResult = $archive->open($zipPath)) !== true){
throw new \InvalidStateException("Encountered ZipArchive error code $openResult while trying to open $zipPath");
}
if(($manifestData = $archive->getFromName("manifest.json")) === false){
throw new \InvalidStateException("Could not load resource pack from $zipPath: manifest.json not found");
}
$archive->close();
$manifest = json_decode($manifestData);
if(!self::verifyManifest($manifest)){
throw new \InvalidStateException("Could not load resource pack from $zipPath: manifest.json is invalid or incomplete");
}
$this->manifest = $manifest;
}
public function getPackName() : string{
return $this->manifest->header->name;
}
public function getPackVersion() : string{
return implode(".", $this->manifest->header->version);
}
public function getPackId() : string{
return $this->manifest->header->uuid;
}
public function getPackSize() : int{
return filesize($this->path);
}
public function getSha256(bool $cached = true) : string{
if($this->sha256 === null or !$cached){
$this->sha256 = openssl_digest(file_get_contents($this->path), "sha256", true);
}
return $this->sha256;
}
public function getPackChunk(int $start, int $length) : string{
return substr(file_get_contents($this->path), $start, $length);
}
}