package com.bergerkiller.bukkit.common.internal.network;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import net.minecraft.server.*;
import net.minecraft.util.io.netty.channel.Channel;
import net.minecraft.util.io.netty.channel.ChannelDuplexHandler;
import net.minecraft.util.io.netty.channel.ChannelHandlerContext;
import net.minecraft.util.io.netty.channel.ChannelPromise;
import net.minecraft.util.io.netty.util.concurrent.GenericFutureListener;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.bergerkiller.bukkit.common.conversion.Conversion;
import com.bergerkiller.bukkit.common.internal.CommonPlugin;
import com.bergerkiller.bukkit.common.protocol.PacketType;
import com.bergerkiller.bukkit.common.reflection.ClassTemplate;
import com.bergerkiller.bukkit.common.reflection.NMSClassTemplate;
import com.bergerkiller.bukkit.common.reflection.SafeConstructor;
import com.bergerkiller.bukkit.common.reflection.classes.EntityPlayerRef;
import com.bergerkiller.bukkit.common.reflection.classes.NetworkManagerRef;
import com.bergerkiller.bukkit.common.reflection.classes.PlayerConnectionRef;
import com.bergerkiller.bukkit.common.utils.CommonUtil;
/**
* Fallback packet handler which uses an injected PlayerConnection replacement
*/
public class CommonPacketHandler extends PacketHandlerHooked {
/**
* Known plugins that malfunction with the default packet handler
*/
private static final String[] incompatibilities = {"Spout"};
/*
* Used for silent packet sending
*/
private Object[] emptyGenericFutureListener;
private SafeConstructor<?> queuedPacketConstructor;
@Override
public String getName() {
return "a PlayerConnection hook";
}
@Override
public boolean onEnable() {
if (!super.onEnable()) {
return false;
}
for (String incompatibility : incompatibilities) {
if (CommonUtil.isPluginInDirectory(incompatibility)) {
// Fail!
failPacketListener(incompatibility);
return false;
}
}
// Initialize queued packet logic for silent sending
ClassTemplate<?> queuedPacketTemplate = NMSClassTemplate.create("QueuedPacket");
this.emptyGenericFutureListener = new GenericFutureListener[0];
this.queuedPacketConstructor = queuedPacketTemplate.getConstructor(PacketType.DEFAULT.getType(), GenericFutureListener[].class);
if (!this.queuedPacketConstructor.isValid()) {
return false;
}
// Bind and done
for(Player player : Bukkit.getOnlinePlayers()) {
CommonChannelListener.bind(player);
}
return true;
}
@Override
public boolean onDisable() {
// Unbind all hooks - but don't do a check since we are disabling
// Can not create new tasks at that point
for(Player player : Bukkit.getOnlinePlayers()) {
CommonChannelListener.unbind(player);
}
return true;
}
@Override
public void onPlayerJoin(Player player) {
CommonChannelListener.bind(player);
}
@Override
public void sendSilentPacket(Player player, Object packet) {
// Instead of using sendPacket, we sneakily insert the packet into the queue
Object networkManager = EntityPlayerRef.getNetworkManager(player);
Queue<Object> pollQueue = NetworkManagerRef.highPriorityQueue.get(networkManager);
pollQueue.add(this.queuedPacketConstructor.newInstance(packet, this.emptyGenericFutureListener));
}
@Override
public long getPendingBytes(Player player) {
return calculatePendingBytes(player);
}
private static void failPacketListener(String pluginName) {
showFailureMessage("a plugin conflict, namely " + pluginName);
}
private static void showFailureMessage(String causeName) {
CommonPlugin.LOGGER_NETWORK.log(Level.SEVERE, "Failed to hook up a PlayerConnection to listen for received and sent packets");
CommonPlugin.LOGGER_NETWORK.log(Level.SEVERE, "This was caused by " + causeName);
CommonPlugin.LOGGER_NETWORK.log(Level.SEVERE, "Install ProtocolLib to restore protocol compatibility");
CommonPlugin.LOGGER_NETWORK.log(Level.SEVERE, "Dev-bukkit: http://dev.bukkit.org/server-mods/protocollib/");
}
public static class CommonChannelListener extends ChannelDuplexHandler {
public static void bind(Player player) {
Object entityPlayer = Conversion.toEntityHandle.convert(player);
Object playerConnection = EntityPlayerRef.playerConnection.get(entityPlayer);
Object networkManager = PlayerConnectionRef.networkManager.get(playerConnection);
Channel channel = NetworkManagerRef.channel.get(networkManager);
channel.pipeline().addBefore("packet_handler", "bkcommonlib", new CommonChannelListener(player));
}
public static void unbind(Player player) {
Object entityPlayer = Conversion.toEntityHandle.convert(player);
Object playerConnection = EntityPlayerRef.playerConnection.get(entityPlayer);
Object networkManager = PlayerConnectionRef.networkManager.get(playerConnection);
final Channel channel = NetworkManagerRef.channel.get(networkManager);
channel.eventLoop().submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
channel.pipeline().remove("bkcommonlib");
return null;
}
});
}
private final PacketHandlerHooked handler;
private final Player player;
public CommonChannelListener(Player player) {
this.handler = (PacketHandlerHooked) CommonPlugin.getInstance().getPacketHandler();
this.player = player;
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
Packet packet = (Packet) msg;
if(handler.handlePacketSend(player, packet, false)) {
super.write(ctx, msg, promise);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Packet packet = (Packet) msg;
if(handler.handlePacketReceive(player, packet, false)) {
super.channelRead(ctx, msg);
}
}
}
}