mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-05-24 14:34:52 +00:00
263 lines
7.7 KiB
PHP
263 lines
7.7 KiB
PHP
<?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/
|
|
*
|
|
*
|
|
*/
|
|
|
|
class MinecraftInterface{
|
|
public $client;
|
|
public $bandwidth;
|
|
private $socket;
|
|
private $data;
|
|
private $chunked;
|
|
private $toChunk;
|
|
private $needCheck;
|
|
function __construct($object, $server, $port = 25565, $listen = false, $client = false, $serverip = "0.0.0.0"){
|
|
$this->socket = new UDPSocket($server, $port, (bool) $listen, $serverip);
|
|
if($this->socket->connected === false){
|
|
console("[ERROR] Couldn't bind to $serverip:".$port, true, true, 0);
|
|
exit(1);
|
|
}
|
|
$this->bandwidth = array(0, 0, microtime(true));
|
|
$this->client = (bool) $client;
|
|
$this->start = microtime(true);
|
|
$this->chunked = array();
|
|
$this->toChunk = array();
|
|
$this->needCheck = array();
|
|
$object->schedule(1, array($this, "checkChunked"), array(), true);
|
|
}
|
|
|
|
public function close(){
|
|
return $this->socket->close(false);
|
|
}
|
|
|
|
protected function getStruct($pid){
|
|
if(isset(Protocol::$raknet[$pid])){
|
|
return Protocol::$raknet[$pid];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function readPacket(){
|
|
$pk = $this->popPacket();
|
|
if($this->socket->connected === false){
|
|
return $pk;
|
|
}
|
|
$buf = "";
|
|
$source = false;
|
|
$port = 1;
|
|
$len = $this->socket->read($buf, $source, $port);
|
|
if($len === false){
|
|
return $pk;
|
|
}
|
|
$this->bandwidth[0] += $len;
|
|
$this->parsePacket($buf, $source, $port);
|
|
return ($pk !== false ? $pk : $this->popPacket());
|
|
}
|
|
|
|
private function parsePacket($buf, $source, $port){
|
|
$pid = ord($buf{0});
|
|
$struct = $this->getStruct($pid);
|
|
if($struct === false){
|
|
if(ServerAPI::request()->api->dhandle("server.unknownpacket", array(
|
|
"pid" => $pid,
|
|
"data" => array(),
|
|
"raw" => $buf,
|
|
"ip" => $source,
|
|
"port" => $port
|
|
)) !== true){
|
|
console("[ERROR] Unknown Packet ID 0x".Utils::strToHex(chr($pid)), true, true, 2);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
$packet = new Packet($pid, $struct, $buf);
|
|
@$packet->parse();
|
|
if($pid === 0x99){
|
|
$CID = PocketMinecraftServer::clientID($source, $port);
|
|
if(!isset($this->chunked[$CID]) and $packet->data[0] !== 0){ //Drop packet
|
|
return false;
|
|
}
|
|
switch($packet->data[0]){
|
|
case 0:
|
|
$this->initChunked($CID, $source, $port);
|
|
return false;
|
|
case 1:
|
|
$this->stopChunked($CID);
|
|
return false;
|
|
case 3:
|
|
$this->ackChunked($CID, $data[1]["id"], $data[1]["index"]);
|
|
return false;
|
|
case 4:
|
|
$this->receiveChunked($CID, $data[1]["id"], $data[1]["index"], $data[1]["count"], $data[1]["data"]);
|
|
return true;
|
|
}
|
|
}
|
|
$this->data[] = array($pid, $packet->data, $buf, $source, $port);
|
|
return true;
|
|
}
|
|
|
|
public function checkChunked($CID){
|
|
$time = microtime(true);
|
|
foreach($this->needCheck as $CID => $packets){
|
|
if($packets[-1] < $time){
|
|
$d = $this->chunked[$CID];
|
|
unset($packets[-1]);
|
|
foreach($packets as $packet){
|
|
$this->writePacket(0x99, $packet, true, $d[1], $d[2], true);
|
|
}
|
|
$this->needCheck[$CID][-1] = $time + 5;
|
|
}
|
|
}
|
|
foreach($this->toChunk as $CID => $packets){
|
|
$d = $this->chunked[$CID];
|
|
$raw = "";
|
|
$MTU = 512;
|
|
foreach($packets as $packet){
|
|
$raw .= $packet;
|
|
if(($len = strlen($packet)) > $MTU){
|
|
$MTU = $len;
|
|
}
|
|
}
|
|
if($MTU > $d[0][2]){
|
|
$this->chunked[$CID][0][2] = $MTU;
|
|
}else{
|
|
$MTU = $d[0][2];
|
|
}
|
|
$raw = str_split(gzdeflate($raw, DEFLATEPACKET_LEVEL), $MTU - 9); // - 1 - 2 - 2 - 2 - 2
|
|
$count = count($raw);
|
|
$messageID = $this->chunked[$CID][0][0]++;
|
|
$this->chunked[$CID][0][0] &= 0xFFFF;
|
|
if(!isset($this->needCheck[$CID])){
|
|
$this->needCheck[$CID] = array();
|
|
}
|
|
$this->needCheck[$CID][$messageID] = array(-1 => $time + 1);
|
|
foreach($raw as $index => $r){
|
|
$p = "\x99\x02".Utils::writeShort($messageID).Utils::writeShort($index).Utils::writeShort($count).Utils::writeShort(strlen($r)).$r;
|
|
$this->needCheck[$CID][$messageID][$index] = $p;
|
|
$this->writePacket(0x99, $p, true, $d[1], $d[2], true);
|
|
}
|
|
unset($this->toChunk[$CID]);
|
|
}
|
|
}
|
|
|
|
public function isChunked($CID){
|
|
return isset($this->chunked[$CID]);
|
|
}
|
|
|
|
private function initChunked($CID, $source, $port){
|
|
console("[DEBUG] Starting DEFLATEPacket for $source:$port", true, true, 2);
|
|
$this->chunked[$CID] = array(
|
|
0 => array(0, 0, 0), //index, sent/received; MTU
|
|
1 => $source,
|
|
2 => $port,
|
|
3 => array(), //Received packets
|
|
);
|
|
$this->writePacket(0x99, array(
|
|
0 => 0, //start
|
|
), false, $source, $port, true);
|
|
}
|
|
|
|
public function stopChunked($CID){
|
|
if(!isset($this->chunked[$CID])){
|
|
return false;
|
|
}
|
|
$this->writePacket(0x99, array(
|
|
0 => 1, //stop
|
|
), false, $this->chunked[$CID][1], $this->chunked[$CID][2], true);
|
|
console("[DEBUG] Stopping DEFLATEPacket for ".$this->chunked[$CID][1].":".$this->chunked[$CID][2], true, true, 2);
|
|
$this->chunked[$CID][3] = null;
|
|
$this->chunked[$CID][4] = null;
|
|
unset($this->chunked[$CID]);
|
|
unset($this->toChunk[$CID]);
|
|
unset($this->needCheck[$CID]);
|
|
}
|
|
|
|
private function ackChunked($CID, $ID, $index){
|
|
unset($this->needCheck[$CID][$ID][$index]);
|
|
if(count($this->needCheck[$CID][$ID]) <= 1){
|
|
unset($this->needCheck[$CID][$ID]);
|
|
}
|
|
}
|
|
|
|
private function receiveChunked($CID, $ID, $index, $count, $data){
|
|
if(!isset($this->chunked[$CID][3][$ID])){
|
|
$this->chunked[$CID][3][$ID] = array();
|
|
}
|
|
$this->chunked[$CID][3][$ID][$index] = $data;
|
|
|
|
if(count($this->chunked[$CID][3][$ID]) === $count){
|
|
ksort($this->chunked[$CID][3][$ID]);
|
|
$data = gzinflate(implode($this->chunked[$CID][3][$ID]), 524280);
|
|
unset($this->chunked[$CID][3][$ID]);
|
|
if($data === false or strlen($data) === 0){
|
|
console("[ERROR] Invalid DEFLATEPacket for ".$this->chunked[$CID][1].":".$this->chunked[$CID][2], true, true, 2);
|
|
}
|
|
$offset = 0;
|
|
while(($plen = Utils::readShort(substr($data, $offset, 2), false)) !== 0xFFFF or $offset >= $len){
|
|
$offset += 2;
|
|
$packet = substr($data, $offset, $plen);
|
|
$this->parsePacket($packet, $this->chunked[$CID][1], $this->chunked[$CID][2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
public function popPacket(){
|
|
if(count($this->data) > 0){
|
|
$p = each($this->data);
|
|
unset($this->data[$p[0]]);
|
|
$p = $p[1];
|
|
return array("pid" => $p[0], "data" => $p[1], "raw" => $p[2], "ip" => $p[3], "port" => $p[4]);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function writePacket($pid, $data = array(), $raw = false, $dest = false, $port = false, $force = false){
|
|
$CID = PocketMinecraftServer::clientID($dest, $port);
|
|
if($raw === false){
|
|
$packet = new Packet($pid, $this->getStruct($pid));
|
|
$packet->data = $data;
|
|
@$packet->create();
|
|
if($force === false and $this->isChunked($CID)){
|
|
if(!isset($this->toChunk[$CID])){
|
|
$this->toChunk[$CID] = array();
|
|
}
|
|
$this->toChunk[$CID][] = $packet->raw;
|
|
$write = strlen($packet->raw);
|
|
}else{
|
|
$write = $this->socket->write($packet->raw, $dest, $port);
|
|
$this->bandwidth[1] += $write;
|
|
}
|
|
}else{
|
|
if($force === false and $this->isChunked($CID)){
|
|
if(!isset($this->toChunk[$CID])){
|
|
$this->toChunk[$CID] = array();
|
|
}
|
|
$this->toChunk[$CID][] = $data;
|
|
$write = strlen($data);
|
|
}else{
|
|
$write = $this->socket->write($data, $dest, $port);
|
|
$this->bandwidth[1] += $write;
|
|
}
|
|
}
|
|
return $write;
|
|
}
|
|
|
|
}
|
|
|
|
?>
|