package com.gmail.zahusek.tinyprotocolapi;
import com.gmail.zahusek.tinyprotocolapi.TinyProtocolAPI;
import com.gmail.zahusek.tinyprotocolapi.asm.reflection.ClassAccess;
import com.gmail.zahusek.tinyprotocolapi.listener.PacketEvent;
import com.gmail.zahusek.tinyprotocolapi.listener.PacketHandlerList;
import com.gmail.zahusek.tinyprotocolapi.listener.RegisteredPacket;
import com.gmail.zahusek.tinyprotocolapi.packet.Packet;
import com.google.common.collect.MapMaker;
import io.netty.channel.*;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
import static com.google.common.collect.Lists.newArrayList;
import static org.bukkit.plugin.java.JavaPlugin.getPlugin;
public class TinyProtocol implements Listener
{
private final ClassAccess CRAFTPLAYER = new ClassAccess(
"{obc}.entity.CraftPlayer");
private final ClassAccess ENTITYPLAYER = new ClassAccess(
"{nms}.EntityPlayer");
private final ClassAccess SERVERCONNECTION = new ClassAccess(
"{nms}.ServerConnection");
private final ClassAccess PLAYERCONNECTION = new ClassAccess(
"{nms}.PlayerConnection");
private final ClassAccess NETWORKMANAGER = new ClassAccess(
"{nms}.NetworkManager");
private final ClassAccess CRAFTSERVER = new ClassAccess(
"{obc}.CraftServer");
private final ClassAccess MINECRAFTSERVER = new ClassAccess(
"{nms}.MinecraftServer");
final TinyProtocolAPI plugin = getPlugin(TinyProtocolAPI.class);
private Map<UUID, Channel> channelLookup = new MapMaker().weakValues()
.makeMap();
private List<Channel> serverChannels = newArrayList();
private List<Object> networkManagers;
private ChannelInboundHandlerAdapter serverChannelHandler;
private ChannelInitializer<Channel> beginInitProtocol;
private ChannelInitializer<Channel> endInitProtocol;
private final String handler = "TinyProtocol";
public TinyProtocol() {
try {
registerChannelHandler();
registerPlayers();
} catch (IllegalArgumentException ex) {
plugin.log("&e[TinyProtocol] Delaying server channel injection due to late bind.");
new BukkitRunnable() {
@Override
public void run() {
registerChannelHandler();
registerPlayers();
plugin.log("&e[TinyProtocol] Late bind injection successful.");
}
}.runTask(plugin);
}
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
private void createServerChannelHandler() {
endInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel a) throws Exception {
try {
synchronized (networkManagers) {
final ChannelPipeline b = a.pipeline();
if (b.get(handler) == null)
b.addBefore("packet_handler", handler,new PacketInterceptor());
injectChannelInternal(a);
}
} catch (Exception e) {
plugin.log("&c[TinyProtocol] Cannot inject incomming channel " + a);
}
}
};
beginInitProtocol = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(endInitProtocol);
}
};
serverChannelHandler = new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
final Channel channel = (Channel) msg;
channel.pipeline().addFirst(beginInitProtocol);
ctx.fireChannelRead(msg);
}
};
}
@SuppressWarnings("unchecked")
private void registerChannelHandler() {
Object a = CRAFTSERVER.get(plugin.getServer(), MINECRAFTSERVER.getUsedClass(),
0);
Object b = MINECRAFTSERVER.get(a, SERVERCONNECTION.getUsedClass(), 0);
networkManagers = (List<Object>) SERVERCONNECTION.invoke(null, List.class, new Class[] {SERVERCONNECTION.getUsedClass()}, b);
createServerChannelHandler();
List<ChannelFuture> c = SERVERCONNECTION.get(b, List.class, 0);
for (ChannelFuture d : c) {
Channel e = d.channel();
serverChannels.add(e);
e.pipeline().addFirst(serverChannelHandler);
}
}
private void unregisterChannelHandler() {
if (serverChannelHandler == null)
return;
serverChannels.stream().forEach((serverChannel) -> {
final ChannelPipeline pipeline = serverChannel.pipeline();
serverChannel.eventLoop().execute(() -> {
try {
pipeline.remove(serverChannelHandler);
} catch (NoSuchElementException e) {
}
});
});
}
private void registerPlayers() {
plugin.getServer().getOnlinePlayers().stream().forEach((player) -> {
injectPlayer(player);
});
}
@Deprecated
public void sendAbstractPacket(Player player, Collection<Packet> packets) {
sendAbstractPacket(player, packets.toArray(new Packet[packets.size()]));
}
@Deprecated
public void sendPacket(Player player, Collection<Object> packets) {
ChannelPipeline a = getChannel(player).pipeline();
for (Object b : packets)
a.writeAndFlush(b);
}
public void sendAbstractPacket(Player player, Packet... packets) {
sendAbstractPacket(getChannel(player), packets);
}
public void sendAbstractPacket(Channel channel, Packet... packets) {
int a = packets.length;
Object[] b = new Object[a];
for(int i = 0; i < a; i++)
b[i] = packets[i].getHandle();
sendPacket(channel, b);
}
public void sendPacket(Player player, Object... packets) {
sendPacket(getChannel(player), packets);
}
public void sendPacket(Channel channel, Object... packets) {
ChannelPipeline a = channel.pipeline();
for (Object b : packets) a.write(b);
a.flush();
}
@Deprecated
public void receiveAbstractPacket(Player player, Collection<Packet> packets) {
receiveAbstractPacket(player, packets.toArray(new Packet[packets.size()]));
}
@Deprecated
public void receivePacket(Player player, Collection<Object> packets)
{
ChannelHandlerContext a = getChannel(player).pipeline().context("encoder");
for (Object b : packets) a.fireChannelRead(b);
}
public void receiveAbstractPacket(Player player, Packet... packets) {
receiveAbstractPacket(getChannel(player), packets);
}
public void receiveAbstractPacket(Channel channel, Packet... packets) {
int a = packets.length;
Object[] b = new Object[a];
for(int i = 0; i < a; i++)
b[i] = packets[i].getHandle();
receivePacket(channel, b);
}
public void receivePacket(Player player, Object... packets)
{receivePacket(getChannel(player), packets);}
public void receivePacket(Channel channel, Object... packets) {
ChannelHandlerContext a = channel.pipeline().context("encoder");
for (Object b : packets) a.fireChannelRead(b);
}
void injectPlayer(Player a) {
Object b = CRAFTPLAYER.invoke(a, "getHandle");
Object c = ENTITYPLAYER.get(b, "playerConnection");
Object d = PLAYERCONNECTION.get(c, "networkManager");
Channel e = NETWORKMANAGER.get(d, "channel");
ChannelPipeline f = e.pipeline();
if (f.get(handler) == null)
f.addBefore("packet_handler", handler, new PacketInterceptor());
channelLookup.put(a.getUniqueId(), e);
injectChannelInternal(getChannel(a)).player = a;
}
PacketInterceptor injectChannelInternal(Channel channel) {
return (PacketInterceptor) channel.pipeline().get(handler);
}
Channel getChannel(Player a) {
return channelLookup.get(a.getUniqueId());
}
void uninjectChannel(Channel channel) {
channel.eventLoop().execute(() -> {
channel.pipeline().remove(handler);
});
}
void close() {
plugin.getServer().getOnlinePlayers().stream().forEach((player) -> {
uninjectChannel(getChannel(player));
});
unregisterChannelHandler();
}
@EventHandler(priority = EventPriority.LOWEST)
void join(PlayerJoinEvent e) {
injectPlayer(e.getPlayer());
}
class PacketInterceptor extends ChannelDuplexHandler {
public volatile Player player;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
for (RegisteredPacket listener : PacketHandlerList.getAllRegisteredPacketListeners())
{
if (!listener.getType().name().equals(msg.getClass().getSimpleName())) continue;
PacketEvent packet = listener.getAccessor().newInstance(0, player, ctx.channel(), msg);
try
{
listener.callEvent(packet);
}
catch(Exception e)
{
plugin.log("&c[TinyProtocol] Problem when overwrite object - &echannel read&c.");
e.printStackTrace();
return;
}
if (packet.isCancelled() || msg == null)
return;
}
super.channelRead(ctx, msg);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) throws Exception {
for (RegisteredPacket listener : PacketHandlerList.getAllRegisteredPacketListeners())
{
if (!listener.getType().name().equals(msg.getClass().getSimpleName())) continue;
PacketEvent packet = listener.getAccessor().newInstance(0, player, ctx.channel(), msg);
try
{
listener.callEvent(packet);
}
catch(Exception e)
{
plugin.log("&c[TinyProtocol] Problem when overwrite object - &echannel write&c.");
e.printStackTrace();
return;
}
if (packet.isCancelled() || msg == null)
return;
}
super.write(ctx, msg, promise);
}
}
}