From c63240c7a8bc78082a7f30721500209f0605e6b7 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Pueyo Date: Fri, 7 Dec 2012 19:47:07 +0100 Subject: [PATCH] Updated to handle more things --- README | 2 + classes/ChunkParser.class.php | 57 +++++++++ classes/CustomPacketHandler.class.php | 56 ++++++++- classes/MapInterface.class.php | 155 ------------------------ classes/NBT.class.php | 14 +-- classes/PocketMinecraftServer.class.php | 23 +++- classes/Session.class.php | 26 +++- classes/Utils.class.php | 66 ++++++++-- common/default.properties | 2 +- common/dependencies.php | 1 - pstruct/5.php | 3 + pstruct/dataName.php | 2 + 12 files changed, 229 insertions(+), 178 deletions(-) delete mode 100644 classes/MapInterface.class.php diff --git a/README b/README index 519b8e7d1..4d2b994ba 100644 --- a/README +++ b/README @@ -34,8 +34,10 @@ Currently a work in progress, and used to document http://www.wiki.vg/Pocket_Min Current features of the server: - Players can connect and move around the world - Online list broadcast + - Configurable day/night cycle - Health and position saving - server.properties configuration file - Whitelist and IP Ban files - Survival & Creative - Awesome features in server list! + - Multiple worlds and importing! diff --git a/classes/ChunkParser.class.php b/classes/ChunkParser.class.php index 2fe293f07..343779b9b 100644 --- a/classes/ChunkParser.class.php +++ b/classes/ChunkParser.class.php @@ -25,3 +25,60 @@ the Free Software Foundation, either version 3 of the License, or */ +define("MAP_WIDTH", 256); +define("MAP_HEIGHT", 128); + +class ChunkParser{ + private $raw = b""; + var $sectorLenght = 4096; //16 * 16 * 16 + var $chunkLenght = 86016; //21 * $sectorLenght + + function __construct(){ + + } + + public function loadFile($file){ + if(!file_exists($file)){ + return false; + } + $this->raw = file_get_contents($file); + $this->chunkLenght = $this->sectorLenght * ord($this->raw{0}); + return true; + } + + private function getOffsetPosition($X, $Z){ + $data = substr($this->raw, ($X << 2) + ($Z << 7), 4); //$X * 4 + $Z * 128 + return array(ord($data{0}), ord($data{1}), ord($data{2}), ord($data{3})); + } + + private function getOffset($X, $Z){ + $info = $this->getOffsetPosition($X, $Z); + return 4096 + (($info[1] * $info[0]) << 12) + (($info[2] * $data[0]) << 16); + } + + public function getChunk($X, $Z, $header = true){ + $X = (int) $X; + $Z = (int) $Z; + if($header === false){ + $add = 4; + }else{ + $add = 0; + } + return substr($this->raw, $this->getOffset($X, $Z) + $add, $this->chunkLenght - $add); + } + + public function getColumn($X, $Z){ + + } + + public function getBlock($x, $y, $z){ + $x = (int) $x; + $y = (int) $y; + $z = (int) $z; + $X = $x >> 4; + $Z = $z >> 4; + $block = $this->getOffset($X, $Z) + 4 + (($x << 6) + $y + ($z << 10)); + $meta = $this->getOffset($X, $Z) + 4 + (($x << 6) + $y + ($z << 10)); + } + +} \ No newline at end of file diff --git a/classes/CustomPacketHandler.class.php b/classes/CustomPacketHandler.class.php index b7d6ecc11..862179096 100644 --- a/classes/CustomPacketHandler.class.php +++ b/classes/CustomPacketHandler.class.php @@ -214,7 +214,32 @@ class CustomPacketHandler{ $this->data["eid"] = Utils::readInt($this->get(4)); }else{ $this->raw .= Utils::writeInt($this->data["eid"]); - } + } + break; + case MC_ADD_ITEM_ENTITY: + if($this->c === false){ + $this->data["eid"] = Utils::readInt($this->get(4)); + $this->data["block"] = Utils::readShort($this->get(2), false); + $this->data["stack"] = ord($this->get(1)); + $this->data["meta"] = Utils::readShort($this->get(2), false); + $this->data["x"] = Utils::readFloat($this->get(4)); + $this->data["y"] = Utils::readFloat($this->get(4)); + $this->data["z"] = Utils::readFloat($this->get(4)); + $this->data["yaw"] = Utils::readByte($this->get(1)); + $this->data["pitch"] = Utils::readByte($this->get(1)); + $this->data["roll"] = Utils::readByte($this->get(1)); + }else{ + $this->raw .= Utils::writeInt($this->data["eid"]); + $this->raw .= Utils::writeShort($this->data["block"]); + $this->raw .= chr($this->data["stack"]); + $this->raw .= Utils::writeShort($this->data["meta"]); + $this->raw .= Utils::writeFloat($this->data["x"]); + $this->raw .= Utils::writeFloat($this->data["y"]); + $this->raw .= Utils::writeFloat($this->data["z"]); + $this->raw .= Utils::writeByte($this->data["yaw"]); + $this->raw .= Utils::writeByte($this->data["pitch"]); + $this->raw .= Utils::writeByte($this->data["roll"]); + } break; case MC_MOVE_PLAYER: if($this->c === false){ @@ -246,6 +271,21 @@ class CustomPacketHandler{ $this->raw .= chr($this->data["face"]); } break; + case MC_UPDATE_BLOCK: + if($this->c === false){ + $this->data["block"] = Utils::readShort($this->get(2)); + $this->data["meta"] = Utils::readShort($this->get(2)); + $this->data["x"] = Utils::readInt($this->get(4)); + $this->data["y"] = Utils::readInt($this->get(4)); + $this->data["z"] = Utils::readInt($this->get(4)); + }else{ + $this->raw .= Utils::writeShort($this->data["block"]); + $this->raw .= Utils::writeShort($this->data["meta"]); + $this->raw .= Utils::writeInt($this->data["x"]); + $this->raw .= Utils::writeInt($this->data["y"]); + $this->raw .= Utils::writeInt($this->data["z"]); + } + break; case MC_REQUEST_CHUNK: if($this->c === false){ $this->data["x"] = Utils::readInt($this->get(4)); @@ -255,6 +295,20 @@ class CustomPacketHandler{ $this->raw .= Utils::writeInt($this->data["y"]); } break; + case MC_CHUNK_DATA: + if($this->c === false){ + $this->data["x"] = Utils::readInt($this->get(4)); + $this->data["z"] = Utils::readInt($this->get(4)); + //$this->data["unknown1"] = $this->get(WTF); + $this->data["unknown1"] = Utils::readInt($this->get(4)); + $this->data["unknown2"] = Utils::readInt($this->get(4)); + //$this->data["unknown3"] = $this->get(WTF); + }else{ + $this->raw .= Utils::writeInt($this->data["x"]); + $this->raw .= Utils::writeInt($this->data["y"]); + $this->raw .= $this->data["data"]; + } + break; case MC_PLAYER_EQUIPMENT: if($this->c === false){ $this->data["eid"] = Utils::readInt($this->get(4)); diff --git a/classes/MapInterface.class.php b/classes/MapInterface.class.php deleted file mode 100644 index 31fcc37f4..000000000 --- a/classes/MapInterface.class.php +++ /dev/null @@ -1,155 +0,0 @@ -client = $client; - $this->map = $this->client->mapParser; - $this->floor = method_exists($this->map, "getFloor"); - $this->column = method_exists($this->map, "getColumn"); - //$this->biome = method_exists($this->map, "getBiome"); - //include("misc/materials.php"); - //$this->material = $material; - //include("misc/biomes.php"); - //$this->biomes = $biomes; - } - - public function getBiome($x, $z){ - $x = (int) $x; - $z = (int) $z; - if($this->biome === true){ - return $this->map->getBiome($x, $z); - }else{ - return 0; - } - } - - /*public function getBiomeName($x, $z){ - $biome = $this->getBiome($x, $z); - return isset($this->biomes[$biome]) ? $this->biomes[$biome]:"Unknown"; - }*/ - - public function getBlockName($x, $y, $z){ - $block = $this->getBlock($x, $y, $z); - return isset($this->material[$block[0]]) ? $this->material[$block[0]]:"Unknown"; - } - - public function getFloor($x, $z, $startY = -1){ - $x = (int) $x; - $z = (int) $z; - if($this->floor === true){ - $map = $this->map->getFloor($x, $z, $startY); - return $map; - }else{ - $startY = ((int) $startY) > -1 ? ((int) $startY):HEIGHT_LIMIT - 1; - for($y = $startY; $y > 0; --$y){ - $block = $this->getBlock($x, $y, $z); - if(!isset($this->material["nosolid"][$block[0]])){ - break; - } - } - return array($y, $block[0], $block[1]); - } - } - - public function changeBlock($x, $y, $z, $block, $metadata = 0){ - $x = (int) $x; - $y = (int) $y; - $z = (int) $z; - return $this->map->changeBlock($x, $y, $z, $block, $metadata); - } - - public function getBlock($x, $y, $z){ - $x = (int) $x; - $y = (int) $y; - $z = (int) $z; - return $this->map->getBlock($x, $y, $z); - } - - public function getColumn($x, $z){ - $x = (int) $x; - $z = (int) $z; - if($this->column === true){ - return $this->map->getColumn($x, $z); - }else{ - $zone = $this->getZone($x,0,$z,$x,HEIGHT_LIMIT,$z); - $data = array(); - foreach($zone as $x => $a){ - foreach($a as $y => $b){ - foreach($b as $z => $block){ - $data[$y] = $block; - } - } - } - return $data; - } - } - - public function getEllipse($x, $y, $z, $rX = 4, $rZ = 4, $rY = 4){ - $x = (int) $x; - $y = (int) $y; - $z = (int) $z; - $rY = abs((int) $rX); - $rY = abs((int) $rZ); - $rY = abs((int) $rY); - return $this->getZone($x-$rX,max(0,$y-$rY),$z-$rZ,$x+$rX,$y+$rY,$z+$rZ); - } - - public function getSphere($x, $y, $z, $r=4){ - $x = (int) $x; - $y = (int) $y; - $z = (int) $z; - $r = abs((int) $r); - return $this->getZone($x-$r,max(0,$y-$r),$z-$r,$x+$r,$y+$r,$z+$r); - } - - public function getZone($x1, $y1, $z1, $x2, $y2, $z2){ - $x1 = (int) $x1; - $y1 = (int) $y1; - $z1 = (int) $z1; - $x2 = (int) $x2; - $y2 = (int) $y2; - $z2 = (int) $z2; - if($x1>$x2 or $y1>$y2 or $z1>$z2){ - return array(); - } - $blocks = array(); - for($x=$x1;$x<=$x2;++$x){ - $blocks[$x] = array(); - for($z=$z1;$z<=$z2;++$z){ - $blocks[$x][$z] = array(); - for($y=$y1;$y<=$y2;++$y){ - $blocks[$x][$z][$y] = $this->getBlock($x,$y,$z); - } - } - } - return $blocks; - } - -} \ No newline at end of file diff --git a/classes/NBT.class.php b/classes/NBT.class.php index 6c4ee9b0a..dc9468e6b 100644 --- a/classes/NBT.class.php +++ b/classes/NBT.class.php @@ -34,8 +34,8 @@ class NBT { } switch(basename($filename, ".dat")){ case "level": - $version = Utils::readInt(strrev(fread($fp, 4))); - $lenght = Utils::readInt(strrev(fread($fp, 4))); + $version = Utils::readLInt(fread($fp, 4)); + $lenght = Utils::readLInt(fread($fp, 4)); break; case "entities": fread($fp, 12); @@ -65,15 +65,15 @@ class NBT { case self::TAG_BYTE: // Signed byte (8 bit) return Utils::readByte(fread($fp, 1)); case self::TAG_SHORT: // Signed short (16 bit, big endian) - return Utils::readShort(strrev(fread($fp, 2))); + return Utils::readLShort(fread($fp, 2)); case self::TAG_INT: // Signed integer (32 bit, big endian) - return Utils::readInt(strrev(fread($fp, 4))); + return Utils::readLInt(fread($fp, 4)); case self::TAG_LONG: // Signed long (64 bit, big endian) - return Utils::readLong(strrev(fread($fp, 8))); + return Utils::readLLong(fread($fp, 8)); case self::TAG_FLOAT: // Floating point value (32 bit, big endian, IEEE 754-2008) - return Utils::readFloat(strrev(fread($fp, 4))); + return Utils::readLFloat(fread($fp, 4)); case self::TAG_DOUBLE: // Double value (64 bit, big endian, IEEE 754-2008) - return Utils::readDouble(strrev(fread($fp, 8))); + return Utils::readLDouble(fread($fp, 8)); case self::TAG_BYTE_ARRAY: // Byte array $arrayLength = $this->readType($fp, self::TAG_INT); $array = array(); diff --git a/classes/PocketMinecraftServer.class.php b/classes/PocketMinecraftServer.class.php index b7d4f722e..cd7ff83c0 100644 --- a/classes/PocketMinecraftServer.class.php +++ b/classes/PocketMinecraftServer.class.php @@ -28,7 +28,7 @@ the Free Software Foundation, either version 3 of the License, or require_once("classes/Session.class.php"); class PocketMinecraftServer{ - var $seed, $protocol, $gamemode, $name, $maxClients, $clients, $eidCnt, $custom, $description, $motd, $timePerSecond, $responses, $spawn, $entities, $mapDir, $mapParser, $map, $level, $tileEntities; + var $seed, $protocol, $gamemode, $name, $maxClients, $clients, $eidCnt, $custom, $description, $motd, $timePerSecond, $responses, $spawn, $entities, $mapDir, $map, $level, $tileEntities; private $database, $interface, $cnt, $events, $version, $serverType, $lastTick; function __construct($name, $gamemode = 1, $seed = false, $protocol = CURRENT_PROTOCOL, $port = 19132, $serverID = false, $version = CURRENT_VERSION){ $this->port = (int) $port; @@ -39,7 +39,6 @@ class PocketMinecraftServer{ $this->version = (int) $version; $this->name = $name; $this->mapDir = false; - $this->mapParser = false; $this->map = false; $this->level = false; $this->tileEntities = array(); @@ -127,6 +126,7 @@ class PocketMinecraftServer{ public function close($reason = "stop"){ $this->chat(false, "Stopping server..."); + $this->save(); $this->stop = true; $this->trigger("onClose"); } @@ -163,10 +163,20 @@ class PocketMinecraftServer{ private function loadMap(){ $this->level = unserialize(file_get_contents($this->mapDir."level.dat")); console("[INFO] Map: ".$this->level["LevelName"]); - $this->seed = $this->level["RandomSeed"]; + $this->time = (int) $this->level["Time"]; + $this->level["Time"] = &$this->time; + console("[INFO] Time: ".$this->time); console("[INFO] Seed: ".$this->seed); console("[INFO] Gamemode: ".($this->gamemode === 0 ? "survival":"creative")); - console("[DEBUG] Loading entities..."); + console("[INFO] Loading map..."); + $this->map = new ChunkParser(); + if(!$this->map->loadFile($this->mapDir."chunks.dat")){ + console("[ERROR] Couldn't load the map \"".$this->level["LevelName"]."\"!", true, true, 0); + $this->map = false; + }else{ + + } + console("[INFO] Loading entities..."); $entities = unserialize(file_get_contents($this->mapDir."entities.dat")); foreach($entities as $entity){ $this->entities[$this->eidCnt] = new Entity($this->eidCnt, ENTITY_MOB, $entity["id"], $this); @@ -175,6 +185,11 @@ class PocketMinecraftServer{ ++$this->eidCnt; } console("[DEBUG] Loaded ".count($this->entities)." Entities", true, true, 2); + $this->action(1000000 * 60 * 15, '$this->chat(false, "Forcing save...");$this->save();$this->chat(false, "Done");'); + } + + public function save(){ + file_put_contents($this->mapDir."level.dat", serialize($this->level)); } public function start(){ diff --git a/classes/Session.class.php b/classes/Session.class.php index b45ece243..3b0802142 100644 --- a/classes/Session.class.php +++ b/classes/Session.class.php @@ -266,6 +266,9 @@ class Session{ ++$this->counter[0]; break; case MC_READY: + if(is_object($this->entity)){ + break; + } $this->server->trigger("onHealthChange", array("eid" => $this->eid, "health" => $this->data["health"])); console("[DEBUG] Player with EID ".$this->eid." \"".$this->username."\" spawned!", true, true, 2); $this->entity = new Entity($this->eid, ENTITY_PLAYER, 0, $this->server); @@ -287,12 +290,33 @@ class Session{ $this->server->trigger("onChat", $this->username." joined the game"); break; case MC_MOVE_PLAYER: - $this->entity->setPosition($data["x"], $data["y"], $data["z"], $data["x"], $data["yaw"], $data["pitch"]); + $this->entity->setPosition($data["x"], $data["y"], $data["z"], $data["yaw"], $data["pitch"]); $this->server->trigger("onPlayerMove", $this->eid); break; case MC_PLAYER_EQUIPMENT: console("[DEBUG] EID ".$this->eid." has now ".$data["block"]." with metadata ".$data["meta"]." in their hands!", true, true, 2); break; + case MC_REQUEST_CHUNK: + console("[DEBUG] Chunk X ".$data["x"]." Z ".$data["z"]." requested", true, true, 2); + break; + case MC_REMOVE_BLOCK: + console("[DEBUG] EID ".$this->eid." broke block at X ".$data["x"]." Y ".$data["y"]." Z ".$data["z"], true, true, 2); + $this->send(0x84, array( + $this->counter[0], + 0x00, + array( + "id" => MC_ADD_ITEM_ENTITY, + "eid" => $this->server->eidCnt++, + "x" => $data["x"], + "y" => $data["y"], + "z" => $data["z"], + "block" => 1, + "meta" => 0, + "stack" => 1, + ), + )); + ++$this->counter[0]; + break; case MC_RESPAWN: $this->server->trigger("onHealthChange", array("eid" => $this->eid, "health" => 20)); $this->entity->setPosition($data["x"], $data["y"], $data["z"], $data["x"], 0, 0); diff --git a/classes/Utils.class.php b/classes/Utils.class.php index 7cb999ac5..ce13bcd98 100644 --- a/classes/Utils.class.php +++ b/classes/Utils.class.php @@ -252,14 +252,6 @@ class Utils{ return pack("H*" , $hex); } - public static function readString($str){ - return preg_replace('/\x00(.)/s', '$1', $str); - } - - public static function writeString($str){ - return preg_replace('/(.)/s', "\x00$1", $str); - } - public static function readBool($b){ return Utils::readByte($b, false) === 0 ? false:true; } @@ -300,6 +292,21 @@ class Utils{ } return pack("n", $value); } + + public static function readLShort($str, $signed = true){ + list(,$unpacked) = unpack("v", $str); + if($unpacked > 0x7fff and $signed === true){ + $unpacked -= 0x10000; // Convert unsigned short to signed short + } + return $unpacked; + } + + public static function writeLShort($value){ + if($value < 0){ + $value += 0x10000; + } + return pack("v", $value); + } public static function readInt($str){ list(,$unpacked) = unpack("N", $str); @@ -316,6 +323,21 @@ class Utils{ return pack("N", $value); } + public static function readLInt($str){ + list(,$unpacked) = unpack("V", $str); + if($unpacked >= 2147483648){ + $unpacked -= 4294967296; + } + return (int) $unpacked; + } + + public static function writeLInt($value){ + if($value < 0){ + $value += 0x100000000; + } + return pack("V", $value); + } + public static function readFloat($str){ list(,$value) = ENDIANNESS === BIG_ENDIAN ? unpack("f", $str):unpack("f", strrev($str)); return $value; @@ -325,6 +347,15 @@ class Utils{ return ENDIANNESS === BIG_ENDIAN ? pack("f", $value):strrev(pack("f", $value)); } + public static function readLFloat($str){ + list(,$value) = ENDIANNESS === BIG_ENDIAN ? unpack("f", strrev($str)):unpack("f", $str); + return $value; + } + + public static function writeLFloat($value){ + return ENDIANNESS === BIG_ENDIAN ? strrev(pack("f", $value)):pack("f", $value); + } + public static function printFloat($value){ return preg_replace("/(\.\d+?)0+$/", "$1", sprintf("%F", $value)); } @@ -337,6 +368,15 @@ class Utils{ public static function writeDouble($value){ return ENDIANNESS === BIG_ENDIAN ? pack("d", $value):strrev(pack("d", $value)); } + + public static function readLDouble($str){ + list(,$value) = ENDIANNESS === BIG_ENDIAN ? unpack("d", strrev($str)):unpack("d", $str); + return $value; + } + + public static function writeLDouble($value){ + return ENDIANNESS === BIG_ENDIAN ? strrev(pack("d", $value)):pack("d", $value); + } public static function readLong($str){ $long = new Math_BigInteger($str, -256); @@ -346,6 +386,16 @@ class Utils{ public static function writeLong($value){ $long = new Math_BigInteger($value, -10); return str_pad($long->toBytes(true), 8, "\x00", STR_PAD_LEFT); + } + + public static function readLLong($str){ + $long = new Math_BigInteger(strrev($str), -256); + return $long->toString(); + } + + public static function writeLLong($value){ + $long = new Math_BigInteger($value, -10); + return strrev(str_pad($long->toBytes(true), 8, "\x00", STR_PAD_LEFT)); } } \ No newline at end of file diff --git a/common/default.properties b/common/default.properties index d9be679a5..2cc701fd0 100644 --- a/common/default.properties +++ b/common/default.properties @@ -8,7 +8,7 @@ white-list=false debug=1 max-players=20 server-type=normal -time-per-second=10 +time-per-second=20 gamemode=1 seed=false level-name=false diff --git a/common/dependencies.php b/common/dependencies.php index 6f092688c..4db8464a7 100644 --- a/common/dependencies.php +++ b/common/dependencies.php @@ -71,7 +71,6 @@ require_once("classes/Utils.class.php"); require_once("classes/UDPSocket.class.php"); require_once("classes/Packet.class.php"); require_once("classes/Entity.class.php"); -require_once("classes/MapInterface.class.php"); require_once("classes/ChunkParser.class.php"); require_once("classes/NBT.class.php"); require_once("classes/SerializedPacketHandler.class.php"); diff --git a/pstruct/5.php b/pstruct/5.php index db02bd596..e65bd176e 100644 --- a/pstruct/5.php +++ b/pstruct/5.php @@ -47,13 +47,16 @@ define("MC_START_GAME", 0x87); define("MC_ADD_PLAYER", 0x89); define("MC_REMOVE_ENTITY", 0x8d); +define("MC_ADD_ITEM_ENTITY", 0x8e); define("MC_MOVE_ENTITY_POSROT", 0x93); define("MC_MOVE_PLAYER", 0x94); define("MC_PLACE_BLOCK", 0x95); define("MC_REMOVE_BLOCK", 0x96); +define("MC_UPDATE_BLOCK", 0x97); define("MC_REQUEST_CHUNK", 0x9d); +define("MC_CHUNK_DATA", 0x9e); define("MC_PLAYER_EQUIPMENT", 0x9f); diff --git a/pstruct/dataName.php b/pstruct/dataName.php index a20cf75bd..f6f62c84a 100644 --- a/pstruct/dataName.php +++ b/pstruct/dataName.php @@ -47,6 +47,7 @@ $dataName = array( MC_ADD_PLAYER => "AddPlayer", MC_REMOVE_ENTITY => "RemoveEntity", + MC_ADD_ITEM_ENTITY => "AddItemEntity", MC_MOVE_ENTITY_POSROT => "MoveEntity_PosRot", MC_MOVE_PLAYER => "MovePlayer", @@ -54,6 +55,7 @@ $dataName = array( MC_REMOVE_BLOCK => "RemoveBlock", MC_REQUEST_CHUNK => "RequestChunk", + MC_CHUNK_DATA => "ChunkData", MC_PLAYER_EQUIPMENT => "PlayerEquipment",