From 069ff9f96583993865d17afcfe224f39284d68ed Mon Sep 17 00:00:00 2001 From: Shoghi Cervantes Pueyo Date: Thu, 6 Dec 2012 20:58:16 +0100 Subject: [PATCH] Different protocols, server properties --- .gitignore | 3 +- classes/CustomPacketHandler.class.php | 55 ++++++++++---- classes/PocketMinecraftServer.class.php | 99 +++++++++++++++---------- classes/Session.class.php | 49 +++++++++--- common/default.properties | 13 ++++ pstruct/4.php | 4 +- pstruct/5.php | 34 ++++++++- pstruct/dataName.php | 34 +++++---- server.php | 61 ++++++++++++++- 9 files changed, 266 insertions(+), 86 deletions(-) create mode 100644 common/default.properties diff --git a/.gitignore b/.gitignore index b3d7c557e..8e58a873e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *.log -*.ini -test.php *.bat +server.properties ################# ## Eclipse diff --git a/classes/CustomPacketHandler.class.php b/classes/CustomPacketHandler.class.php index 6d95afccc..8dc45e2ef 100644 --- a/classes/CustomPacketHandler.class.php +++ b/classes/CustomPacketHandler.class.php @@ -56,7 +56,7 @@ class CustomPacketHandler{ $this->raw .= Utils::writeLong($this->data["payload"]); } break; - case MC_CLIENT_HANDSHAKE: + case MC_CLIENT_CONNECT: if($this->c === false){ $this->data["clientID"] = Utils::readLong($this->get(8)); $this->data["session"] = Utils::readLong($this->get(8)); @@ -98,7 +98,7 @@ class CustomPacketHandler{ $this->raw .= Utils::writeLong($this->data["session2"]); } break; - case MC_CLIENT_CONNECT: + case MC_CLIENT_HANDSHAKE: if($this->c === false){ $this->data["cookie"] = $this->get(4); // 043f57fe $this->data["security"] = $this->get(1); @@ -130,11 +130,11 @@ class CustomPacketHandler{ case MC_LOGIN: if($this->c === false){ $this->data["username"] = $this->get(Utils::readShort($this->get(2), false)); - $this->data["unknown1"] = Utils::readInt($this->get(4)); - $this->data["unknown2"] = Utils::readInt($this->get(4)); + $this->data["maxX"] = Utils::readInt($this->get(4)); + $this->data["maxY"] = Utils::readInt($this->get(4)); }else{ $this->raw .= Utils::writeShort(strlen($this->data["username"])).$this->data["username"]; - $this->raw .= "\x00\x00\x00\x07\x00\x00\x00\x07"; + $this->raw .= "\x00\x00\x00\x08\x00\x00\x00\x08"; } break; case MC_LOGIN_STATUS: @@ -158,19 +158,19 @@ class CustomPacketHandler{ $this->raw .= Utils::writeShort(strlen($this->data["message"])).$this->data["message"]; } break; - case 0x86: + case MC_SET_TIME: if($this->c === false){ $this->data["time"] = Utils::readInt($this->get(4)); }else{ $this->raw .= Utils::writeInt($this->data["time"]); } break; - case 0x87: + case MC_START_GAME: if($this->c === false){ $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["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)); @@ -178,13 +178,20 @@ class CustomPacketHandler{ $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::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"]); } - break; - case 0x94: //MovePlayer + break; + case MC_REMOVE_ENTITY: + if($this->c === false){ + $this->data["eid"] = Utils::readInt($this->get(4)); + }else{ + $this->raw .= Utils::writeInt($this->data["eid"]); + } + break; + case MC_MOVE_PLAYER: if($this->c === false){ $this->data["eid"] = Utils::readInt($this->get(4)); $this->data["x"] = Utils::readFloat($this->get(4)); @@ -201,7 +208,7 @@ class CustomPacketHandler{ $this->raw .= Utils::writeFloat($this->data["pitch"]); } break; - case 0x96: //RemoveBlock + case MC_REMOVE_BLOCK: if($this->c === false){ $this->data["x"] = Utils::readInt($this->get(4)); $this->data["y"] = Utils::readInt($this->get(4)); @@ -214,14 +221,34 @@ class CustomPacketHandler{ $this->raw .= chr($this->data["face"]); } break; - case 0xa5: //SetHealth + case MC_REQUEST_CHUNK: + if($this->c === false){ + $this->data["x"] = Utils::readInt($this->get(4)); + $this->data["z"] = Utils::readInt($this->get(4)); + }else{ + $this->raw .= Utils::writeInt($this->data["x"]); + $this->raw .= Utils::writeInt($this->data["y"]); + } + break; + case MC_PLAYER_EQUIPMENT: + if($this->c === false){ + $this->data["eid"] = Utils::readInt($this->get(4)); + $this->data["block"] = Utils::readShort($this->get(2), true); + $this->data["meta"] = Utils::readShort($this->get(2), true); + }else{ + $this->raw .= Utils::writeInt($this->data["eid"]); + $this->raw .= Utils::writeShort($this->data["block"]); + $this->raw .= Utils::writeShort($this->data["meta"]); + } + break; + case MC_SET_HEALTH: //SetHealth if($this->c === false){ $this->data["health"] = ord($this->get(1)); }else{ $this->raw .= chr($this->data["health"]); } break; - case 0xb1: + case MC_CLIENT_MESSAGE: if($this->c === false){ $this->data["message"] = $this->get(Utils::readShort($this->get(2), false)); }else{ diff --git a/classes/PocketMinecraftServer.class.php b/classes/PocketMinecraftServer.class.php index 407ac4b4f..c9160507c 100644 --- a/classes/PocketMinecraftServer.class.php +++ b/classes/PocketMinecraftServer.class.php @@ -28,15 +28,19 @@ 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; + var $seed, $protocol, $gamemode, $name, $maxClients, $clients, $eidCnt, $custom, $description, $motd; protected $interface, $entities, $player, $cnt, $events, $version, $serverType; function __construct($name, $gamemode = 1, $seed = false, $protocol = CURRENT_PROTOCOL, $port = 19132, $serverID = false, $version = CURRENT_VERSION){ $this->gamemode = (int) $gamemode; $this->port = (int) $port; $this->version = (int) $version; $this->name = $name; + $this->custom = array(); $this->cnt = 1; + $this->eidCnt = 1; $this->maxClients = 20; + $this->description = ""; + $this->motd = "Welcome to ".$name; $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()); @@ -46,13 +50,14 @@ class PocketMinecraftServer{ $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->action(1000000, '$this->time += 10;$this->trigger("onTimeChange", $this->time);'); + $this->action(1000000 * 60 * 10, '$this->custom = array();'); $this->setType("normal"); $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");'); + $this->action(1000000 * 3 * 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");'); + $this->action(1000000 * 3 * 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] Protocol Version: ".$this->protocol); @@ -131,41 +136,57 @@ class PocketMinecraftServer{ $CID = $this->clientID($packet["ip"], $packet["port"]); if(isset($this->clients[$CID])){ $this->clients[$CID]->handle($packet["pid"], $data); - } - switch($packet["pid"]){ - case 0x02: - $this->send(0x1c, array( - $data[0], - $this->serverID, - MAGIC, - $this->serverType. $this->name . " [".count($this->clients)."/".$this->maxClients."]", - ), false, $packet["ip"], $packet["port"]); - break; - case 0x05: - $version = $data[1]; - $size = strlen($data[2]); - if($version !== $this->protocol){ - $this->send(0x1a, array( - 5, - MAGIC, + }else{ + switch($packet["pid"]){ + case 0x02: + if(!isset($this->custom["times_".$CID])){ + $this->custom["times_".$CID] = 0; + } + $ln = 15; + $txt = substr($this->description, $this->custom["times_".$CID], $ln); + $txt .= substr($this->descriptiont, 0, $ln - strlen($txt)); + $this->send(0x1c, array( + $data[0], $this->serverID, - ), false, $packet["ip"], $packet["port"]); - }else{ - $this->send(0x06, array( MAGIC, - $this->serverID, - 0, - strlen($packet["raw"]), + $this->serverType. $this->name . " [".($this->gamemode === 1 ? "C":"S")." ".count($this->clients)."/".$this->maxClients."] ".$txt, ), false, $packet["ip"], $packet["port"]); - } - break; - case 0x07: - $port = $data[2]; - $MTU = $data[3]; - $clientID = $data[4]; - $this->clients[$CID] = new Session($this, $clientID, $packet["ip"], $packet["port"]); - $this->clients[$CID]->handle(0x07, $data); - break; + $this->custom["times_".$CID] = ($this->custom["times_".$CID] + 1) % strlen($this->description); + break; + case 0x05: + if(count($this->clients) >= $this->maxClients){ + break; + } + $version = $data[1]; + $size = strlen($data[2]); + if($version !== $this->protocol){ + $this->send(0x1a, array( + 5, + MAGIC, + $this->serverID, + ), false, $packet["ip"], $packet["port"]); + }else{ + $this->send(0x06, array( + MAGIC, + $this->serverID, + 0, + strlen($packet["raw"]), + ), false, $packet["ip"], $packet["port"]); + } + break; + case 0x07: + if(count($this->clients) >= $this->maxClients){ + break; + } + $port = $data[2]; + $MTU = $data[3]; + $clientID = $data[4]; + $EID = $this->eidCnt++; + $this->clients[$CID] = new Session($this, $clientID, $EID, $packet["ip"], $packet["port"]); + $entities[$EID] = &$this->clients[$CID]; + $this->clients[$CID]->handle(0x07, $data); + break; + } } } @@ -190,11 +211,11 @@ class PocketMinecraftServer{ public function trigger($event, $data = ""){ console("[INTERNAL] Event ". $event, true, true, 3); if(isset($this->events[$event]) and !isset($this->events["disabled"][$event])){ - foreach($this->events[$event] as $eid => $ev){ + foreach($this->events[$event] as $evid => $ev){ if(isset($ev[1]) and ($ev[1] === true or is_object($ev[1]))){ - $this->responses[$eid] = call_user_func(array(($ev[1] === true ? $this:$ev[1]), $ev[0]), $data, $event, $this); + $this->responses[$evid] = call_user_func(array(($ev[1] === true ? $this:$ev[1]), $ev[0]), $data, $event, $this); }else{ - $this->responses[$eid] = call_user_func($ev[0], $data, $event, $this); + $this->responses[$evid] = call_user_func($ev[0], $data, $event, $this); } } } diff --git a/classes/Session.class.php b/classes/Session.class.php index 85683fdaf..994cca9cd 100644 --- a/classes/Session.class.php +++ b/classes/Session.class.php @@ -28,11 +28,12 @@ the Free Software Foundation, either version 3 of the License, or class Session{ protected $server, $serverID, $timeout, $connected, $evid; - var $clientID, $ip, $port, $counter, $username; - function __construct($server, $clientID, $ip, $port){ + var $clientID, $ip, $port, $counter, $username, $EID; + function __construct($server, $clientID, $EID, $ip, $port){ $this->server = $server; $this->clientID = $clientID; $this->CID = $this->server->clientID($ip, $port); + $this->EID = $EID; $this->ip = $ip; $this->port = $port; $this->serverID =& $this->server->serverID; @@ -113,7 +114,7 @@ class Session{ case MC_CLIENT_DISCONNECT: $this->close("client disconnect"); break; - case MC_CLIENT_HANDSHAKE: + case MC_CLIENT_CONNECT: $this->send(0x84, array( $this->counter[0], 0x00, @@ -126,10 +127,12 @@ class Session{ )); ++$this->counter[0]; break; - + case MC_CLIENT_HANDSHAKE: + + break; case MC_LOGIN: $this->username = $data["username"]; - console("[INFO] ".$this->username." connected from ".$this->ip.":".$this->port); + console("[INFO] Player \"".$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( @@ -147,20 +150,44 @@ class Session{ array( "id" => MC_START_GAME, "seed" => $this->server->seed, - "x" => 128, + "x" => 128.5, "y" => 100, - "z" => 128, + "z" => 128.5, "unknown1" => 0, "gamemode" => $this->server->gamemode, - "unknwon2" => 0, + "eid" => $this->EID, ), )); ++$this->counter[0]; break; - case 0x84: - console("[DEBUG] ".$this->username." spawned!", true, true, 2); + case MC_READY: + $this->send(0x84, array( + $this->counter[0], + 0x00, + array( + "id" => MC_SET_TIME, + "time" => $this->server->time, + ), + )); + console("[DEBUG] Player with EID ".$this->EID." \"".$this->username."\" spawned!", true, true, 2); $this->server->trigger("onChat", $this->username." joined the game"); - $this->eventHandler("Welcome to ".$this->server->name, "onChat"); + $this->eventHandler($this->server->motd, "onChat"); + break; + case MC_MOVE_PLAYER: + console("[DEBUG] EID ".$this->EID." moved: X ".$data["x"].", Y ".$data["y"].", Z ".$data["z"].", Pitch ".$data["pitch"].", Yaw ".$data["yaw"], true, true, 2); + break; + case MC_PLAYER_EQUIPMENT: + $this->send(0x84, array( + $this->counter[0], + 0x00, + array( + "id" => MC_PLAYER_EQUIPMENT, + "eid" => 0, + "block" => 323, + "meta" => 0, + ), + )); + console("[DEBUG] EID ".$this->EID." has now ".$data["block"]." with metadata ".$data["meta"]." in their hands!", true, true, 2); break; } diff --git a/common/default.properties b/common/default.properties new file mode 100644 index 000000000..21fe6de36 --- /dev/null +++ b/common/default.properties @@ -0,0 +1,13 @@ +#Pocket Minecraft PHP server properties +server-name=PHP Server +description= Warning: This is a Work in Progress custom server. +motd=Welcome to PHP Server +port=19132 +gamemode=1 +protocol=CURRENT +seed=false +server-id=false +server-type=normal +max-players=20 +spawn=128.5;100;128.5 +regenerate-config=true \ No newline at end of file diff --git a/pstruct/4.php b/pstruct/4.php index 747ffb6c8..959218404 100644 --- a/pstruct/4.php +++ b/pstruct/4.php @@ -29,9 +29,9 @@ the Free Software Foundation, either version 3 of the License, or define("MC_KEEP_ALIVE", 0x00); -define("MC_CLIENT_HANDSHAKE", 0x09); +define("MC_CLIENT_CONNECT", 0x09); define("MC_SERVER_HANDSHAKE", 0x10); -define("MC_CLIENT_CONNECT", 0x13); +define("MC_CLIENT_HANDSHAKE", 0x13); define("MC_CLIENT_DISCONNECT", 0x15); define("MC_LOGIN", 0x86); define("MC_LOGIN_STATUS", 0x87); diff --git a/pstruct/5.php b/pstruct/5.php index 30799803d..d5ada1af9 100644 --- a/pstruct/5.php +++ b/pstruct/5.php @@ -29,13 +29,41 @@ the Free Software Foundation, either version 3 of the License, or define("MC_KEEP_ALIVE", 0x00); -define("MC_CLIENT_HANDSHAKE", 0x09); + +define("MC_CLIENT_CONNECT", 0x09); define("MC_SERVER_HANDSHAKE", 0x10); -define("MC_CLIENT_CONNECT", 0x13); + +define("MC_CLIENT_HANDSHAKE", 0x13); + define("MC_CLIENT_DISCONNECT", 0x15); + define("MC_LOGIN", 0x82); define("MC_LOGIN_STATUS", 0x83); define("MC_READY", 0x84); define("MC_CHAT", 0x85); define("MC_SET_TIME", 0x86); -define("MC_START_GAME", 0x87); \ No newline at end of file +define("MC_START_GAME", 0x87); + +define("MC_REMOVE_ENTITY", 0x8d); + +define("MC_MOVE_ENTITY_POSROT", 0x93); +define("MC_MOVE_PLAYER", 0x94); +define("MC_PLACE_BLOCK", 0x95); +define("MC_REMOVE_BLOCK", 0x96); + +define("MC_REQUEST_CHUNK", 0x9d); + +define("MC_PLAYER_EQUIPMENT", 0x9f); + +define("MC_USE_ITEM", 0xa1); +define("MC_PLAYER_ACTION", 0xa2); +define("MC_SET_ENTITY_DATA", 0xa3); +define("MC_SET_ENTITY_MOTION", 0xa4); +define("MC_SET_HEALTH", 0xa5); +define("MC_SET_SPAWN_POSITION", 0xa6); +define("MC_ANIMATE", 0xa7); +define("MC_RESPAWN", 0xa8); + +define("MC_CLIENT_MESSAGE", 0xb1); +define("MC_SIGN_UPDATE", 0xb2); +define("MC_ADVENTURE_SETTINGS", 0xb3); \ No newline at end of file diff --git a/pstruct/dataName.php b/pstruct/dataName.php index b985158ef..7445e8400 100644 --- a/pstruct/dataName.php +++ b/pstruct/dataName.php @@ -28,10 +28,10 @@ the Free Software Foundation, either version 3 of the License, or $dataName = array( MC_KEEP_ALIVE => "KeepAlive", - MC_CLIENT_HANDSHAKE => "ClientHandshake", + MC_CLIENT_CONNECT => "ClientConnect", MC_SERVER_HANDSHAKE => "ServerHandshake", - MC_CLIENT_CONNECT => "ClientConnect", + MC_CLIENT_HANDSHAKE => "ClientHandshake", MC_CLIENT_DISCONNECT => "ClientDisconnect", @@ -44,21 +44,27 @@ $dataName = array( MC_SET_TIME => "SetTime", MC_START_GAME => "StartGame", - 0x93 => "MoveEntity_PosRot", - 0x94 => "MovePlayer", + MC_REMOVE_ENTITY => "RemoveEntity", - 0x96 => "RemoveBlock", + MC_MOVE_ENTITY_POSROT => "MoveEntity_PosRot", + MC_MOVE_PLAYER => "MovePlayer", + MC_PLACE_BLOCK => "PlaceBlock", + MC_REMOVE_BLOCK => "RemoveBlock", - 0x9d => "RequestChunk", + MC_REQUEST_CHUNK => "RequestChunk", - 0x9f => "PlayerEquipment", + MC_PLAYER_EQUIPMENT => "PlayerEquipment", - 0xa1 => "UseItem", + MC_USE_ITEM => "UseItem", + MC_PLAYER_ACTION => "PlayerAction", + MC_SET_ENTITY_DATA => "SetEntityData", + MC_SET_ENTITY_MOTION => "SetEntityMotion", + MC_SET_HEALTH => "SetHealth", + MC_SET_SPAWN_POSITION => "SetSpawnPosition", + MC_ANIMATE => "Animate", + MC_RESPAWN => "Respawn", - 0xa4 => "SetEntityMotion", - 0xa5 => "SetHealth", - - 0xa7 => "Animate", - - 0xb1 => "ClientMessage" + MC_CLIENT_MESSAGE => "ClientMessage", + MC_SIGN_UPDATE => "SignUpdate", + MC_ADVENTURE_SETTINGS => "AdventureSettings", ); \ No newline at end of file diff --git a/server.php b/server.php index f92e63536..35cf6a36b 100644 --- a/server.php +++ b/server.php @@ -28,6 +28,65 @@ the Free Software Foundation, either version 3 of the License, or require_once("common/dependencies.php"); require_once("classes/PocketMinecraftServer.class.php"); file_put_contents("packets.log", ""); +file_put_contents("console.log", ""); + +$prop = @file_get_contents(FILE_PATH."server.properties"); +if(trim($prop) == ""){ + console("[WARNING] No server.properties found, using default settings"); + copy(FILE_PATH."common/default.properties", FILE_PATH."server.properties"); + $prop = file_get_contents(FILE_PATH."server.properties"); +} +$prop = explode("\n", str_replace("\r", "", $prop)); +$config = array(); +foreach($prop as $line){ + if(trim($line) == "" or $line{0} == "#"){ + continue; + } + $d = explode("=", $line); + $n = strtolower(array_shift($d)); + $v = implode("=", $d); + switch($n){ + case "protocol": + if(trim($v) == "CURRENT"){ + $v = CURRENT_PROTOCOL; + break; + } + case "gamemode": + case "max-players": + case "port": + $v = (int) $v; + break; + case "seed": + case "server-id": + $v = trim($v) == "false" ? false:preg_replace("/^[0-9\-]/", "", $v); + break; + case "spawn": + $v = explode(";", $v); + $v = array("x" => floatval($v[0]), "y" => floatval($v[1]), "z" => floatval($v[2])); + break; + case "regenerate-config": + $v = trim($v) == "true" ? true:false; + break; + } + $config[$n] = $v; +} + +$server = new PocketMinecraftServer($config["server-name"], $config["gamemode"], $config["seed"], $config["protocol"], $config["port"], $config["server-id"]); +$server->setType($config["type"]); +$server->maxClients = $config["max-players"]; +$server->description = $config["description"]; +$server->motd = $config["motd"]; + +if($config["regenerate-config"] == true){ + $config["seed"] = $server->seed; + $config["server-id"] = $server->serverID; + $config["regenerate-config"] = "false"; + $config["spawn"] = implode(";", $config["spawn"]); + $prop = "#Pocket Minecraft PHP server properties\r\n#".date("D M j H:i:s T Y")."\r\n"; + foreach($config as $n => $v){ + $prop .= $n."=".$v."\r\n"; + } + file_put_contents(FILE_PATH."server.properties", $prop); +} -$server = new PocketMinecraftServer("PHP Server", 1, false, 5); $server->start(); \ No newline at end of file