Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@dktapps
Last active January 14, 2021 11:32
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dktapps/09b28c2b8f7d9621924d4d41057c9384 to your computer and use it in GitHub Desktop.
Save dktapps/09b28c2b8f7d9621924d4d41057c9384 to your computer and use it in GitHub Desktop.

This tool is used to generate the data found in BedrockData.

Usage

Place the script in the root of a PocketMine-MP source code repository. Make sure the composer autoloader has been appropriately generated.

php decoder.php input_file.txt

Input file

The script requires an input file containing base64-encoded packets, one per line, like so:

read:packetbase64

or

write:packetbase64
<?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();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment