diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..90de8d6
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+PacketLogger
\ No newline at end of file
diff --git a/.idea/PacketLogger.iml b/.idea/PacketLogger.iml
new file mode 100644
index 0000000..858a20d
--- /dev/null
+++ b/.idea/PacketLogger.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..e206d70
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..1162f43
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..7d8393a
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml
new file mode 100644
index 0000000..922003b
--- /dev/null
+++ b/.idea/scopes/scope_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..def6a6a
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..bf9819b
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,342 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1405186988136
+ 1405186988136
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..011eb9d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+Copyright (c) 2014 shoghicp
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/plugin.yml b/plugin.yml
new file mode 100644
index 0000000..89f9958
--- /dev/null
+++ b/plugin.yml
@@ -0,0 +1,5 @@
+name: PacketLogger
+author: shoghicp
+api: 1.0.0
+version: 1.0.0
+main: shoghicp\PacketLogger\PacketLogger
\ No newline at end of file
diff --git a/resources/config.yml b/resources/config.yml
new file mode 100644
index 0000000..5604635
--- /dev/null
+++ b/resources/config.yml
@@ -0,0 +1,23 @@
+#Configuration file for PacketLogger
+
+#variables available: {name}, {clientId}, {ip}, {time}
+logName: "{name}_{clientId}-{time}.log"
+
+#These selectors will match when ANY of the conditions is met
+selectors:
+ mode: match #When match, it will allow when a selector is matched. When refuse, it'll log when there is no match
+ name: #case insensitive
+ - shoghicp
+ - steve
+
+ clientId:
+ - 123456
+
+ ip:
+ - 192.168.0.1
+
+#These filters decide what packets will be logged
+filters:
+ packetId: #Filter by the packet id (faster)
+ default: true #Default value of the packets not specified here. true will log it, false will not
+ 0xba: false #Example: FullChunkDataPacket won't be logged
diff --git a/src/shoghicp/PacketLogger/PacketLogger.php b/src/shoghicp/PacketLogger/PacketLogger.php
new file mode 100644
index 0000000..fa7f6bb
--- /dev/null
+++ b/src/shoghicp/PacketLogger/PacketLogger.php
@@ -0,0 +1,223 @@
+saveDefaultConfig();
+ $config = $this->getConfig();
+ $selectors = $config->get("selectors");
+
+ if($config->exists("logName")){
+ $this->logName = $config->get("logName");
+ }
+
+ $this->mode = isset($selectors["mode"]) ? (strtolower($selectors["mode"]) === "refuse" ? false : true) : true;
+
+ if(isset($selectors["name"])){
+ foreach($selectors["name"] as $name){
+ $this->nameSelector[strtolower($name)] = true;
+ }
+ }
+
+ if(isset($selectors["clientId"])){
+ foreach($selectors["clientId"] as $clientId){
+ $this->clientIdSelector[(int) $clientId] = true;
+ }
+ }
+
+ if(isset($selectors["ip"])){
+ foreach($selectors["ip"] as $ip){
+ $this->ipSelector[$ip] = true;
+ }
+ }
+
+ $filters = $config->get("filters");
+ if(isset($filters["packetId"])){
+ $this->packetIdDefault = (bool) $filters["packetId"]["default"];
+ unset($filters["packetId"]["default"]);
+ foreach($filters["packetId"] as $packetId => $allow){
+ $this->packetIdFilter[(int) $packetId] = (bool) $allow;
+ }
+ }
+
+ $this->getServer()->getPluginManager()->registerEvents($this, $this);
+ }
+
+ public function onDisable(){
+ foreach($this->getServer()->getOnlinePlayers() as $player){
+ if(isset($this->sessions[spl_object_hash($player)])){
+ $this->closeSession($player);
+ }
+ }
+ }
+
+ private function isPacketAllowed(DataPacket $packet){
+ if(isset($this->packetIdFilter[$packet->pid()])){
+ return $this->packetIdFilter[$packet->pid()];
+ }
+ return $this->packetIdDefault;
+ }
+
+ private function logInboundPacket(Player $player, DataPacket $packet){
+ if($this->isPacketAllowed($packet)){
+ $packet->encode(); //other plugins might have changed the packet
+ $header = "[Client -> Server 0x".dechex($packet->pid())."] ".(new \ReflectionClass($packet))->getShortName() . " (length ".strlen($packet->buffer).")";
+ $fields = $this->getFields($packet);
+ $binary = trim(Utils::hexdump($packet->buffer));
+ fwrite($this->sessions[spl_object_hash($player)], $header . PHP_EOL . $binary . PHP_EOL . $fields . PHP_EOL . PHP_EOL . PHP_EOL);
+ }
+ }
+
+ private function logOutboundPacket(Player $player, DataPacket $packet){
+ if($this->isPacketAllowed($packet)){
+ $packet->encode(); //needed :(
+ $header = "[Server -> Client 0x".dechex($packet->pid())."] ".(new \ReflectionClass($packet))->getShortName() . " (length ".strlen($packet->buffer).")";
+ $fields = $this->getFields($packet);
+ $binary = trim(Utils::hexdump($packet->buffer));
+ fwrite($this->sessions[spl_object_hash($player)], $header . PHP_EOL . $binary . PHP_EOL . $fields . PHP_EOL . PHP_EOL . PHP_EOL);
+ }
+ }
+
+ private static function safePrint($value, $spaces = 2){
+ if(is_object($value)){
+ if((new \ReflectionClass($value))->hasMethod("__toString")){
+ $value = $value->__toString();
+ }else{
+ $value = get_class($value);
+ }
+ }elseif(is_string($value)){
+ if(!Utils::printable($value)){
+ $value = "0x".bin2hex($value);
+ }
+ }elseif(is_array($value)){
+ $d = "Array:";
+ foreach($value as $key => $v){
+ $d .= PHP_EOL . str_repeat(" ", $spaces) . "$key: " . self::safePrint($v, $spaces + 1);
+ }
+ $value = $d;
+ }else{
+ $value = trim(str_replace("\n", "\n ", print_r($value, true)));
+ }
+
+ return $value;
+ }
+
+ private function getFields(DataPacket $packet){
+ $output = "";
+ foreach($packet as $key => $value){
+ if($key === "buffer"){
+ continue;
+ }
+
+ $output .= " $key: " . self::safePrint($value) . PHP_EOL;
+ }
+
+ return rtrim($output);
+ }
+
+ private function writeHeader($fp, $name, $clientId, Player $player){
+ fwrite($fp, "# Log generated by PacketLogger ".$this->getDescription()->getVersion()
+ ." in PocketMine-MP ".$this->getServer()->getPocketMineVersion() ." for Minecraft: PE ".$this->getServer()->getVersion()
+ ." (protocol #".Info::CURRENT_PROTOCOL.")" . PHP_EOL . PHP_EOL);
+ fwrite($fp, "Player: $name, clientId $clientId from [/".$player->getAddress().":".$player->getPort() . PHP_EOL);
+ fwrite($fp, "Start time: ".date("c") . PHP_EOL . PHP_EOL);
+ }
+
+ private function closeSession(Player $player){
+ fwrite($this->sessions[$i = spl_object_hash($player)], PHP_EOL . PHP_EOL . "End time: ". date("c"));
+ fclose($this->sessions[$i]);
+ unset($this->sessions[$i]);
+ }
+
+ /**
+ * @param PlayerQuitEvent $event
+ *
+ * @priority NORMAL
+ */
+ public function onPlayerQuit(PlayerQuitEvent $event){
+ if(isset($this->sessions[spl_object_hash($event->getPlayer())])){
+ $this->closeSession($event->getPlayer());
+ }
+ }
+
+ /**
+ * @param DataPacketReceiveEvent $event
+ *
+ * @priority MONITOR
+ * @ignoreCancelled true
+ */
+ public function onInboundPacket(DataPacketReceiveEvent $event){
+ $player = $event->getPlayer();
+ $packet = $event->getPacket();
+ if(isset($this->sessions[spl_object_hash($player)])){
+ $this->logInboundPacket($player, $packet);
+ }elseif($packet instanceof LoginPacket){
+ $name = strtolower($packet->username);
+ $clientId = $packet->clientId;
+ $ip = $player->getAddress();
+
+ $match = false;
+ if(isset($this->nameSelector[$name])){
+ $match = true;
+ }elseif(isset($this->clientIdSelector[$clientId])){
+ $match = true;
+ }elseif(isset($this->ipSelector[$ip])){
+ $match = true;
+ }
+
+ if($match === $this->mode){
+ $path = $this->getDataFolder() . str_replace([
+ "{name}",
+ "{clientId}",
+ "{ip}",
+ "{time}"
+ ], [
+ $name,
+ $clientId,
+ $ip,
+ time()
+ ], $this->logName);
+ $this->sessions[spl_object_hash($player)] = fopen($path, "w+b");
+ $this->writeHeader($this->sessions[spl_object_hash($player)], $name, $clientId, $player);
+ $this->logInboundPacket($player, $packet);
+ }
+ }
+ }
+
+ /**
+ * @param DataPacketSendEvent $event
+ *
+ * @priority MONITOR
+ * @ignoreCancelled true
+ */
+ public function onOutboundPacket(DataPacketSendEvent $event){
+ $player = $event->getPlayer();
+ if(isset($this->sessions[spl_object_hash($player)])){
+ $this->logOutboundPacket($player, $event->getPacket());
+ }
+ }
+}
\ No newline at end of file