From f7c4fd4ec9cffd51074f012ea6e13c1058c38692 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 25 Apr 2014 02:55:17 +0200 Subject: [PATCH] Added support for "post packet events". NetworkMarker now contains a list of post listeners that are invoked (in no particular order) when a packet has been serialize and sent to a player, OR, when it has been enqueued for processing by the server. This works for both 1.7.2+ (Netty) and 1.6.4 and earlier, though the 1.6.4 version has a good deal more overhead. --- .../protocol/events/NetworkMarker.java | 54 +++++++- .../protocol/events/PacketEvent.java | 52 +++++++- .../protocol/events/PacketListener.java | 2 +- .../protocol/events/PacketOutputAdapter.java | 1 - .../protocol/events/PacketPostAdapter.java | 22 ++++ .../protocol/events/PacketPostListener.java | 23 ++++ .../protocol/injector/NetworkProcessor.java | 83 ++++++++++++ .../injector/PacketFilterManager.java | 4 +- .../injector/netty/ChannelInjector.java | 118 +++++++++++++++--- .../injector/packet/ReadPacketModifier.java | 9 ++ .../injector/packet/WritePacketModifier.java | 37 +++--- .../injector/player/PlayerInjector.java | 2 +- 12 files changed, 356 insertions(+), 51 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketPostAdapter.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketPostListener.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java index dcf721f0..193ddc8c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java @@ -7,15 +7,18 @@ import java.nio.ByteBuffer; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.PriorityQueue; import javax.annotation.Nonnull; import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.utility.ByteBufferInputStream; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.StreamSerializer; import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; import com.google.common.primitives.Ints; /** @@ -48,6 +51,8 @@ public abstract class NetworkMarker { // Custom network handler private PriorityQueue outputHandlers; + // Sent listeners + private List postListeners; // The input buffer private ByteBuffer inputBuffer; @@ -151,7 +156,6 @@ public abstract class NetworkMarker { *

* The data is exactly the same as in {@link #getInputBuffer()}. * @see #getInputBuffer() - * @param excludeHeader - whether or not to exclude the packet ID header. * @return The incoming serialized packet data as a stream, or NULL if the packet was transmitted locally. */ public DataInputStream getInputStream() { @@ -252,6 +256,45 @@ public abstract class NetworkMarker { } } + /** + * Add a listener that is invoked after a packet has been successfully sent to the client, or received + * by the server. + *

+ * Received packets are not guarenteed to have been fully processed, but packets passed + * to {@link ProtocolManager#recieveClientPacket(Player, PacketContainer)} will be processed after the + * current packet event. + *

+ * Note that post listeners will be executed asynchronously off the main thread. They are not executed + * in any defined order. + * @param listener - the listener that will be invoked. + * @return TRUE if it was added. + */ + public boolean addPostListener(PacketPostListener listener) { + if (postListeners == null) + postListeners = Lists.newArrayList(); + return postListeners.add(listener); + } + + /** + * Remove the first instance of the given listener. + * @param listener - listener to remove. + * @return TRUE if it was removed, FALSE otherwise. + */ + public boolean removePostListener(PacketPostListener listener) { + if (postListeners != null) { + return postListeners.remove(listener); + } + return false; + } + + /** + * Retrieve an immutable view of all the listeners that will be invoked once the packet has been sent or received. + * @return Every post packet listener. Never NULL. + */ + public List getPostListeners() { + return postListeners != null ? Collections.unmodifiableList(postListeners) : Collections.emptyList(); + } + /** * Ensure that the packet event is server side. */ @@ -304,6 +347,15 @@ public abstract class NetworkMarker { return marker != null && !marker.getOutputHandlers().isEmpty(); } + /** + * Determine if the given marker has any post listeners. + * @param marker - the marker to check. + * @return TRUE if it does, FALSE otherwise. + */ + public static boolean hasPostListeners(NetworkMarker marker) { + return marker != null && !marker.getPostListeners().isEmpty(); + } + /** * Retrieve the byte buffer stored in the given marker. * @param marker - the marker. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java index 6a0e8530..76c4cab0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java @@ -70,6 +70,7 @@ public class PacketEvent extends EventObject implements Cancellable { // Whether or not a packet event is read only private boolean readOnly; + private boolean filtered; /** * Use the static constructors to create instances of this event. @@ -77,18 +78,20 @@ public class PacketEvent extends EventObject implements Cancellable { */ public PacketEvent(Object source) { super(source); + this.filtered = true; } private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) { - this(source, packet, null, player, serverPacket); + this(source, packet, null, player, serverPacket, true); } - private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket) { + private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket, boolean filtered) { super(source); this.packet = packet; this.playerReference = new WeakReference(player); this.networkMarker = marker; this.serverPacket = serverPacket; + this.filtered = filtered; } private PacketEvent(PacketEvent origial, AsyncMarker asyncMarker) { @@ -97,6 +100,7 @@ public class PacketEvent extends EventObject implements Cancellable { this.playerReference = origial.playerReference; this.cancel = origial.cancel; this.serverPacket = origial.serverPacket; + this.filtered = origial.filtered; this.asyncMarker = asyncMarker; this.asynchronous = true; } @@ -121,7 +125,22 @@ public class PacketEvent extends EventObject implements Cancellable { * @return The event. */ public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) { - return new PacketEvent(source, packet, marker, client, false); + return new PacketEvent(source, packet, marker, client, false, true); + } + + /** + * Creates an event representing a client packet transmission. + *

+ * If filtered is FALSE, then this event is only processed by packet monitors. + * @param source - the event source. + * @param packet - the packet. + * @param marker - the network marker. + * @param client - the client that sent the packet. + * @param filtered - whether or not this packet event is processed by every packet listener. + * @return The event. + */ + public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client, boolean filtered) { + return new PacketEvent(source, packet, marker, client, false, filtered); } /** @@ -144,7 +163,22 @@ public class PacketEvent extends EventObject implements Cancellable { * @return The event. */ public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) { - return new PacketEvent(source, packet, marker, recipient, true); + return new PacketEvent(source, packet, marker, recipient, true, true); + } + + /** + * Creates an event representing a server packet transmission. + *

+ * If filtered is FALSE, then this event is only processed by packet monitors. + * @param source - the event source. + * @param packet - the packet. + * @param marker - the network marker. + * @param recipient - the client that will receieve the packet. + * @param filtered - whether or not this packet event is processed by every packet listener. + * @return The event. + */ + public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient, boolean filtered) { + return new PacketEvent(source, packet, marker, recipient, true, filtered); } /** @@ -286,6 +320,16 @@ public class PacketEvent extends EventObject implements Cancellable { return playerReference.get(); } + /** + * Determine if this packet is filtered by every packet listener. + *

+ * If not, it will only be intercepted by monitor packets. + * @return TRUE if it is, FALSE otherwise. + */ + public boolean isFiltered() { + return filtered; + } + /** * Whether or not this packet was created by the server. *

diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java index 53c0c427..5d1e4143 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketListener.java @@ -20,7 +20,7 @@ package com.comphenix.protocol.events; import org.bukkit.plugin.Plugin; /** - * Represents a listener that receives notifications when packets are sent or received. + * Represents a listener that receives notifications when packets are sending or being received. *

* Use {@link PacketAdapter} for a simple wrapper around this interface. * @author Kristian diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketOutputAdapter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketOutputAdapter.java index 4cf5d417..95efdf38 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketOutputAdapter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketOutputAdapter.java @@ -4,7 +4,6 @@ import org.bukkit.plugin.Plugin; /** * Represents an adapter version of the output handler interface. - * * @author Kristian */ public abstract class PacketOutputAdapter implements PacketOutputHandler { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketPostAdapter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketPostAdapter.java new file mode 100644 index 00000000..2fd6986a --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketPostAdapter.java @@ -0,0 +1,22 @@ +package com.comphenix.protocol.events; + +import org.bukkit.plugin.Plugin; + +import com.google.common.base.Preconditions; + +/** + * Represents an adapter version of a post listener. + * @author Kristian + */ +public abstract class PacketPostAdapter implements PacketPostListener { + private Plugin plugin; + + public PacketPostAdapter(Plugin plugin) { + this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL"); + } + + @Override + public Plugin getPlugin() { + return plugin; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketPostListener.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketPostListener.java new file mode 100644 index 00000000..a9c158d9 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketPostListener.java @@ -0,0 +1,23 @@ +package com.comphenix.protocol.events; + +import org.bukkit.plugin.Plugin; + +/** + * Represents a packet listener that is invoked after a packet has been sent or received. + * @author Kristian + */ +public interface PacketPostListener { + /** + * Retrieve the plugin this listener belongs to. + * @return The assoicated plugin. + */ + public Plugin getPlugin(); + + /** + * Invoked after a packet has been sent or received. + *

+ * Note that this is invoked asynchronously. + * @param event - the packet event. + */ + public void onPostEvent(PacketEvent event); +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java new file mode 100644 index 00000000..b74a2e81 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/NetworkProcessor.java @@ -0,0 +1,83 @@ +package com.comphenix.protocol.injector; + +import java.util.PriorityQueue; + +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.events.NetworkMarker; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketOutputHandler; +import com.comphenix.protocol.events.PacketPostListener; + +/** + * Represents a processor for network markers. + * @author Kristian + */ +public class NetworkProcessor { + private ErrorReporter reporter; + + /** + * Construct a new network processor. + * @param reporter - the reporter. + */ + public NetworkProcessor(ErrorReporter reporter) { + this.reporter = reporter; + } + + /** + * Process the serialized packet byte array with the given network marker. + * @param event - current packet event. + * @param marker - the network marker. + * @param input - the input array. + * @return The output array. + */ + public byte[] processOutput(PacketEvent event, NetworkMarker marker, final byte[] input) { + // Bit of a hack - but we need the performance + PriorityQueue handlers = (PriorityQueue) + marker.getOutputHandlers(); + byte[] output = input; + + // Let each handler prepare the actual output + while (!handlers.isEmpty()) { + PacketOutputHandler handler = handlers.poll(); + + try { + byte[] changed = handler.handle(event, output); + + // Don't break just because a plugin returned NULL + if (changed != null) { + output = changed; + } else { + throw new IllegalStateException("Handler cannot return a NULL array."); + } + } catch (OutOfMemoryError e) { + throw e; + } catch (ThreadDeath e) { + throw e; + } catch (Throwable e) { + reporter.reportMinimal(handler.getPlugin(), "PacketOutputHandler.handle()", e); + } + } + return output; + } + + /** + * Invoke the post listeners, if any. + * @param marker - the network marker, or NULL. + */ + public void invokePostListeners(PacketEvent event, NetworkMarker marker) { + if (NetworkMarker.hasPostListeners(marker)) { + // Invoke every sent listener + for (PacketPostListener listener : marker.getPostListeners()) { + try { + listener.onPostEvent(event); + } catch (OutOfMemoryError e) { + throw e; + } catch (ThreadDeath e) { + throw e; + } catch (Throwable e) { + reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e); + } + } + } + } +} 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 ba61cd2d..e54a923f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -782,7 +782,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Inform the MONITOR packets if (!filters) { - PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver); + PacketEvent event = PacketEvent.fromServer(this, packet, marker, receiver, false); sendingListeners.invokePacketSending( reporter, event, ListenerPriority.MONITOR); @@ -832,7 +832,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Let the monitors know though recievedListeners.invokePacketSending( reporter, - PacketEvent.fromClient(this, packet, marker, sender), + PacketEvent.fromClient(this, packet, marker, sender, false), ListenerPriority.MONITOR); } 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 9f0f412e..b66d9bf6 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 @@ -3,7 +3,10 @@ package com.comphenix.protocol.injector.netty; import java.lang.reflect.InvocationTargetException; import java.net.Socket; import java.net.SocketAddress; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; +import java.util.ListIterator; import java.util.Map.Entry; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; @@ -13,6 +16,9 @@ import net.minecraft.util.io.netty.buffer.ByteBuf; import net.minecraft.util.io.netty.channel.Channel; import net.minecraft.util.io.netty.channel.ChannelHandler; import net.minecraft.util.io.netty.channel.ChannelHandlerContext; +import net.minecraft.util.io.netty.channel.ChannelInboundHandler; +import net.minecraft.util.io.netty.channel.ChannelInboundHandlerAdapter; +import net.minecraft.util.io.netty.channel.ChannelPromise; import net.minecraft.util.io.netty.channel.socket.SocketChannel; import net.minecraft.util.io.netty.handler.codec.ByteToMessageDecoder; import net.minecraft.util.io.netty.handler.codec.MessageToByteEncoder; @@ -31,7 +37,7 @@ import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.events.PacketOutputHandler; +import com.comphenix.protocol.injector.NetworkProcessor; import com.comphenix.protocol.injector.server.SocketInjector; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.VolatileField; @@ -94,6 +100,11 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { */ private PacketEvent currentEvent; + /** + * A packet event that should be processed by the write method. + */ + private PacketEvent finalEvent; + /** * A flag set by the main thread to indiciate that a packet should not be processed. */ @@ -107,12 +118,17 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { private ByteToMessageDecoder vanillaDecoder; private MessageToByteEncoder vanillaEncoder; - // Our extra handler + // Our extra handlers private MessageToByteEncoder protocolEncoder; - + private ChannelInboundHandler finishHandler; + private Deque finishQueue = new ArrayDeque(); + // The channel listener private ChannelListener channelListener; + // Processing network markers + private NetworkProcessor processor; + // Closed private boolean injected; private boolean closed; @@ -131,6 +147,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { this.originalChannel = Preconditions.checkNotNull(channel, "channel cannot be NULL"); this.channelListener = Preconditions.checkNotNull(channelListener, "channelListener cannot be NULL"); this.factory = Preconditions.checkNotNull(factory, "factory cannot be NULL"); + this.processor = new NetworkProcessor(ProtocolLibrary.getErrorReporter()); // Get the channel field this.channelField = new VolatileField( @@ -178,10 +195,27 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { ChannelInjector.this.encode(ctx, packet, output); } + + @Override + public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception { + super.write(ctx, packet, promise); + ChannelInjector.this.finalWrite(ctx, packet, promise); + } + }; + + // Intercept recieved packets + finishHandler = new ChannelInboundHandlerAdapter() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + // Execute context first + ctx.fireChannelRead(msg); + ChannelInjector.this.finishRead(ctx, msg); + } }; // Insert our handlers - note that we effectively replace the vanilla encoder/decoder originalChannel.pipeline().addBefore("decoder", "protocol_lib_decoder", this); + originalChannel.pipeline().addBefore("protocol_lib_decoder", "protocol_lib_finish", finishHandler); originalChannel.pipeline().addAfter("encoder", "protocol_lib_encoder", protocolEncoder); // Intercept all write methods @@ -250,7 +284,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { return true; } } - + /** * Process a given message on the packet listeners. * @param message - the message/packet. @@ -286,10 +320,10 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { * @throws Exception If anything went wrong. */ protected void encode(ChannelHandlerContext ctx, Object packet, ByteBuf output) throws Exception { + NetworkMarker marker = null; + PacketEvent event = currentEvent; + try { - PacketEvent event = currentEvent; - NetworkMarker marker = null; - // Skip every kind of non-filtered packet if (!scheduleProcessPackets.get()) { return; @@ -323,15 +357,16 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { if (packet != null && event != null && NetworkMarker.hasOutputHandlers(marker)) { ByteBuf packetBuffer = ctx.alloc().buffer(); ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, packetBuffer); - byte[] data = getBytes(packetBuffer); - - for (PacketOutputHandler handler : marker.getOutputHandlers()) { - data = handler.handle(event, data); - } + + // Let each handler prepare the actual output + byte[] data = processor.processOutput(event, marker, getBytes(packetBuffer)); // Write the result output.writeBytes(data); packet = null; + + // Sent listeners? + finalEvent = event; return; } } catch (Exception e) { @@ -341,10 +376,29 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { // Attempt to handle the packet nevertheless if (packet != null) { ENCODE_BUFFER.invoke(vanillaEncoder, ctx, packet, output); + finalEvent = event; } } } + /** + * Invoked when a packet has been written to the channel. + * @param ctx - current context. + * @param packet - the packet that has been written. + * @param promise - a promise. + */ + protected void finalWrite(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) { + PacketEvent event = finalEvent; + + if (event != null) { + // Necessary to prevent infinite loops + finalEvent = null; + currentEvent = null; + + processor.invokePostListeners(event, NetworkMarker.getNetworkMarker(event)); + } + } + private void scheduleMainThread(final Object packetCopy) { // Don't use BukkitExecutors for this - it has a bit of overhead Bukkit.getScheduler().scheduleSyncDelayedTask(factory.getPlugin(), new Runnable() { @@ -360,9 +414,12 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { byteBuffer.markReaderIndex(); DECODE_BUFFER.invoke(vanillaDecoder, ctx, byteBuffer, packets); - try { - if (packets.size() > 0) { - Object input = packets.get(0); + try { + // Reset queue + finishQueue.clear(); + + for (ListIterator it = packets.listIterator(); it.hasNext(); ) { + Object input = it.next(); Class packetClass = input.getClass(); NetworkMarker marker = null; @@ -377,10 +434,14 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { // Handle packet changes if (output != null) { - if (output.isCancelled()) - packets.clear(); - else if (output.getPacket().getHandle() != input) - packets.set(0, output.getPacket().getHandle()); + if (output.isCancelled()) { + it.remove(); + continue; + } else if (output.getPacket().getHandle() != input) { + it.set(output.getPacket().getHandle()); + } + + finishQueue.addLast(output); } } } catch (Exception e) { @@ -389,6 +450,24 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { } } + /** + * Invoked after our decoder. + * @param ctx - current context. + * @param msg - the current packet. + */ + protected void finishRead(ChannelHandlerContext ctx, Object msg) { + // Assume same order + PacketEvent event = finishQueue.pollFirst(); + + if (event != null) { + NetworkMarker marker = NetworkMarker.getNetworkMarker(event); + + if (marker != null) { + processor.invokePostListeners(event, marker); + } + } + } + /** * Invoked when we may need to handle the login packet. * @param packetClass - the packet class. @@ -608,6 +687,7 @@ class ChannelInjector extends ByteToMessageDecoder implements Injector { @Override public Object call() throws Exception { originalChannel.pipeline().remove(ChannelInjector.this); + originalChannel.pipeline().remove(finishHandler); originalChannel.pipeline().remove(protocolEncoder); return null; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java index 865db75d..20995bf1 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java @@ -28,8 +28,10 @@ import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.NetworkProcessor; import com.google.common.collect.MapMaker; import net.sf.cglib.proxy.MethodInterceptor; @@ -47,6 +49,7 @@ class ReadPacketModifier implements MethodInterceptor { // Report errors private ErrorReporter reporter; + private NetworkProcessor processor; // If this is a read packet data method private boolean isReadPacketDataMethod; @@ -58,6 +61,7 @@ class ReadPacketModifier implements MethodInterceptor { this.packetID = packetID; this.packetInjector = packetInjector; this.reporter = reporter; + this.processor = new NetworkProcessor(reporter); this.isReadPacketDataMethod = isReadPacketDataMethod; } @@ -152,9 +156,14 @@ class ReadPacketModifier implements MethodInterceptor { if (event.isCancelled()) { override.put(thisObj, CANCEL_MARKER); + return returnValue; } else if (!objectEquals(thisObj, result)) { override.put(thisObj, result); } + + // This is fine - received packets are enqueued in any case + NetworkMarker marker = NetworkMarker.getNetworkMarker(event); + processor.invokePostListeners(event, marker); } } catch (OutOfMemoryError e) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/WritePacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/WritePacketModifier.java index 3b0a9904..367e51f6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/WritePacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/WritePacketModifier.java @@ -30,6 +30,7 @@ import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketOutputHandler; +import com.comphenix.protocol.injector.NetworkProcessor; import com.google.common.collect.MapMaker; import net.sf.cglib.proxy.MethodInterceptor; @@ -55,12 +56,14 @@ public class WritePacketModifier implements MethodInterceptor { // Report errors private final ErrorReporter reporter; + private final NetworkProcessor processor; // Whether or not this represents the write method private boolean isWriteMethod; public WritePacketModifier(ErrorReporter reporter, boolean isWriteMethod) { this.reporter = reporter; + this.processor = new NetworkProcessor(reporter); this.isWriteMethod = isWriteMethod; } @@ -85,39 +88,24 @@ public class WritePacketModifier implements MethodInterceptor { } if (isWriteMethod) { - PriorityQueue handlers = (PriorityQueue) - information.marker.getOutputHandlers(); - // If every output handler has been removed - ignore everything - if (!handlers.isEmpty()) { + if (!information.marker.getOutputHandlers().isEmpty()) { try { DataOutput output = (DataOutput) args[0]; // First - we need the initial buffer ByteArrayOutputStream outputBufferStream = new ByteArrayOutputStream(); proxy.invoke(information.proxyObject, new Object[] { new DataOutputStream(outputBufferStream) }); - byte[] outputBuffer = outputBufferStream.toByteArray(); // Let each handler prepare the actual output - while (!handlers.isEmpty()) { - PacketOutputHandler handler = handlers.poll(); - - try { - byte[] changed = handler.handle(information.event, outputBuffer); - - // Don't break just because a plugin returned NULL - if (changed != null) { - outputBuffer = changed; - } else { - throw new IllegalStateException("Handler cannot return a NULL array."); - } - } catch (Exception e) { - reporter.reportMinimal(handler.getPlugin(), "PacketOutputHandler.handle()", e); - } - } - + byte[] outputBuffer = processor.processOutput(information.event, information.marker, + outputBufferStream.toByteArray()); + // Write that output to the network stream output.write(outputBuffer); + + // We're done + processor.invokePostListeners(information.event, information.marker); return null; } catch (OutOfMemoryError e) { @@ -131,6 +119,11 @@ public class WritePacketModifier implements MethodInterceptor { ); } } + + // Invoke this write method first + proxy.invoke(information.proxyObject, args); + processor.invokePostListeners(information.event, information.marker); + return null; } // Default to the super method diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index 0bd8045c..2e6b7ed8 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -636,7 +636,7 @@ public abstract class PlayerInjector implements SocketInjector { marker = NetworkMarker.getNetworkMarker(event); // See if we need to proxy the write method - if (result != null && NetworkMarker.hasOutputHandlers(marker)) { + if (result != null && (NetworkMarker.hasOutputHandlers(marker) || NetworkMarker.hasPostListeners(marker))) { result = writePacketInterceptor.constructProxy(result, event, marker); } return result;