|
<?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/ |
|
* |
|
* |
|
*/ |
|
|
|
declare(strict_types=1); |
|
|
|
namespace pocketmine\protocoltools; |
|
|
|
use pocketmine\block\BlockFactory; |
|
use pocketmine\entity\Attribute; |
|
use pocketmine\item\Item; |
|
use pocketmine\item\ItemFactory; |
|
use pocketmine\nbt\NBT; |
|
use pocketmine\nbt\NetworkLittleEndianNBTStream; |
|
use pocketmine\nbt\tag\CompoundTag; |
|
use pocketmine\nbt\tag\ListTag; |
|
use pocketmine\nbt\tag\StringTag; |
|
use pocketmine\network\mcpe\NetworkSession; |
|
use pocketmine\network\mcpe\protocol\AvailableActorIdentifiersPacket; |
|
use pocketmine\network\mcpe\protocol\AvailableCommandsPacket; |
|
use pocketmine\network\mcpe\protocol\BiomeDefinitionListPacket; |
|
use pocketmine\network\mcpe\protocol\CraftingDataPacket; |
|
use pocketmine\network\mcpe\protocol\DataPacket; |
|
use pocketmine\network\mcpe\protocol\InventoryContentPacket; |
|
use pocketmine\network\mcpe\protocol\PacketPool; |
|
use pocketmine\network\mcpe\protocol\PlayerAuthInputPacket; |
|
use pocketmine\network\mcpe\protocol\StartGamePacket; |
|
use pocketmine\network\mcpe\protocol\types\ContainerIds; |
|
use function array_chunk; |
|
use function array_map; |
|
use function base64_decode; |
|
use function base64_encode; |
|
use function bin2hex; |
|
use function chr; |
|
use function define; |
|
use function explode; |
|
use function file; |
|
use function file_put_contents; |
|
use function get_class; |
|
use function implode; |
|
use function json_decode; |
|
use function json_encode; |
|
use function ksort; |
|
use function ord; |
|
use function sort; |
|
use function strlen; |
|
use function strpos; |
|
use function substr; |
|
use const JSON_PRETTY_PRINT; |
|
use const PHP_EOL; |
|
use const SORT_NUMERIC; |
|
|
|
|
|
$func = function(){ |
|
|
|
global $argv; |
|
if(!isset($argv[1])){ |
|
die('Usage: ' . PHP_BINARY . ' ' . __FILE__ . ' <input file>'); |
|
} |
|
|
|
require 'vendor/autoload.php'; |
|
define('pocketmine\RESOURCE_PATH', __DIR__ . '/src/pocketmine/resources/'); |
|
|
|
PacketPool::init(); |
|
Attribute::init(); |
|
BlockFactory::init(); |
|
ItemFactory::init(); |
|
|
|
$handler = new class extends NetworkSession{ |
|
|
|
public function handleDataPacket(DataPacket $packet){ |
|
|
|
} |
|
|
|
public function handleStartGame(StartGamePacket $packet) : bool{ |
|
$blockTableFile = \pocketmine\RESOURCE_PATH . '/vanilla/required_block_states.dat'; |
|
if(file_exists($blockTableFile)){ |
|
echo "calculating required block table diff\n"; |
|
$oldBlockTable = (new NetworkLittleEndianNBTStream())->read(file_get_contents($blockTableFile)); |
|
if(!($oldBlockTable instanceof ListTag) or $oldBlockTable->getTagType() !== NBT::TAG_Compound){ |
|
throw new \RuntimeException("unexpected block table data, expected TAG_List<TAG_Compound> root"); |
|
} |
|
|
|
$newBlockTable = clone $packet->blockTable; |
|
/** @var CompoundTag $newState */ |
|
foreach($newBlockTable as $newState){ |
|
$newState->getCompoundTag("block")->removeTag("version"); |
|
} |
|
/** @var CompoundTag $oldState */ |
|
foreach($oldBlockTable as $oldState){ |
|
$oldState->getCompoundTag("block")->removeTag("version"); |
|
} |
|
$matched = 0; |
|
do{ |
|
$matchedThisLoop = 0; |
|
foreach($oldBlockTable as $k1 => $oldState){ |
|
foreach($newBlockTable as $k2 => $newState){ |
|
if($newState->equals($oldState)){ |
|
$oldBlockTable->remove($k1); |
|
$newBlockTable->remove($k2); |
|
$matched++; |
|
$matchedThisLoop++; |
|
break 2; |
|
} |
|
} |
|
} |
|
}while($matchedThisLoop > 0); |
|
echo "matched $matched states\n"; |
|
echo $oldBlockTable->count() . " old states removed: \n"; |
|
if($oldBlockTable->count() > 0){ |
|
echo $oldBlockTable . "\n"; |
|
} |
|
echo $newBlockTable->count() . " new states added: \n"; |
|
if($newBlockTable->count() > 0){ |
|
echo $newBlockTable . "\n"; |
|
} |
|
} |
|
echo "updating required blockstates table\n"; |
|
file_put_contents($blockTableFile, (new NetworkLittleEndianNBTStream())->write($packet->blockTable)); |
|
|
|
echo "updating legacy block ID mapping table\n"; |
|
$list = []; |
|
foreach($packet->blockTable as $entry){ |
|
assert($entry instanceof CompoundTag); |
|
$list[$entry->getCompoundTag("block")->getString("name")] = $entry->getShort("id"); |
|
} |
|
asort($list, SORT_NUMERIC); |
|
file_put_contents(\pocketmine\RESOURCE_PATH . '/vanilla/block_id_map.json', json_encode($list, JSON_PRETTY_PRINT)); |
|
|
|
echo "updating legacy item ID mapping table\n"; |
|
asort($packet->itemTable, SORT_NUMERIC); |
|
file_put_contents(\pocketmine\RESOURCE_PATH . '/vanilla/item_id_map.json', json_encode($packet->itemTable, JSON_PRETTY_PRINT)); |
|
|
|
return true; |
|
} |
|
|
|
public function handleInventoryContent(InventoryContentPacket $packet) : bool{ |
|
if($packet->windowId === ContainerIds::CREATIVE){ |
|
echo "updating creative inventory data\n"; |
|
file_put_contents(\pocketmine\RESOURCE_PATH . 'vanilla/creativeitems.json', json_encode($packet->items, JSON_PRETTY_PRINT)); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
public function handleCraftingData(CraftingDataPacket $packet) : bool{ |
|
echo "updating crafting data\n"; |
|
$resultData = []; |
|
|
|
foreach($packet->decodedEntries as $entry){ |
|
switch($entry["type"]){ |
|
case CraftingDataPacket::ENTRY_SHAPED_CHEMISTRY: |
|
case CraftingDataPacket::ENTRY_SHAPED: |
|
$keys = []; |
|
|
|
$shape = []; |
|
$char = ord("A"); |
|
|
|
$map = array_chunk($entry["input"], $entry["width"]); |
|
foreach($map as $x => $row){ |
|
/** |
|
* @var Item $ingredient |
|
*/ |
|
foreach($row as $y => $ingredient){ |
|
if($ingredient->getId() === 0){ |
|
$shape[$x][$y] = " "; |
|
}else{ |
|
$hash = json_encode($ingredient); |
|
if(isset($keys[$hash])){ |
|
$shape[$x][$y] = $keys[$hash]; |
|
}else{ |
|
$keys[$hash] = $shape[$x][$y] = chr($char); |
|
$char++; |
|
} |
|
} |
|
} |
|
} |
|
|
|
unset($entry["input"]); |
|
|
|
$shapeEncoded = array_map(function(array $array) : string{ |
|
return implode('', $array); |
|
}, $shape); |
|
$entry["shape"] = $shapeEncoded; |
|
foreach($keys as $item => $letter){ |
|
$entry["input"][$letter] = json_decode($item, true); |
|
} |
|
unset($entry["uuid"], $entry["height"], $entry["width"]); |
|
break; |
|
case CraftingDataPacket::ENTRY_SHAPELESS: |
|
case CraftingDataPacket::ENTRY_SHAPELESS_CHEMISTRY: |
|
case CraftingDataPacket::ENTRY_SHULKER_BOX: |
|
unset($entry["uuid"]); |
|
|
|
break; |
|
default: |
|
//no preprocessing needed |
|
break; |
|
} |
|
static $typeMap = [ |
|
CraftingDataPacket::ENTRY_SHAPELESS => "shapeless", |
|
CraftingDataPacket::ENTRY_SHAPED => "shaped", |
|
CraftingDataPacket::ENTRY_FURNACE => "smelting", |
|
CraftingDataPacket::ENTRY_FURNACE_DATA => "smelting", |
|
CraftingDataPacket::ENTRY_MULTI => "special_hardcoded", |
|
CraftingDataPacket::ENTRY_SHULKER_BOX => "shapeless_shulker_box", |
|
CraftingDataPacket::ENTRY_SHAPELESS_CHEMISTRY => "shapeless_chemistry", |
|
CraftingDataPacket::ENTRY_SHAPED_CHEMISTRY => "shaped_chemistry" |
|
]; |
|
if(!isset($typeMap[$entry["type"]])){ |
|
throw new \UnexpectedValueException("Unknown recipe type ID " . $entry["type"]); |
|
} |
|
$entry["type"] = $typeMap[$entry["type"]]; |
|
$resultData[] = $entry; |
|
} |
|
|
|
foreach($packet->potionTypeRecipes as $recipe){ |
|
$resultData[] = [ |
|
"type" => "potion_type", |
|
"input_potion_id" => $recipe->getInputPotionType(), |
|
"output_potion_id" => $recipe->getOutputPotionType(), |
|
"ingredient" => ItemFactory::get($recipe->getIngredientItemId()) |
|
]; |
|
} |
|
foreach($packet->potionContainerRecipes as $recipe){ |
|
$resultData[] = [ |
|
"type" => "potion_container_change", |
|
"input_item_id" => $recipe->getInputItemId(), |
|
"output_item_id" => $recipe->getOutputItemId(), |
|
"ingredient" => ItemFactory::get($recipe->getIngredientItemId()) |
|
]; |
|
} |
|
|
|
foreach($resultData as &$_entry){ |
|
ksort($_entry); |
|
} |
|
file_put_contents(\pocketmine\RESOURCE_PATH . '/vanilla/recipes.json', json_encode($resultData, JSON_PRETTY_PRINT)); |
|
|
|
return true; |
|
} |
|
|
|
public function handleAvailableActorIdentifiers(AvailableActorIdentifiersPacket $packet) : bool{ |
|
echo "storing actor identifiers" . PHP_EOL; |
|
$tag = (new NetworkLittleEndianNBTStream())->read($packet->namedtag); |
|
if(!($tag instanceof CompoundTag)){ |
|
echo $tag . "\n"; |
|
throw new \RuntimeException("unexpected actor identifiers table, expected TAG_Compound root"); |
|
} |
|
if(!$tag->hasTag("idlist", ListTag::class) or $tag->getListTag("idlist")->getTagType() !== NBT::TAG_Compound){ |
|
echo $tag . "\n"; |
|
throw new \RuntimeException("expected TAG_List<TAG_Compound>(\"idlist\") tag inside root TAG_Compound"); |
|
} |
|
if($tag->count() > 1){ |
|
echo $tag . "\n"; |
|
echo "!!! unexpected extra data found in available actor identifiers\n"; |
|
} |
|
echo "updating legacy => string entity ID mapping table\n"; |
|
$map = []; |
|
/** |
|
* @var CompoundTag $thing |
|
*/ |
|
foreach($tag->getListTag("idlist") as $thing){ |
|
$map[$thing->getString("id")] = $thing->getInt("rid"); |
|
} |
|
asort($map, SORT_NUMERIC); |
|
file_put_contents(\pocketmine\RESOURCE_PATH . '/vanilla/entity_id_map.json', json_encode($map, JSON_PRETTY_PRINT)); |
|
echo "storing entity identifiers\n"; |
|
file_put_contents(\pocketmine\RESOURCE_PATH . '/vanilla/entity_identifiers.nbt', $packet->namedtag); |
|
return true; |
|
} |
|
|
|
public function handleBiomeDefinitionList(BiomeDefinitionListPacket $packet) : bool{ |
|
echo "storing biome definitions" . PHP_EOL; |
|
$defs = (new NetworkLittleEndianNBTStream())->read($packet->namedtag); |
|
file_put_contents(\pocketmine\RESOURCE_PATH . '/vanilla/biome_definitions.nbt', $packet->namedtag); |
|
return true; |
|
} |
|
}; |
|
|
|
foreach(file($argv[1], FILE_IGNORE_NEW_LINES) as $line){ |
|
[$type, $b64] = explode(':', $line); |
|
$b64 = trim($b64); |
|
$pk = PacketPool::getPacket(base64_decode($b64)); |
|
$pk->decode(); |
|
$pk->handle($handler); |
|
if(!$pk->feof()){ |
|
echo "didn't read all data from " . get_class($pk) . " (stopped at offset " . $pk->getOffset() . " of " . strlen($pk->getBuffer()) . " bytes): " . bin2hex($pk->getRemaining()) . "\n"; |
|
} |
|
} |
|
|
|
}; |
|
|
|
if(!defined('pocketmine\_PHPSTAN_ANALYSIS')){ |
|
$func(); |
|
} |