From 051a4eda8714a4834de753e3888d06ec7fdbc873 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 17 Jul 2013 03:52:27 +0200 Subject: [PATCH] Add the ability to intercept the write method of packets. This is done by constructing a proxy around the class after every event handler has been invoked, intercepting the write method. Each PacketOutputHandler registered by the packet event listeners is invoked in turn, modifying a byte array of the data that will be written to the network stream. The byte array is initially filled with the serialized version of the packet in the packet event. --- .../com/comphenix/protocol/PacketStream.java | 26 +++- .../comphenix/protocol/async/AsyncMarker.java | 4 +- .../protocol/events/ListenerOptions.java | 13 ++ .../protocol/events/ListeningWhitelist.java | 38 ++++- .../protocol/events/NetworkMarker.java | 133 ++++++++++++++++++ .../protocol/events/PacketEvent.java | 65 ++++++++- .../protocol/events/PacketOutputHandler.java | 35 +++++ .../injector/DelayedPacketManager.java | 44 ++++-- .../protocol/injector/PacketConstructor.java | 1 - .../injector/PacketFilterManager.java | 28 ++-- .../injector/packet/InterceptWritePacket.java | 92 ++++++++++++ .../injector/packet/ProxyPacketInjector.java | 4 +- .../injector/packet/WritePacketModifier.java | 124 ++++++++++++++++ .../injector/player/InjectedArrayList.java | 2 +- .../injector/player/NetworkFieldInjector.java | 13 +- .../player/NetworkObjectInjector.java | 13 +- .../player/NetworkServerInjector.java | 14 +- .../player/PlayerInjectionHandler.java | 4 +- .../injector/player/PlayerInjector.java | 33 ++++- .../player/ProxyPlayerInjectionHandler.java | 5 +- .../injector/server/BukkitSocketInjector.java | 8 +- .../injector/server/QueuedSendPacket.java | 14 +- .../injector/server/SocketInjector.java | 5 +- .../server/TemporaryPlayerFactory.java | 2 +- .../injector/spigot/DummyPlayerHandler.java | 5 +- .../injector/spigot/SpigotPacketInjector.java | 7 +- 26 files changed, 661 insertions(+), 71 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketOutputHandler.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InterceptWritePacket.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/WritePacketModifier.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketStream.java b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketStream.java index a243452a..d5f9b83a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/PacketStream.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/PacketStream.java @@ -22,6 +22,7 @@ import java.lang.reflect.InvocationTargetException; import org.bukkit.entity.Player; import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; /** @@ -30,7 +31,6 @@ import com.comphenix.protocol.events.PacketContainer; * @author Kristian */ public interface PacketStream { - /** * Send a packet to the given player. * @param reciever - the reciever. @@ -49,6 +49,18 @@ public interface PacketStream { */ public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException; + + /** + * Send a packet to the given player. + * @param reciever - the reciever. + * @param packet - packet to send. + * @param marker - the network marker to use. + * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. + * @throws InvocationTargetException - if an error occured when sending the packet. + */ + public void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) + throws InvocationTargetException; + /** * Simulate recieving a certain packet from a given player. @@ -70,4 +82,16 @@ public interface PacketStream { */ public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException; + + /** + * Simulate recieving a certain packet from a given player. + * @param sender - the sender. + * @param packet - the packet that was sent. + * @param marker - the network marker to use. + * @param filters - whether or not to invoke any packet filters below {@link ListenerPriority#MONITOR}. + * @throws InvocationTargetException If the reflection machinery failed. + * @throws IllegalAccessException If the underlying method caused an error. + */ + public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) + throws IllegalAccessException, InvocationTargetException; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java index 5b0e6a1c..73dcdc70 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java @@ -381,9 +381,9 @@ public class AsyncMarker implements Serializable, Comparable { void sendPacket(PacketEvent event) throws IOException { try { if (event.isServerPacket()) { - packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), false); + packetStream.sendServerPacket(event.getPlayer(), event.getPacket(), event.getNetworkMarker(), false); } else { - packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), false); + packetStream.recieveClientPacket(event.getPlayer(), event.getPacket(), event.getNetworkMarker(), false); } transmitted = true; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java new file mode 100644 index 00000000..224197e6 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java @@ -0,0 +1,13 @@ +package com.comphenix.protocol.events; + +/** + * Represents additional options a listener may require. + * + * @author Kristian + */ +public enum ListenerOptions { + /** + * Retrieve the serialized client packet as it appears on the network stream. + */ + INTERCEPT_INPUT_BUFFER, +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java index 79546e56..78a23560 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java @@ -17,6 +17,9 @@ package com.comphenix.protocol.events; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; import java.util.Set; import com.comphenix.protocol.injector.GamePhase; @@ -29,7 +32,7 @@ import com.google.common.collect.Sets; * @author Kristian */ public class ListeningWhitelist { - + /** * A whitelist with no packets - indicates that the listener shouldn't observe any packets. */ @@ -38,6 +41,7 @@ public class ListeningWhitelist { private ListenerPriority priority; private Set whitelist; private GamePhase gamePhase; + private Set options = EnumSet.noneOf(ListenerOptions.class); /** * Creates a packet whitelist for a given priority with a set of packet IDs. @@ -83,6 +87,19 @@ public class ListeningWhitelist { this.gamePhase = gamePhase; } + /** + * Creates a packet whitelist for a given priority with a set of packet IDs and options. + * @param priority - the listener priority. + * @param whitelist - list of packet IDs to observe/enable. + * @param gamePhase - which game phase to receieve notifications on. + */ + public ListeningWhitelist(ListenerPriority priority, Integer[] whitelist, GamePhase gamePhase, ListenerOptions... options) { + this.priority = priority; + this.whitelist = Sets.newHashSet(whitelist); + this.gamePhase = gamePhase; + this.options.addAll(Arrays.asList(options)); + } + /** * Whether or not this whitelist has any enabled packets. * @return TRUE if there are any packets, FALSE otherwise. @@ -115,9 +132,17 @@ public class ListeningWhitelist { return gamePhase; } + /** + * Retrieve every special option associated with this whitelist. + * @return Every special option. + */ + public Set getOptions() { + return Collections.unmodifiableSet(options); + } + @Override public int hashCode(){ - return Objects.hashCode(priority, whitelist, gamePhase); + return Objects.hashCode(priority, whitelist, gamePhase, options); } /** @@ -156,7 +181,9 @@ public class ListeningWhitelist { if(obj instanceof ListeningWhitelist){ final ListeningWhitelist other = (ListeningWhitelist) obj; return Objects.equal(priority, other.priority) - && Objects.equal(whitelist, other.whitelist); + && Objects.equal(whitelist, other.whitelist) + && Objects.equal(gamePhase, other.gamePhase) + && Objects.equal(options, other.options); } else{ return false; } @@ -169,6 +196,9 @@ public class ListeningWhitelist { else return Objects.toStringHelper(this) .add("priority", priority) - .add("packets", whitelist).toString(); + .add("packets", whitelist) + .add("gamephase", gamePhase) + .add("options", options). + toString(); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java new file mode 100644 index 00000000..fd6b3546 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/NetworkMarker.java @@ -0,0 +1,133 @@ +package com.comphenix.protocol.events; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.PriorityQueue; + +import javax.annotation.Nonnull; + +import com.google.common.base.Preconditions; +import com.google.common.primitives.Ints; + +public class NetworkMarker { + // Custom network handler + private PriorityQueue outputHandlers; + // The input buffer + private ByteBuffer inputBuffer; + + private ConnectionSide side; + + /** + * Construct a new network marker. + *

+ * The input buffer is only non-null for client-side packets. + * @param side - whether or not this marker belongs to a client or server packet. + * @param inputBuffer - the read serialized packet data. + */ + public NetworkMarker(@Nonnull ConnectionSide side, byte[] inputBuffer) { + this.side = Preconditions.checkNotNull(side, "side cannot be NULL."); + + if (inputBuffer != null) { + this.inputBuffer = ByteBuffer.wrap(inputBuffer); + } + } + + /** + * Retrieve whether or not this marker belongs to a client or a server side packet. + * @return The side the parent packet belongs to. + */ + public ConnectionSide getSide() { + return side; + } + + /** + * Retrieve the serialized packet data (excluding the header) from the network input stream. + *

+ * The returned buffer is read-only. If the parent event is a server side packet this + * method throws {@link IllegalStateException}. + *

+ * It returns NULL if the packet was transmitted by a plugin locally. + * @return A byte buffer containing the raw packet data read from the network. + */ + public ByteBuffer getInputBuffer() { + if (side.isForServer()) + throw new IllegalStateException("Server-side packets have no input buffer."); + return inputBuffer != null ? inputBuffer.asReadOnlyBuffer() : null; + } + + /** + * Enqueue the given output handler for managing how the current packet will be written to the network stream. + *

+ * Note that output handlers are not serialized, as most consumers will probably implement them using anonymous classes. + * It is not safe to serialize anonymous classes, as their name depend on the order in which they are declared in the parent class. + *

+ * This can only be invoked on server side packet events. + * @param handler - the handler that will take part in serializing the packet. + * @return TRUE if it was added, FALSE if it has already been added. + */ + public boolean addOutputHandler(@Nonnull PacketOutputHandler handler) { + checkServerSide(); + Preconditions.checkNotNull(handler, "handler cannot be NULL."); + + // Lazy initialization - it's imperative that we save space and time here + if (outputHandlers == null) { + outputHandlers = new PriorityQueue(10, new Comparator() { + @Override + public int compare(PacketOutputHandler o1, PacketOutputHandler o2) { + return Ints.compare(o1.getPriority().getSlot(), o2.getPriority().getSlot()); + } + }); + } + return outputHandlers.add(handler); + } + + /** + * Remove a given output handler from the serialization queue. + *

+ * This can only be invoked on server side packet events. + * @param handler - the handler to remove. + * @return TRUE if the handler was removed, FALSE otherwise. + */ + public boolean removeOutputHandler(@Nonnull PacketOutputHandler handler) { + checkServerSide(); + Preconditions.checkNotNull(handler, "handler cannot be NULL."); + + if (outputHandlers != null) { + return outputHandlers.remove(handler); + } + return false; + } + + /** + * Retrieve every registered output handler in no particular order. + * @return Every registered output handler. + */ + @Nonnull + public Collection getOutputHandlers() { + if (outputHandlers != null) { + return outputHandlers; + } else { + return Collections.emptyList(); + } + } + + /** + * Ensure that the packet event is server side. + */ + private void checkServerSide() { + if (side.isForClient()) { + throw new IllegalStateException("Must be a server side packet."); + } + } + + /** + * Determine if the given marker has any output handlers. + * @param marker - the marker to check. + * @return TRUE if it does, FALSE otherwise. + */ + public static boolean hasOutputHandlers(NetworkMarker marker) { + return marker != null && !marker.getOutputHandlers().isEmpty(); + } +} 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 5d43ff50..8f0f1be7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketEvent.java @@ -22,11 +22,11 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.ref.WeakReference; import java.util.EventObject; - import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import com.comphenix.protocol.async.AsyncMarker; +import com.google.common.base.Preconditions; public class PacketEvent extends EventObject implements Cancellable { /** @@ -43,6 +43,9 @@ public class PacketEvent extends EventObject implements Cancellable { private AsyncMarker asyncMarker; private boolean asynchronous; + + // Network input and output handlers + private NetworkMarker networkMarker; // Whether or not a packet event is read only private boolean readOnly; @@ -56,9 +59,14 @@ public class PacketEvent extends EventObject implements Cancellable { } private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) { + this(source, packet, null, player, serverPacket); + } + + private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket) { super(source); this.packet = packet; this.playerReference = new WeakReference(player); + this.networkMarker = marker; this.serverPacket = serverPacket; } @@ -83,6 +91,18 @@ public class PacketEvent extends EventObject implements Cancellable { return new PacketEvent(source, packet, client, false); } + /** + * Creates an event representing a client packet transmission. + * @param source - the event source. + * @param packet - the packet. + * @param marker - the network marker. + * @param client - the client that sent the packet. + * @return The event. + */ + public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) { + return new PacketEvent(source, packet, marker, client, false); + } + /** * Creates an event representing a server packet transmission. * @param source - the event source. @@ -94,6 +114,18 @@ public class PacketEvent extends EventObject implements Cancellable { return new PacketEvent(source, packet, recipient, true); } + /** + * Creates an event representing a server packet transmission. + * @param source - the event source. + * @param packet - the packet. + * @param marker - the network marker. + * @param recipient - the client that will receieve the packet. + * @return The event. + */ + public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) { + return new PacketEvent(source, packet, marker, recipient, true); + } + /** * Create an asynchronous packet event from a synchronous event and a async marker. * @param event - the original synchronous event. @@ -121,7 +153,7 @@ public class PacketEvent extends EventObject implements Cancellable { throw new IllegalStateException("The packet event is read-only."); this.packet = packet; } - + /** * Retrieves the packet ID. * @return The current packet ID. @@ -137,7 +169,36 @@ public class PacketEvent extends EventObject implements Cancellable { public boolean isCancelled() { return cancel; } + + /** + * Retrieve the object responsible for managing the serialized input and output of a packet. + *

+ * Note that the serialized input data is only available for client-side packets, and the output handlers + * can only be applied to server-side packets. + * @return The network manager. + */ + public NetworkMarker getNetworkMarker() { + if (networkMarker == null) { + if (isServerPacket()) { + networkMarker = new NetworkMarker( + serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE, null); + } else { + throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener."); + } + } + return networkMarker; + } + /** + * Update the network manager. + *

+ * This method is internal - do not call. + * @param networkMarker - the new network manager. + */ + public void setNetworkMarker(NetworkMarker networkMarker) { + this.networkMarker = Preconditions.checkNotNull(networkMarker, "marker cannot be NULL"); + } + /** * Sets whether or not the packet should be cancelled. Uncancelling is possible. *

diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketOutputHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketOutputHandler.java new file mode 100644 index 00000000..4e9c4856 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketOutputHandler.java @@ -0,0 +1,35 @@ +package com.comphenix.protocol.events; + +import org.bukkit.plugin.Plugin; + +/** + * Represents a custom packet serializer onto the network stream. + * + * @author Kristian + */ +public interface PacketOutputHandler { + /** + * Retrieve the priority that decides the order each network handler is allowed to manipulate the output buffer. + *

+ * Higher priority is executed before lower. + * @return The handler priority. + */ + public ListenerPriority getPriority(); + + /** + * The plugin that owns this output handler. + * @return The owner plugin. + */ + public Plugin getPlugin(); + + /** + * Invoked when a given packet is to be written to the output stream. + *

+ * Note that the buffer is initially filled with the output from the default write method. This excludes + * the packet ID header. + * @param event - the packet that will be outputted. + * @param buffer - the data that is currently scheduled to be outputted. + * @return The modified byte array to write. NULL is not permitted. + */ + public byte[] handle(PacketEvent event, byte[] buffer); +} 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 006f650b..48cbe4a6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java @@ -19,6 +19,7 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.events.ConnectionSide; +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; @@ -49,16 +50,19 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { private static class QueuedPacket { private final Player player; private final PacketContainer packet; + private final NetworkMarker marker; + private final boolean filtered; private final ConnectionSide side; - public QueuedPacket(Player player, PacketContainer packet, boolean filtered, ConnectionSide side) { + public QueuedPacket(Player player, PacketContainer packet, NetworkMarker marker, boolean filtered, ConnectionSide side) { this.player = player; this.packet = packet; + this.marker = marker; this.filtered = filtered; this.side = side; } - + /** * Retrieve the packet that will be transmitted or receieved. * @return The packet. @@ -83,6 +87,14 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { return side; } + /** + * Retrieve the associated network marker used to serialize packets on the network stream. + * @return The associated marker. + */ + public NetworkMarker getMarker() { + return marker; + } + /** * Determine if the packet should be intercepted by packet listeners. * @return TRUE if it should, FALSE otherwise. @@ -162,10 +174,10 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { // Attempt to send it now switch (packet.getSide()) { case CLIENT_SIDE: - delegate.recieveClientPacket(packet.getPlayer(), packet.getPacket(), packet.isFiltered()); + delegate.recieveClientPacket(packet.getPlayer(), packet.getPacket(), packet.getMarker(), packet.isFiltered()); break; case SERVER_SIDE: - delegate.sendServerPacket(packet.getPlayer(), packet.getPacket(), packet.isFiltered()); + delegate.sendServerPacket(packet.getPlayer(), packet.getPacket(), packet.getMarker(), packet.isFiltered()); break; default: @@ -197,29 +209,39 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { @Override public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException { - sendServerPacket(reciever, packet, true); + sendServerPacket(reciever, packet, null, true); } - + @Override public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { + sendServerPacket(reciever, packet, null, filters); + } + + @Override + public void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException { if (delegate != null) { - delegate.sendServerPacket(reciever, packet, filters); + delegate.sendServerPacket(reciever, packet, marker, filters); } else { - queuedPackets.add(new QueuedPacket(reciever, packet, filters, ConnectionSide.SERVER_SIDE)); + queuedPackets.add(new QueuedPacket(reciever, packet, marker, filters, ConnectionSide.SERVER_SIDE)); } } @Override public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException { - recieveClientPacket(sender, packet, true); + recieveClientPacket(sender, packet, null, true); } @Override public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException { + recieveClientPacket(sender, packet, null, filters); + } + + @Override + public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) throws IllegalAccessException, InvocationTargetException { if (delegate != null) { - delegate.recieveClientPacket(sender, packet, filters); + delegate.recieveClientPacket(sender, packet, marker, filters); } else { - queuedPackets.add(new QueuedPacket(sender, packet, filters, ConnectionSide.CLIENT_SIDE)); + queuedPackets.add(new QueuedPacket(sender, packet, marker, filters, ConnectionSide.CLIENT_SIDE)); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java index ce5f5b1f..121dff89 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java @@ -142,7 +142,6 @@ public class PacketConstructor { * @throws RuntimeException Minecraft threw an exception. */ public PacketContainer createPacket(Object... values) throws FieldAccessException { - try { // Convert types for (int i = 0; i < values.length; i++) { 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 2848cfe5..0d9b1a1e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -550,11 +550,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok @Override public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException { - sendServerPacket(reciever, packet, true); + sendServerPacket(reciever, packet, null, true); } @Override public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { + sendServerPacket(reciever, packet, null, true); + } + + @Override + public void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException { if (reciever == null) throw new IllegalArgumentException("reciever cannot be NULL."); if (packet == null) @@ -562,25 +567,30 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // We may have to enable player injection indefinitely after this if (packetCreation.compareAndSet(false, true)) incrementPhases(GamePhase.PLAYING); - + // Inform the MONITOR packets if (!filters) { + PacketEvent event = PacketEvent.fromServer(this, packet, marker, reciever); + sendingListeners.invokePacketSending( - reporter, - PacketEvent.fromServer(this, packet, reciever), - ListenerPriority.MONITOR); + reporter, event, ListenerPriority.MONITOR); + marker = event.getNetworkMarker(); } - - playerInjection.sendServerPacket(reciever, packet, filters); + playerInjection.sendServerPacket(reciever, packet, marker, filters); } @Override public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException { - recieveClientPacket(sender, packet, true); + recieveClientPacket(sender, packet, null, true); } @Override public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException { + recieveClientPacket(sender, packet, null, true); + } + + @Override + public void recieveClientPacket(Player sender, PacketContainer packet, NetworkMarker marker, boolean filters) throws IllegalAccessException, InvocationTargetException { if (sender == null) throw new IllegalArgumentException("sender cannot be NULL."); if (packet == null) @@ -606,7 +616,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Let the monitors know though recievedListeners.invokePacketSending( reporter, - PacketEvent.fromClient(this, packet, sender), + PacketEvent.fromClient(this, packet, marker, sender), ListenerPriority.MONITOR); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InterceptWritePacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InterceptWritePacket.java new file mode 100644 index 00000000..4231b56d --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InterceptWritePacket.java @@ -0,0 +1,92 @@ +package com.comphenix.protocol.injector.packet; + +import java.io.DataOutput; +import java.lang.reflect.Method; + +import net.sf.cglib.proxy.Callback; +import net.sf.cglib.proxy.CallbackFilter; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.NoOp; + +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.PacketEvent; +import com.comphenix.protocol.reflect.MethodInfo; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.utility.MinecraftReflection; + +/** + * Retrieve a packet instance that has its write method intercepted. + * @author Kristian + */ +public class InterceptWritePacket { + public static final ReportType REPORT_CANNOT_FIND_WRITE_PACKET_METHOD = new ReportType("Cannot find write packet method in %s."); + public static final ReportType REPORT_CANNOT_CONSTRUCT_WRITE_PROXY = new ReportType("Cannot construct write proxy packet %s."); + + /** + * Matches the readPacketData(DataInputStream) method in Packet. + */ + private static FuzzyMethodContract WRITE_PACKET = FuzzyMethodContract.newBuilder(). + returnTypeVoid(). + parameterDerivedOf(DataOutput.class). + parameterCount(1). + build(); + + private ClassLoader classLoader; + private ErrorReporter reporter; + + private CallbackFilter filter; + private boolean writePacketIntercepted; + + public InterceptWritePacket(ClassLoader classLoader, ErrorReporter reporter) { + this.classLoader = classLoader; + this.reporter = reporter; + } + + /** + * Construct a new instance of the proxy object. + * @return New instance of proxy. + */ + public Object constructProxy(Object proxyObject, PacketEvent event, NetworkMarker marker) { + // Construct the proxy object + Enhancer ex = new Enhancer(); + + // Initialize the shared filter + if (filter == null) { + filter = new CallbackFilter() { + @Override + public int accept(Method method) { + // Skip methods defined in Object + if (WRITE_PACKET.isMatch(MethodInfo.fromMethod(method), null)) { + return 1; + } else { + return 0; + } + } + }; + } + + // Subclass the generic packet class + ex.setSuperclass(MinecraftReflection.getPacketClass()); + ex.setCallbackFilter(filter); + ex.setClassLoader(classLoader); + ex.setCallbacks(new Callback[] { + NoOp.INSTANCE, + new WritePacketModifier(reporter, proxyObject, event, marker) + }); + + Object proxy = ex.create(); + + if (proxy != null) { + // Check that we found the read method + if (!writePacketIntercepted) { + reporter.reportWarning(this, + Report.newBuilder(REPORT_CANNOT_FIND_WRITE_PACKET_METHOD). + messageParam(MinecraftReflection.getPacketClass())); + } + } + return proxy; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java index 800f264e..cb7527a7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java @@ -137,7 +137,7 @@ class ProxyPacketInjector implements PacketInjector { /** * Matches the readPacketData(DataInputStream) method in Packet. */ - private static FuzzyMethodContract readPacket = FuzzyMethodContract.newBuilder(). + private static FuzzyMethodContract READ_PACKET = FuzzyMethodContract.newBuilder(). returnTypeVoid(). parameterDerivedOf(DataInput.class). parameterCount(1). @@ -240,7 +240,7 @@ class ProxyPacketInjector implements PacketInjector { // Skip methods defined in Object if (method.getDeclaringClass().equals(Object.class)) { return 0; - } else if (readPacket.isMatch(MethodInfo.fromMethod(method), null)) { + } else if (READ_PACKET.isMatch(MethodInfo.fromMethod(method), null)) { readPacketIntercepted = true; return 1; } else { 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 new file mode 100644 index 00000000..a4be84d0 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/WritePacketModifier.java @@ -0,0 +1,124 @@ +/* + * 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 + * 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. + * 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 + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector.packet; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.lang.reflect.Method; +import java.util.PriorityQueue; + +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.PacketEvent; +import com.comphenix.protocol.events.PacketOutputHandler; + +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +public class WritePacketModifier implements MethodInterceptor { + public static final ReportType REPORT_CANNOT_WRITE_SERVER_PACKET = new ReportType("Cannot write server packet."); + + // Report errors + private final ErrorReporter reporter; + + // Marker that contains custom writers + private final Object proxyObject; + private final PacketEvent event; + private final NetworkMarker marker; + + public WritePacketModifier(ErrorReporter reporter, Object proxyObject, PacketEvent event, NetworkMarker marker) { + this.proxyObject = proxyObject; + this.event = event; + this.marker = marker; + this.reporter = reporter; + } + + @Override + public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + PriorityQueue handlers = (PriorityQueue) marker.getOutputHandlers(); + + // If every output handler has been removed - ignore everything + if (!handlers.isEmpty()) { + try { + DataOutput output = (DataOutput) args[0]; + + // First - we need the initial buffer + ByteArrayOutputStream outputBufferStream = new ByteArrayOutputStream(); + proxy.invokeSuper(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(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); + } + } + + // Write that output to the network stream + output.write(outputBuffer); + + } catch (Throwable e) { + // Minecraft cannot handle this error + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_WRITE_SERVER_PACKET).callerParam(args[0]).error(e) + ); + } + } + + // Default to the super method + return proxy.invokeSuper(proxyObject, args); + } + + /** + * Retrieve the proxied Minecraft object. + * @return The proxied object. + */ + public Object getProxyObject() { + return proxyObject; + } + + /** + * Retrieve the associated packet event. + * @return The packet event. + */ + public PacketEvent getEvent() { + return event; + } + + /** + * Retrieve the network marker that is in use. + * @return The network marker. + */ + public NetworkMarker getMarker() { + return marker; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java index 4884dce0..2f974c32 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java @@ -86,7 +86,7 @@ class InjectedArrayList extends ArrayList { super.add(result); } else { // We'll use the FakePacket marker instead of preventing the filters - injector.sendServerPacket(createNegativePacket(packet), true); + injector.sendServerPacket(createNegativePacket(packet), null, true); } // Collection.add contract diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java index 559c93ef..bfa828d7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java @@ -31,6 +31,7 @@ import com.comphenix.protocol.Packets; import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; @@ -76,14 +77,10 @@ class NetworkFieldInjector extends PlayerInjector { // Determine if we're listening private IntegerSet sendingFilters; - // Used to construct proxy objects - private ClassLoader classLoader; - public NetworkFieldInjector(ClassLoader classLoader, ErrorReporter reporter, Player player, ListenerInvoker manager, IntegerSet sendingFilters) throws IllegalAccessException { - super(reporter, player, manager); - this.classLoader = classLoader; + super(classLoader, reporter, player, manager); this.sendingFilters = sendingFilters; } @@ -105,12 +102,15 @@ class NetworkFieldInjector extends PlayerInjector { } @Override - public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { + public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException { if (networkManager != null) { try { if (!filtered) { ignoredPackets.add(packet); } + if (marker != null) { + queuedMarkers.put(packet, marker); + } // Note that invocation target exception is a wrapper for a checked exception queueMethod.invoke(networkManager, packet); @@ -146,7 +146,6 @@ class NetworkFieldInjector extends PlayerInjector { @Override public void injectManager() { - if (networkManager != null) { @SuppressWarnings("rawtypes") diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java index 2ea0da49..78d3e31f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java @@ -35,6 +35,7 @@ import com.comphenix.protocol.Packets; import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; @@ -51,9 +52,6 @@ public class NetworkObjectInjector extends PlayerInjector { // Determine if we're listening private IntegerSet sendingFilters; - // Used to construct proxy objects - private ClassLoader classLoader; - // After commit 336a4e00668fd2518c41242755ed6b3bdc3b0e6c (Update CraftBukkit to Minecraft 1.4.4.), // CraftBukkit stopped redirecting map chunk and map chunk bulk packets to a separate queue. // Thus, NetworkFieldInjector can safely handle every packet (though not perfectly - some packets @@ -79,9 +77,8 @@ public class NetworkObjectInjector extends PlayerInjector { */ public NetworkObjectInjector(ClassLoader classLoader, ErrorReporter reporter, Player player, ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException { - super(reporter, player, invoker); + super(classLoader, reporter, player, invoker); this.sendingFilters = sendingFilters; - this.classLoader = classLoader; } @Override @@ -103,11 +100,15 @@ public class NetworkObjectInjector extends PlayerInjector { } @Override - public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { + public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException { Object networkDelegate = filtered ? networkManagerRef.getValue() : networkManagerRef.getOldValue(); if (networkDelegate != null) { try { + if (marker != null) { + queuedMarkers.put(packet, marker); + } + // Note that invocation target exception is a wrapper for a checked exception queueMethod.invoke(networkDelegate, packet); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index a3fc2748..a5efbb11 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -30,6 +30,7 @@ import com.comphenix.protocol.concurrency.IntegerSet; 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.PacketListener; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; @@ -64,9 +65,6 @@ class NetworkServerInjector extends PlayerInjector { // Determine if we're listening private IntegerSet sendingFilters; - // Used to create proxy objects - private ClassLoader classLoader; - // Whether or not the player has disconnected private boolean hasDisconnected; @@ -78,8 +76,7 @@ class NetworkServerInjector extends PlayerInjector { ListenerInvoker invoker, IntegerSet sendingFilters, InjectedServerConnection serverInjection) throws IllegalAccessException { - super(reporter, player, invoker); - this.classLoader = classLoader; + super(classLoader, reporter, player, invoker); this.sendingFilters = sendingFilters; this.serverInjection = serverInjection; } @@ -90,11 +87,15 @@ class NetworkServerInjector extends PlayerInjector { } @Override - public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { + public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException { Object serverDelegate = filtered ? serverHandlerRef.getValue() : serverHandlerRef.getOldValue(); if (serverDelegate != null) { try { + if (marker != null) { + queuedMarkers.put(packet, marker); + } + // Note that invocation target exception is a wrapper for a checked exception MinecraftMethods.getSendPacketMethod().invoke(serverDelegate, packet); @@ -112,7 +113,6 @@ class NetworkServerInjector extends PlayerInjector { @Override public void injectManager() { - if (serverHandlerRef == null) throw new IllegalStateException("Cannot find server handler."); // Don't inject twice 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 bb8f4a2b..4f4858dc 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 @@ -6,6 +6,7 @@ import java.net.InetSocketAddress; import java.util.Set; import org.bukkit.entity.Player; +import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; @@ -113,10 +114,11 @@ public interface PlayerInjectionHandler { * Send the given packet to the given reciever. * @param reciever - the player receiver. * @param packet - the packet to send. + * @param marker * @param filters - whether or not to invoke the packet filters. * @throws InvocationTargetException If an error occured during sending. */ - public abstract void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) + public abstract void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException; /** 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 79bb4c00..72e5b2e1 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 @@ -24,6 +24,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Socket; import java.net.SocketAddress; +import java.util.Map; import net.sf.cglib.proxy.Factory; import org.bukkit.entity.Player; @@ -32,6 +33,7 @@ import com.comphenix.protocol.Packets; 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.events.PacketListener; @@ -39,6 +41,7 @@ import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.comphenix.protocol.injector.packet.InterceptWritePacket; import com.comphenix.protocol.injector.server.SocketInjector; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -46,6 +49,7 @@ import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; +import com.google.common.collect.MapMaker; public abstract class PlayerInjector implements SocketInjector { // Disconnect method related reports @@ -120,6 +124,13 @@ public abstract class PlayerInjector implements SocketInjector { // Handle errors protected ErrorReporter reporter; + // Used to construct proxy objects + protected ClassLoader classLoader; + + // Previous markers + protected Map queuedMarkers = new MapMaker().weakKeys().makeMap(); + protected InterceptWritePacket writePacketInterceptor; + // Whether or not the injector has been cleaned private boolean clean; @@ -127,10 +138,14 @@ public abstract class PlayerInjector implements SocketInjector { boolean updateOnLogin; Player updatedPlayer; - public PlayerInjector(ErrorReporter reporter, Player player, ListenerInvoker invoker) throws IllegalAccessException { + public PlayerInjector(ClassLoader classLoader, ErrorReporter reporter, Player player, ListenerInvoker invoker) throws IllegalAccessException { + this.classLoader = classLoader; this.reporter = reporter; this.player = player; this.invoker = invoker; + + // Intercept the write method + writePacketInterceptor = new InterceptWritePacket(classLoader, reporter); } /** @@ -492,11 +507,12 @@ public abstract class PlayerInjector implements SocketInjector { /** * Send a packet to the client. * @param packet - server packet to send. + * @param marker - the network marker. * @param filtered - whether or not the packet will be filtered by our listeners. * @param InvocationTargetException If an error occured when sending the packet. */ @Override - public abstract void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException; + public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException; /** * Inject a hook to catch packets sent to the current player. @@ -582,9 +598,11 @@ public abstract class PlayerInjector implements SocketInjector { // Make sure we're listening if (id != null && hasListener(id)) { + NetworkMarker marker = queuedMarkers.remove(packet); + // A packet has been sent guys! PacketContainer container = new PacketContainer(id, packet); - PacketEvent event = PacketEvent.fromServer(invoker, container, currentPlayer); + PacketEvent event = PacketEvent.fromServer(invoker, container, marker, currentPlayer); invoker.invokePacketSending(event); // Cancelling is pretty simple. Just ignore the packet. @@ -592,7 +610,14 @@ public abstract class PlayerInjector implements SocketInjector { return null; // Right, remember to replace the packet again - return event.getPacket().getHandle(); + Object result = event.getPacket().getHandle(); + marker = event.getNetworkMarker(); + + // See if we need to proxy the write method + if (result != null && NetworkMarker.hasOutputHandlers(marker)) { + result = writePacketInterceptor.constructProxy(result, event, marker); + } + return result; } } catch (Throwable e) { 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 584cb8af..f13a4440 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 @@ -38,6 +38,7 @@ import com.comphenix.protocol.concurrency.IntegerSet; 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.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketListener; @@ -533,12 +534,12 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { * @throws InvocationTargetException If an error occured during sending. */ @Override - public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { + public void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException { SocketInjector injector = getInjector(reciever); // Send the packet, or drop it completely if (injector != null) { - injector.sendServerPacket(packet.getHandle(), filters); + injector.sendServerPacket(packet.getHandle(), marker, filters); } else { throw new PlayerLoggedOutException(String.format( "Unable to send packet %s (%s): Player %s has logged out.", diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java index e4f55330..0d45d6cb 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java @@ -9,6 +9,8 @@ import java.util.List; import org.bukkit.entity.Player; +import com.comphenix.protocol.events.NetworkMarker; + public class BukkitSocketInjector implements SocketInjector { private Player player; @@ -41,9 +43,9 @@ public class BukkitSocketInjector implements SocketInjector { } @Override - public void sendServerPacket(Object packet, boolean filtered) + public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException { - QueuedSendPacket command = new QueuedSendPacket(packet, filtered); + QueuedSendPacket command = new QueuedSendPacket(packet, marker, filtered); // Queue until we can find something better syncronizedQueue.add(command); @@ -65,7 +67,7 @@ public class BukkitSocketInjector implements SocketInjector { try { synchronized(syncronizedQueue) { for (QueuedSendPacket command : syncronizedQueue) { - delegate.sendServerPacket(command.getPacket(), command.isFiltered()); + delegate.sendServerPacket(command.getPacket(), command.getMarker(), command.isFiltered()); } syncronizedQueue.clear(); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java index e12f47a8..0e7a479b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java @@ -1,18 +1,30 @@ package com.comphenix.protocol.injector.server; +import com.comphenix.protocol.events.NetworkMarker; + /** * Represents a single send packet command. * @author Kristian */ class QueuedSendPacket { private final Object packet; + private final NetworkMarker marker; private final boolean filtered; - public QueuedSendPacket(Object packet, boolean filtered) { + public QueuedSendPacket(Object packet, NetworkMarker marker, boolean filtered) { this.packet = packet; + this.marker = marker; this.filtered = filtered; } + /** + * Retrieve the network marker. + * @return Marker. + */ + public NetworkMarker getMarker() { + return marker; + } + /** * Retrieve the underlying packet that will be sent. * @return The underlying packet. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java index e484c2e5..e4d6dc49 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/SocketInjector.java @@ -6,6 +6,8 @@ import java.net.SocketAddress; import org.bukkit.entity.Player; +import com.comphenix.protocol.events.NetworkMarker; + /** * Represents an injector that only gives access to a player's socket. * @@ -36,10 +38,11 @@ public interface SocketInjector { /** * Send a packet to the client. * @param packet - server packet to send. + * @param marker - the network marker. * @param filtered - whether or not the packet will be filtered by our listeners. * @param InvocationTargetException If an error occured when sending the packet. */ - public abstract void sendServerPacket(Object packet, boolean filtered) + public abstract void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) throws InvocationTargetException; /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java index 62db7f2a..83c6f00b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java @@ -191,7 +191,7 @@ public class TemporaryPlayerFactory { * @throws FieldAccessException If we were unable to construct the message packet. */ private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { - injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false); + injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), null, false); return null; } } 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 dd64e3ad..af8e9f0e 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 @@ -7,6 +7,7 @@ import java.util.Set; import org.bukkit.entity.Player; import com.comphenix.protocol.concurrency.IntegerSet; +import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; @@ -69,8 +70,8 @@ class DummyPlayerHandler implements PlayerInjectionHandler { } @Override - public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { - injector.sendServerPacket(reciever, packet, filters); + public void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException { + injector.sendServerPacket(reciever, packet, marker, filters); } @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java index 070e3c14..220c8c78 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/SpigotPacketInjector.java @@ -24,6 +24,7 @@ import com.comphenix.protocol.concurrency.IntegerSet; import com.comphenix.protocol.error.DelegatedErrorReporter; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.ListenerInvoker; @@ -460,15 +461,15 @@ public class SpigotPacketInjector implements SpigotPacketListener { }, CLEANUP_DELAY); } } - /** * Invoked when a plugin wants to sent a packet. * @param reciever - the packet receiver. * @param packet - the packet to transmit. + * @param marker - the network marker object. * @param filters - whether or not to invoke the packet listeners. * @throws InvocationTargetException If anything went wrong. */ - void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { + void sendServerPacket(Player reciever, PacketContainer packet, NetworkMarker marker, boolean filters) throws InvocationTargetException { NetworkObjectInjector networkObject = getInjector(reciever); // If TRUE, process this packet like any other @@ -478,7 +479,7 @@ public class SpigotPacketInjector implements SpigotPacketListener { ignoredPackets.add(packet.getHandle()); if (networkObject != null) - networkObject.sendServerPacket(packet.getHandle(), filters); + networkObject.sendServerPacket(packet.getHandle(), marker, filters); else throw new PlayerLoggedOutException("Player " + reciever + " has logged out"); }