From 8b1c010b4d19a58ea007adc4e2c181e809e09ca6 Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Pueyo Date: Thu, 6 Dec 2012 16:10:26 +0100 Subject: [PATCH] More packets, new structure --- classes/CustomPacketHandler.class.php | 67 +++++++++++++++--- classes/PocketMinecraftClient.class.php | 17 ++--- classes/PocketMinecraftServer.class.php | 61 +++++++++++++---- classes/SerializedPacketHandler.class.php | 2 +- classes/Session.class.php | 83 +++++++++++++++++------ common/dependencies.php | 17 +++-- pstruct/RakNet.php | 18 ++--- pstruct/dataName.php | 15 ++++ server.php | 2 +- 9 files changed, 213 insertions(+), 69 deletions(-) diff --git a/classes/CustomPacketHandler.class.php b/classes/CustomPacketHandler.class.php index d98230fcb..70931bf8b 100644 --- a/classes/CustomPacketHandler.class.php +++ b/classes/CustomPacketHandler.class.php @@ -58,11 +58,11 @@ class CustomPacketHandler{ break; case 0x09: if($this->c === false){ - $this->data["clientID"] = $this->get(8); + $this->data["clientID"] = Utils::readLong($this->get(8)); $this->data["session"] = Utils::readLong($this->get(8)); $this->data["unknown2"] = $this->get(1); }else{ - $this->raw .= $this->data["clientID"]; + $this->raw .= Utils::writeLong($this->data["clientID"]); $this->raw .= Utils::writeLong($this->data["session"]); $this->raw .= "\x00"; } @@ -124,6 +124,9 @@ class CustomPacketHandler{ case 0x15: //null break; + case 0x18: + //null + break; case 0x82: if($this->c === false){ $this->data["username"] = $this->get(Utils::readShort($this->get(2), false)); @@ -139,6 +142,13 @@ class CustomPacketHandler{ $this->data["status"] = Utils::readInt($this->get(4)); }else{ $this->raw .= Utils::writeInt($this->data["status"]); + } + break; + case 0x84: + if($this->c === false){ + $this->data["status"] = ord($this->get(1)); + }else{ + $this->raw .= chr($this->data["status"]); } break; case 0x85: @@ -157,22 +167,59 @@ class CustomPacketHandler{ break; case 0x87: if($this->c === false){ - $this->data["seed"] = $this->get(4); - $this->data["spawnX"] = Utils::readInt($this->get(4)); - $this->data["spawnY"] = Utils::readInt($this->get(4)); - $this->data["spawnZ"] = Utils::readInt($this->get(4)); + $this->data["seed"] = Utils::readInt($this->get(4)); + $this->data["unknown1"] = Utils::readInt($this->get(4)); + $this->data["gamemode"] = Utils::readInt($this->get(4)); + $this->data["unknown2"] = Utils::readInt($this->get(4)); $this->data["x"] = Utils::readFloat($this->get(4)); $this->data["y"] = Utils::readFloat($this->get(4)); $this->data["z"] = Utils::readFloat($this->get(4)); }else{ - $this->raw .= $this->data["seed"]; - $this->raw .= Utils::writeInt($this->data["spawnX"]); - $this->raw .= Utils::writeInt($this->data["spawnY"]); - $this->raw .= Utils::writeInt($this->data["spawnZ"]); + $this->raw .= Utils::writeInt($this->data["seed"]); + $this->raw .= Utils::writeInt($this->data["unknown1"]); + $this->raw .= Utils::writeInt($this->data["gamemode"]); + $this->raw .= Utils::writeInt($this->data["unknown2"]); $this->raw .= Utils::writeFloat($this->data["x"]); $this->raw .= Utils::writeFloat($this->data["y"]); $this->raw .= Utils::writeFloat($this->data["z"]); } + break; + case 0x94: //MovePlayer + if($this->c === false){ + $this->data["eid"] = Utils::readInt($this->get(4)); + $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::readFloat($this->get(4)); + $this->data["pitch"] = Utils::readFloat($this->get(4)); + }else{ + $this->raw .= Utils::writeInt($this->data["eid"]); + $this->raw .= Utils::writeFloat($this->data["x"]); + $this->raw .= Utils::writeFloat($this->data["y"]); + $this->raw .= Utils::writeFloat($this->data["z"]); + $this->raw .= Utils::writeFloat($this->data["yaw"]); + $this->raw .= Utils::writeFloat($this->data["pitch"]); + } + break; + case 0x96: //RemoveBlock + if($this->c === false){ + $this->data["x"] = Utils::readInt($this->get(4)); + $this->data["y"] = Utils::readInt($this->get(4)); + $this->data["z"] = Utils::readInt($this->get(4)); + $this->data["face"] = ord($this->get(1)); + }else{ + $this->raw .= Utils::writeInt($this->data["x"]); + $this->raw .= Utils::writeInt($this->data["y"]); + $this->raw .= Utils::writeInt($this->data["z"]); + $this->raw .= chr($this->data["face"]); + } + break; + case 0xa5: //SetHealth + if($this->c === false){ + $this->data["health"] = ord($this->get(1)); + }else{ + $this->raw .= chr($this->data["health"]); + } break; case 0xb1: if($this->c === false){ diff --git a/classes/PocketMinecraftClient.class.php b/classes/PocketMinecraftClient.class.php index c79778619..6c6590ff3 100644 --- a/classes/PocketMinecraftClient.class.php +++ b/classes/PocketMinecraftClient.class.php @@ -28,20 +28,20 @@ the Free Software Foundation, either version 3 of the License, or class PocketMinecraftClient{ protected $interface, $protocol, $entities, $player, $cnt, $events, $username, $version, $clientID, $connected, $serverID, $start; var $serverList = array(), $counter; - function __construct($username, $protocol = CURRENT_PROTOCOL, $version = CURRENT_VERSION){ + function __construct($username, $protocol = CURRENT_PROTOCOL, $clientID = false, $version = CURRENT_VERSION){ //$this->player = new Player($username); $this->start = microtime(true); $this->version = (int) $version; $this->username = $username; $this->connected = false; $this->cnt = 1; - $this->clientID = Utils::getRandomBytes(8); + $this->clientID = $clientID === false ? Utils::readLong(Utils::getRandomBytes(8)):$clientID; $this->events = array("disabled" => array()); $this->actions = array(); $this->interface = new MinecraftInterface("255.255.255.255", $protocol, 19132); - console("[INFO] Creating Minecraft Client"); + console("[INFO] Starting Minecraft Client"); console("[INFO] Username: ".$this->username); - console("[INFO] Version: ".$this->version); + console("[INFO] Client GUID: ".$this->clientID); $this->event("onReceivedPacket", "packetHandler", true); $this->stop = false; $this->counter = array(0,0); @@ -118,7 +118,7 @@ class PocketMinecraftClient{ array( "id" => 0x09, "clientID" => $this->clientID, - "session" => "\x00\x00\x00\x00\x03\x1c\xaf\x05", + "session" => Utils::readLong("\x00\x00\x00\x00\x03\x1c\xaf\x05"), ), )); ++$this->counter[0]; @@ -138,6 +138,7 @@ class PocketMinecraftClient{ "port" => 19132, "dataArray" => $data["dataArray"], "session" => $data["session"], + "session2" => $data["session2"], ), )); ++$this->counter[0]; @@ -146,7 +147,7 @@ class PocketMinecraftClient{ 0x00, array( "id" => 0x00, - "payload" => "\x00\x00\x00\x00\x00".$data["session"]."\x5e", + "payload" => $data["session"], ), )); ++$this->counter[0]; @@ -159,7 +160,7 @@ class PocketMinecraftClient{ ), )); ++$this->counter[0]; - $this->send(0x84, array( + /*$this->send(0x84, array( $this->counter[0], 0x00, array( @@ -167,7 +168,7 @@ class PocketMinecraftClient{ "message" => $this->username, ), )); - ++$this->counter[0]; + ++$this->counter[0];*/ break; case 0x86: console("[DEBUG] Time: ".$data["time"], true, true, 2); diff --git a/classes/PocketMinecraftServer.class.php b/classes/PocketMinecraftServer.class.php index 23c060f93..41b332085 100644 --- a/classes/PocketMinecraftServer.class.php +++ b/classes/PocketMinecraftServer.class.php @@ -28,28 +28,54 @@ the Free Software Foundation, either version 3 of the License, or require_once("classes/Session.class.php"); class PocketMinecraftServer{ - var $seed, $protocol; - protected $interface, $entities, $player, $cnt, $events, $username, $version, $clients, $serverType; - function __construct($username, $protocol = CURRENT_PROTOCOL, $version = CURRENT_VERSION){ - //$this->player = new Player($username); + var $seed, $protocol, $gamemode, $name, $maxClients, $clients; + protected $interface, $entities, $player, $cnt, $events, $version, $serverType; + function __construct($name, $gamemode = 1, $seed = false, $port = 19132, $protocol = CURRENT_PROTOCOL, $serverID = false, $version = CURRENT_VERSION){ + $this->gamemode = (int) $gamemode; + $this->port = (int) $port; $this->version = (int) $version; - $this->username = $username; + $this->name = $name; $this->cnt = 1; - $this->serverID = Utils::getRandomBytes(8); - $this->seed = Utils::getRandomBytes(4);//"\x4f\xf0\x2d\x84"; + $this->maxClients = 20; + $this->serverID = $serverID === false ? Utils::readLong(Utils::getRandomBytes(8)):$serverID; + $this->seed = $seed === false ? Utils::readInt(Utils::getRandomBytes(4)):((int) $seed); $this->events = array("disabled" => array()); $this->actions = array(); $this->clients = array(); $this->protocol = (int) $protocol; + $this->time = 0; + //$this->event("onTick", "onTick", true); + $this->event("onChat", "eventHandler", true); + //$this->action(1000000, '$this->time += 10000;$this->trigger("onTimeChange", $this->time);'); $this->setType("normal"); - $this->interface = new MinecraftInterface("255.255.255.255", $this->protocol, 19132, true, false); - console("[INFO] Creating Minecraft Server"); - console("[INFO] Username: ".$this->username); - console("[INFO] Seed: ".Utils::readInt($this->seed)); - //console("[INFO] Protocol: ".$this->protocol); + $this->interface = new MinecraftInterface("255.255.255.255", $this->protocol, $this->port, true, false); + console("[INFO] Starting Minecraft PE Server at *:".$this->port); + $this->action(1000000 * 5 * 60, '$this->chat(false, "This server uses Pocket-Minecraft-PHP");'); + sleep(2); + $this->action(1000000 * 5 * 60, '$this->chat(false, "Check it at http://bit.ly/RE7uaW");'); + console("[INFO] Server Name: ".$this->name); + console("[INFO] Server GUID: ".$this->serverID); + console("[INFO] Seed: ".$this->seed); + console("[INFO] Gamemode: ".($this->gamemode === 0 ? "survival":"creative")); + console("[INFO] Max Clients: ".$this->maxClients); $this->stop = false; } + public function close($reason = "stop"){ + $this->chat(false, "Stopping server..."); + $this->stop = true; + $this->trigger("onClose"); + } + + public function chat($owner, $text, $target = true){ + $message = ""; + if($owner !== false){ + $message = "<".$owner."> "; + } + $message .= $text; + $this->trigger("onChat", $text); + } + public function setType($type = "demo"){ switch($type){ case "normal": @@ -62,6 +88,14 @@ class PocketMinecraftServer{ } + public function eventHandler($data, $event){ + switch($event){ + case "onChat": + console("[CHAT] $data"); + break; + } + } + public function action($microseconds, $code){ $this->actions[] = array($microseconds / 1000000, microtime(true), $code); console("[INTERNAL] Attached to action ".$microseconds, true, true, 3); @@ -72,6 +106,7 @@ class PocketMinecraftServer{ register_tick_function(array($this, "tickerFunction")); $this->action(50000, '$this->trigger("onTick", $time);'); $this->event("onReceivedPacket", "packetHandler", true); + register_shutdown_function(array($this, "close")); $this->process(); } @@ -102,7 +137,7 @@ class PocketMinecraftServer{ $data[0], $this->serverID, MAGIC, - $this->serverType. $this->username, + $this->serverType. $this->name . " [".count($this->clients)."/".$this->maxClients."]", ), false, $packet["ip"], $packet["port"]); break; case 0x05: diff --git a/classes/SerializedPacketHandler.class.php b/classes/SerializedPacketHandler.class.php index d03106dc6..98d40d0e8 100644 --- a/classes/SerializedPacketHandler.class.php +++ b/classes/SerializedPacketHandler.class.php @@ -60,7 +60,7 @@ class SerializedPacketHandler{ $pid = ord($this->get(1)); } - $len = Utils::readShort($this->get(2), false) >> 3; + $len = ceil(Utils::readShort($this->get(2), false) / 8); //Utils::readShort($this->get(2), false) >> 3; if($pid !== 0x00){ $c = Utils::readTriad($this->get(3)); } diff --git a/classes/Session.class.php b/classes/Session.class.php index 44ec5b78c..606f51080 100644 --- a/classes/Session.class.php +++ b/classes/Session.class.php @@ -27,30 +27,75 @@ the Free Software Foundation, either version 3 of the License, or class Session{ - protected $server, $serverID, $timeout, $eventID, $connected; + protected $server, $serverID, $timeout, $connected, $evid; var $clientID, $ip, $port, $counter, $username; function __construct($server, $clientID, $ip, $port){ $this->server = $server; $this->clientID = $clientID; + $this->CID = $this->server->clientID($ip, $port); $this->ip = $ip; $this->port = $port; $this->serverID =& $this->server->serverID; - $this->eventID = $this->server->event("onTick", array($this, "checkTimeout")); - console("[DEBUG] New Session started with ".$ip.":".$port, true, true, 2); + $this->timeout = microtime(true) + 25; + $this->evid = array(); + $this->evid[] = array("onTick", $this->server->event("onTick", array($this, "checkTimeout"))); + $this->evid[] = array("onClose", $this->server->event("onClose", array($this, "close"))); + console("[DEBUG] New Session started with ".$ip.":".$port.". Client GUID ".$this->clientID, true, true, 2); $this->connected = true; $this->counter = array(0, 0); } public function checkTimeout($time){ if($time > $this->timeout){ - $this->close(); + $this->close("timeout"); } } - public function close($reason = "timeout"){ - $this->server->deleteEvent("onTick", $this->eventID); + public function close($reason = "server stop"){ + foreach($this->evid as $ev){ + $this->server->deleteEvent($ev[0], $ev[1]); + } + if($this->reason === "server stop"){ + $this->send(0x84, array( + $this->counter[0], + 0x00, + array( + "id" => 0x15, + ), + )); + ++$this->counter[0]; + } $this->connected = false; + $this->server->trigger("onChat", $this->username." left the game"); console("[DEBUG] Session with ".$this->ip.":".$this->port." closed due to ".$reason, true, true, 2); + unset($this->server->clients[$this->CID]); + } + + public function eventHandler($data, $event){ + switch($event){ + case "onTimeChange": + $this->send(0x84, array( + $this->counter[0], + 0x00, + array( + "id" => 0x86, + "time" => $data, + ), + )); + ++$this->counter[0]; + break; + case "onChat": + $this->send(0x84, array( + $this->counter[0], + 0x00, + array( + "id" => 0x85, + "message" => $data, + ), + )); + ++$this->counter[0]; + break; + } } public function handle($pid, &$data){ @@ -72,16 +117,6 @@ class Session{ $this->send(0xc0, array(1, true, $data[0])); } switch($data["id"]){ - /*case 0x00: - $this->send(0x84, array( - $this->counter[0], - 0x40, - array( - "payload" => $data["payload"], - ), - )); - ++$this->counter[0]; - break;*/ case 0x15: $this->close("client disconnect"); break; @@ -102,6 +137,8 @@ class Session{ case 0x82: $this->username = $data["username"]; console("[INFO] ".$this->username." connected from ".$this->ip.":".$this->port); + $this->evid[] = array("onTimeChange", $this->server->event("onTimeChange", array($this, "eventHandler"))); + $this->evid[] = array("onChat", $this->server->event("onChat", array($this, "eventHandler"))); $this->send(0x84, array( $this->counter[0], 0x00, @@ -120,15 +157,17 @@ class Session{ "x" => 128, "y" => 100, "z" => 128, - "spawnX" => 0, - "spawnY" => 0, - "spawnZ" => 0, + "unknown1" => 0, + "gamemode" => $this->server->gamemode, + "unknwon2" => 0, ), )); ++$this->counter[0]; break; case 0x84: - console("[INFO] ".$this->username." spawned!"); + console("[DEBUG] ".$this->username." spawned!", true, true, 2); + $this->server->trigger("onChat", $this->username." joined the game"); + $this->eventHandler("Welcome to ".$this->server->name, "onChat"); break; } @@ -141,7 +180,9 @@ class Session{ } public function send($pid, $data = array(), $raw = false){ - $this->server->send($pid, $data, $raw, $this->ip, $this->port); + if($this->connected === true){ + $this->server->send($pid, $data, $raw, $this->ip, $this->port); + } } } \ No newline at end of file diff --git a/common/dependencies.php b/common/dependencies.php index f4f78e29d..9e2ed4470 100644 --- a/common/dependencies.php +++ b/common/dependencies.php @@ -43,18 +43,23 @@ if(version_compare("5.4.0", PHP_VERSION) > 0){ define("HEX2BIN", true); } -if(php_sapi_name()!=="cli"){ - console("[ERROR] Use PHP-CLI to execute the client or create your own", true, true, 0); +if(php_sapi_name() !== "cli"){ + console("[ERROR] Use PHP-CLI to execute the library or create your own", true, true, 0); ++$errors; } -if(!function_exists("gzinflate")){ +if(!extension_loaded("sockets")){ + console("[ERROR] Unable to find Socket extension", true, true, 0); + ++$errors; +} + +/*if(!extension_loaded("zlib")){ console("[ERROR] Unable to find Zlib extension", true, true, 0); ++$errors; -} +}*/ -if(!function_exists("socket_create")){ - console("[ERROR] Unable to find Socket functions", true, true, 0); +if(!extension_loaded("sqlite3")){ + console("[ERROR] Unable to find SQLite3 extension", true, true, 0); ++$errors; } diff --git a/pstruct/RakNet.php b/pstruct/RakNet.php index d616c7824..97b155754 100644 --- a/pstruct/RakNet.php +++ b/pstruct/RakNet.php @@ -27,7 +27,7 @@ the Free Software Foundation, either version 3 of the License, or $pstruct = array( 0x02 => array( - "double", //Ping ID + "long", //Ping ID "magic", ), @@ -39,7 +39,7 @@ $pstruct = array( 0x06 => array( "magic", - 8, //Server GUID + "long", //Server GUID "byte", //Server Security "short", //MTU Size ), @@ -49,12 +49,12 @@ $pstruct = array( 5, //Security Cookie (idk why it's sent here) "short", //Server UDP Port "short", //MTU Size - 8, //Client GUID + "long", //Client GUID ), 0x08 => array( "magic", - 8, //Server GUID + "long", //Server GUID "short", //Client UDP Port "short", //MTU Size "byte", //Security @@ -63,19 +63,19 @@ $pstruct = array( 0x1a => array( "byte", //Server Version "magic", - 8, //Server GUID + "long", //Server GUID ), 0x1c => array( - "double", //Ping ID - 8, //Server ID + "long", //Ping ID + "long", //Server GUID "magic", "string", //Data ), 0x1d => array( - "double", //Ping ID - 8, //Server ID + "long", //Ping ID + "long", //Server GUID "magic", "string", //Data ), diff --git a/pstruct/dataName.php b/pstruct/dataName.php index e3c38b567..e370ad499 100644 --- a/pstruct/dataName.php +++ b/pstruct/dataName.php @@ -27,11 +27,16 @@ the Free Software Foundation, either version 3 of the License, or $dataName = array( 0x00 => "KeepAlive", + 0x09 => "ClientHandshake", 0x10 => "ServerHandshake", + 0x13 => "ClientConnect", 0x15 => "ClientDisconnect", + + 0x18 => "ServerDisconnect", + 0x82 => "Login", 0x83 => "LoginStatus", 0x84 => "Ready", @@ -41,7 +46,17 @@ $dataName = array( 0x93 => "MoveEntity_PosRot", 0x94 => "MovePlayer", + + 0x96 => "RemoveBlock", + 0x9d => "RequestChunk", + 0x9f => "PlayerEquipment", + + 0xa1 => "UseItem", + 0xa4 => "SetEntityMotion", + 0xa5 => "SetHealth", + + 0xa7 => "Animate", ); \ No newline at end of file diff --git a/server.php b/server.php index 78863ad99..1f4ce11a2 100644 --- a/server.php +++ b/server.php @@ -29,6 +29,6 @@ require_once("common/dependencies.php"); require_once("classes/PocketMinecraftServer.class.php"); file_put_contents("packets.log", ""); -$server = new PocketMinecraftServer("TEST"); +$server = new PocketMinecraftServer("PHP Server"); $server->setType("minecon"); $server->start(); \ No newline at end of file