From 489c72418dc3a74a5122809ab5198e174be2a075 Mon Sep 17 00:00:00 2001 From: Dan Mulloy Date: Sat, 3 Jan 2015 23:18:43 -0500 Subject: [PATCH] Add initial support for wire format packets --- .../com/comphenix/protocol/PacketStream.java | 33 +++++++++---- .../injector/DelayedPacketManager.java | 44 ++++++++++++++--- .../injector/PacketFilterManager.java | 19 ++++++++ .../injector/netty/ChannelInjector.java | 16 ++++++- .../injector/netty/NettyProtocolInjector.java | 10 ++++ .../protocol/injector/netty/WirePacket.java | 42 +++++++++++++++++ .../player/PlayerInjectionHandler.java | 10 ++-- .../player/ProxyPlayerInjectionHandler.java | 47 +++++++++++-------- .../injector/spigot/DummyPlayerHandler.java | 7 +++ 9 files changed, 190 insertions(+), 38 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketStream.java b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketStream.java index 6ffd3710..5df88d6d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketStream.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketStream.java @@ -2,16 +2,16 @@ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * Copyright (C) 2012 Kristian S. Stangeland * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ @@ -24,6 +24,7 @@ import org.bukkit.entity.Player; import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.injector.netty.WirePacket; /** * Represents a object capable of sending or receiving packets. @@ -37,7 +38,7 @@ public interface PacketStream { * @param packet - packet to send. * @throws InvocationTargetException - if an error occured when sending the packet. */ - public void sendServerPacket(Player receiver, PacketContainer packet) + public void sendServerPacket(Player receiver, PacketContainer packet) throws InvocationTargetException; /** @@ -61,6 +62,22 @@ public interface PacketStream { public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException; + /** + * Send a wire packet to the given player. + * @param receiver - the receiver. + * @param id - packet id. + * @param bytes - packet bytes. + * @throws InvocationTargetException if an error occured when sending the packet. + */ + public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException; + + /** + * Send a wire packet to the given player. + * @param receiver - the receiver. + * @param packet - packet to send. + * @throws InvocationTargetException if an error occured when sending the packet. + */ + public void sendWirePacket(Player receiver, WirePacket packet) throws InvocationTargetException; /** * Simulate recieving a certain packet from a given player. @@ -69,7 +86,7 @@ public interface PacketStream { * @throws InvocationTargetException If the reflection machinery failed. * @throws IllegalAccessException If the underlying method caused an error. */ - public void recieveClientPacket(Player sender, PacketContainer packet) + public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException; /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java index 263bbaea..d96fbcac 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java @@ -27,6 +27,7 @@ import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.comphenix.protocol.injector.netty.WirePacket; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.utility.MinecraftVersion; @@ -45,6 +46,7 @@ import com.google.common.collect.Sets; public class DelayedPacketManager implements ProtocolManager, InternalManager { // Registering packet IDs that are not supported public static final ReportType REPORT_CANNOT_SEND_QUEUED_PACKET = new ReportType("Cannot send queued packet %s."); + public static final ReportType REPORT_CANNOT_SEND_QUEUED_WIRE_PACKET = new ReportType("Cannot send queued wire packet %s."); public static final ReportType REPORT_CANNOT_REGISTER_QUEUED_LISTENER = new ReportType("Cannot register queued listener %s."); private volatile InternalManager delegate; @@ -104,7 +106,7 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { /** * Update the delegate to the underlying manager. *

- * This will prompt this packet manager to immediately transmit and + * This will prompt this packet manager to immediately transmit and * register all queued packets an listeners. * @param delegate - delegate to the new manager. */ @@ -130,7 +132,7 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { delegate.addPacketListener(listener); } catch (IllegalArgumentException e) { // Inform about this plugin error - reporter.reportWarning(this, + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_REGISTER_QUEUED_LISTENER). callerParam(delegate).messageParam(listener).error(e)); } @@ -150,7 +152,7 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { } } - private Runnable queuedAddPacket(final ConnectionSide side, final Player player, final PacketContainer packet, + private Runnable queuedAddPacket(final ConnectionSide side, final Player player, final PacketContainer packet, final NetworkMarker marker, final boolean filtered) { return new Runnable() { @@ -170,10 +172,10 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { } } catch (Exception e) { // Inform about this plugin error - reporter.reportWarning(this, + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_PACKET). callerParam(delegate).messageParam(packet).error(e)); - } + } } }; } @@ -207,6 +209,36 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { } } + @Override + public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException { + WirePacket packet = new WirePacket(id, bytes); + sendWirePacket(receiver, packet); + } + + @Override + public void sendWirePacket(final Player receiver, final WirePacket packet) throws InvocationTargetException { + if (delegate != null) { + delegate.sendWirePacket(receiver, packet); + } else { + queuedActions.add(new Runnable() { + + @Override + public void run() { + try { + delegate.sendWirePacket(receiver, packet); + } catch (Throwable ex) { + // Inform about this plugin error + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_WIRE_PACKET) + .callerParam(delegate) + .messageParam(packet) + .error(ex)); + } + } + + }); + } + } + @Override public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException { recieveClientPacket(sender, packet, null, true); @@ -405,7 +437,7 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { @Override public void updateEntity(Entity entity, List observers) throws FieldAccessException { - if (delegate != null) + if (delegate != null) delegate.updateEntity(entity, observers); else EntityUtilities.updateEntity(entity, observers); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index 662c6744..a2d26d02 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -17,6 +17,8 @@ package com.comphenix.protocol.injector; +import io.netty.channel.Channel; + import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Collections; @@ -67,6 +69,7 @@ import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.netty.NettyProtocolInjector; +import com.comphenix.protocol.injector.netty.WirePacket; import com.comphenix.protocol.injector.packet.InterceptWritePacket; import com.comphenix.protocol.injector.packet.PacketInjector; import com.comphenix.protocol.injector.packet.PacketInjectorBuilder; @@ -833,6 +836,22 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok playerInjection.sendServerPacket(receiver, packet, marker, filters); } + @Override + public void sendWirePacket(Player receiver, int id, byte[] bytes) throws InvocationTargetException { + WirePacket packet = new WirePacket(id, bytes); + sendWirePacket(receiver, packet); + } + + @Override + public void sendWirePacket(Player receiver, WirePacket packet) throws InvocationTargetException { + Channel channel = playerInjection.getChannel(receiver); + if (channel == null) { + throw new InvocationTargetException(new NullPointerException(), "Failed to obtain channel for " + receiver.getName()); + } + + channel.writeAndFlush(packet); + } + @Override public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException { recieveClientPacket(sender, packet, null, true); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java index f810eaf8..84f37e6a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/ChannelInjector.java @@ -223,7 +223,12 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { protocolEncoder = new MessageToByteEncoder() { @Override protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { - ChannelInjector.this.encode(ctx, packet, output); + if (packet instanceof WirePacket) { + // Special case for wire format + ChannelInjector.this.encodeWirePacket(ctx, (WirePacket) packet, output); + } else { + ChannelInjector.this.encode(ctx, packet, output); + } } @Override @@ -381,6 +386,11 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { super.exceptionCaught(ctx, cause); } + protected void encodeWirePacket(ChannelHandlerContext ctx, WirePacket packet, ByteBuf output) throws Exception { + packet.writeId(output); + packet.writeBytes(output); + } + /** * Encode a packet to a byte buffer, taking over for the standard Minecraft encoder. * @param ctx - the current context. @@ -875,4 +885,8 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { return injector; } } + + public Channel getChannel() { + return originalChannel; + } } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java index 3979895e..82e9508b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/NettyProtocolInjector.java @@ -373,6 +373,16 @@ public class NettyProtocolInjector implements ChannelListener { public void handleDisconnect(Player player) { injectionFactory.fromPlayer(player, listener).close(); } + + @Override + public Channel getChannel(Player player) { + Injector injector = injectionFactory.fromPlayer(player, listener); + if (injector instanceof ChannelInjector) { + return ((ChannelInjector) injector).getChannel(); + } + + return null; + } }; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java new file mode 100644 index 00000000..e73acbac --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/WirePacket.java @@ -0,0 +1,42 @@ +/** + * (c) 2015 dmulloy2 + */ +package com.comphenix.protocol.injector.netty; + +import io.netty.buffer.ByteBuf; + +/** + * @author dmulloy2 + */ + +public class WirePacket { + private final int id; + private final byte[] bytes; + + public WirePacket(int id, byte[] bytes) { + this.id = id; + this.bytes = bytes; + } + + public int getId() { + return id; + } + + public byte[] getBytes() { + return bytes; + } + + public void writeId(ByteBuf output) { + int i = id; + while ((i & -128) != 0) { + output.writeByte(i & 127 | 128); + i >>>= 7; + } + + output.writeByte(i); + } + + public void writeBytes(ByteBuf output) { + output.writeBytes(bytes); + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index eb8f5076..a6a33175 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -1,5 +1,7 @@ package com.comphenix.protocol.injector.player; +import io.netty.channel.Channel; + import java.io.DataInputStream; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; @@ -76,7 +78,7 @@ public interface PlayerInjectionHandler { public abstract void addPacketHandler(PacketType type, Set options); /** - * Remove an underlying packet handler of this type. + * Remove an underlying packet handler of this type. * @param type - packet type to unregister. */ public abstract void removePacketHandler(PacketType type); @@ -127,7 +129,7 @@ public interface PlayerInjectionHandler { * Send the given packet to the given receiver. * @param receiver - the player receiver. * @param packet - the packet to send. - * @param marker + * @param marker * @param filters - whether or not to invoke the packet filters. * @throws InvocationTargetException If an error occurred during sending. */ @@ -159,7 +161,7 @@ public interface PlayerInjectionHandler { /** * Determine if a listener is valid or not. *

- * If not, a warning will be printed to the console. + * If not, a warning will be printed to the console. * @param listener - listener to check. */ public abstract void checkListener(PacketListener listener); @@ -198,4 +200,6 @@ public interface PlayerInjectionHandler { * @return TRUE if we do, FALSE otherwise. */ public abstract boolean hasMainThreadListener(PacketType type); + + public abstract Channel getChannel(Player player); } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 474e0b91..4417bafe 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -2,21 +2,23 @@ * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * Copyright (C) 2012 Kristian S. Stangeland * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA */ package com.comphenix.protocol.injector.player; +import io.netty.channel.Channel; + import java.io.DataInputStream; import java.io.InputStream; import java.lang.ref.WeakReference; @@ -94,7 +96,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { private WeakReference lastSuccessfulHook; // Dummy injection - private ConcurrentMap dummyInjectors = + private ConcurrentMap dummyInjectors = SafeCacheBuilder.newBuilder(). expireAfterWrite(30, TimeUnit.SECONDS). build(BlockingHashMap.newInvalidCacheLoader()); @@ -128,7 +130,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { private Predicate injectionFilter; public ProxyPlayerInjectionHandler( - ErrorReporter reporter, Predicate injectionFilter, + ErrorReporter reporter, Predicate injectionFilter, ListenerInvoker invoker, Set packetListeners, Server server, MinecraftVersion version) { this.reporter = reporter; @@ -175,7 +177,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { return loginPlayerHook; case PLAYING: return playingPlayerHook; - default: + default: throw new IllegalArgumentException("Cannot retrieve injection hook for both phases at the same time."); } } @@ -230,9 +232,9 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { private PlayerInjector getHookInstance(Player player, PlayerInjectHooks hook) throws IllegalAccessException { // Construct the correct player hook switch (hook) { - case NETWORK_HANDLER_FIELDS: + case NETWORK_HANDLER_FIELDS: return new NetworkFieldInjector(reporter, player, invoker, sendingFilters); - case NETWORK_MANAGER_OBJECT: + case NETWORK_MANAGER_OBJECT: return new NetworkObjectInjector(reporter, player, invoker, sendingFilters); case NETWORK_SERVER_OBJECT: return new NetworkServerInjector(reporter, player, invoker, sendingFilters, serverInjection); @@ -373,8 +375,8 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { } catch (Exception e) { // Mark this injection attempt as a failure - reporter.reportDetailed(this, - Report.newBuilder(REPORT_PLAYER_HOOK_FAILED).messageParam(tempHook).callerParam(player, injectionPoint, phase).error(e) + reporter.reportDetailed(this, + Report.newBuilder(REPORT_PLAYER_HOOK_FAILED).messageParam(tempHook).callerParam(player, injectionPoint, phase).error(e) ); hookFailed = true; } @@ -401,7 +403,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { // Update values if (injector != null) lastSuccessfulHook = new WeakReference(injector); - if (permanentHook != getPlayerHook(phase)) + if (permanentHook != getPlayerHook(phase)) setPlayerHook(phase, tempHook); // Save injector @@ -458,7 +460,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { if (injector != null) { injector.setUpdatedPlayer(player); } else { - inputStreamLookup.setSocketInjector(player.getAddress(), + inputStreamLookup.setSocketInjector(player.getAddress(), new BukkitSocketInjector(player)); } } @@ -547,7 +549,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { injector.sendServerPacket(packet.getHandle(), marker, filters); } else { throw new PlayerLoggedOutException(String.format( - "Unable to send packet %s (%s): Player %s has logged out.", + "Unable to send packet %s (%s): Player %s has logged out.", packet.getType(), packet, receiver )); } @@ -569,7 +571,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { injector.processPacket(mcPacket); else throw new PlayerLoggedOutException(String.format( - "Unable to receieve packet %s. Player %s has logged out.", + "Unable to receieve packet %s. Player %s has logged out.", mcPacket, player )); } @@ -691,7 +693,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { /** * Determine if a listener is valid or not. *

- * If not, a warning will be printed to the console. + * If not, a warning will be printed to the console. * @param listener - listener to check. */ @Override @@ -703,8 +705,8 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { // We won't prevent the listener, as it may still have valid packets if (result != null) { - reporter.reportWarning(this, - Report.newBuilder(REPORT_UNSUPPPORTED_LISTENER).messageParam(PacketAdapter.getPluginName(listener), result) + reporter.reportWarning(this, + Report.newBuilder(REPORT_UNSUPPPORTED_LISTENER).messageParam(PacketAdapter.getPluginName(listener), result) ); // These are illegal @@ -753,4 +755,9 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { playerInjection.clear(); invoker = null; } + + @Override + public Channel getChannel(Player player) { + throw new UnsupportedOperationException(); + } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java index a7f1f372..801fd84e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java @@ -1,5 +1,7 @@ package com.comphenix.protocol.injector.spigot; +import io.netty.channel.Channel; + import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; @@ -82,4 +84,9 @@ class DummyPlayerHandler extends AbstractPlayerHandler { public void updatePlayer(Player player) { // Do nothing } + + @Override + public Channel getChannel(Player player) { + throw new UnsupportedOperationException(); + } }