/* * 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.events; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.ref.WeakReference; import java.util.EventObject; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.async.AsyncMarker; import com.comphenix.protocol.error.PluginContext; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.injector.temporary.TemporaryPlayer; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import javax.annotation.Nullable; /** * Represents a packet sending or receiving event. Changes to the packet will be reflected in the final version to be * sent or received. It is also possible to cancel an event. * * @author Kristian */ public class PacketEvent extends EventObject implements Cancellable { public static final ReportType REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING = new ReportType( "Plugin %s changed packet type from %s to %s in packet listener. This is confusing for other plugins! (Not an error, though!)"); private static final SetMultimap CHANGE_WARNINGS = Multimaps.synchronizedSetMultimap(HashMultimap.create()); /** * Automatically generated by Eclipse. */ private static final long serialVersionUID = -5360289379097430620L; // Network input and output handlers NetworkMarker networkMarker; private transient WeakReference playerReference; private PacketContainer packet; private boolean serverPacket; private boolean cancel; private AsyncMarker asyncMarker; private boolean asynchronous; // Whether or not a packet event is read only private boolean readOnly; private boolean filtered; @Nullable private PacketEvent bundle; /** * Use the static constructors to create instances of this event. * * @param source - the event source. */ 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, true, null); } private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket, boolean filtered, @Nullable PacketEvent bundleEvent) { super(source); if(packet == null) { throw new IllegalArgumentException("packet cannot be null"); } this.packet = packet; this.playerReference = new WeakReference<>(player); this.networkMarker = marker; this.serverPacket = serverPacket; this.filtered = filtered; this.bundle = bundleEvent; } private PacketEvent(PacketEvent original, AsyncMarker asyncMarker) { super(original.source); this.packet = original.packet; this.playerReference = original.getPlayerReference(); this.cancel = original.cancel; this.serverPacket = original.serverPacket; this.filtered = original.filtered; this.networkMarker = original.networkMarker; this.asyncMarker = asyncMarker; this.asynchronous = true; } /** * Creates an event representing a client packet transmission. * * @param source - the event source. * @param packet - the packet. * @param client - the client that sent the packet. * @return The event. */ public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) { 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, true, null); } /** * 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, null); } /** * Creates an event representing a server packet transmission. * * @param source - the event source. * @param packet - the packet. * @param recipient - the client that will receieve the packet. * @return The event. */ public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) { 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, true, null); } /** * 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 fromServer(source, packet, marker, recipient, filtered, null); } /** * 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 this packet event is processed by every packet listener. * @param bundle - The corresponding packet event of the bundle if this packet is part of a bundle. Otherwise, null. * @return The event. */ public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient, boolean filtered, @Nullable PacketEvent bundle) { return new PacketEvent(source, packet, marker, recipient, true, filtered, bundle); } /** * Create an asynchronous packet event from a synchronous event and a async marker. * * @param event - the original synchronous event. * @param marker - the asynchronous marker. * @return The new packet event. */ public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) { return new PacketEvent(event, marker); } /** * Determine if we are executing the packet event in an asynchronous thread. *

* If so, you must synchronize all calls to the Bukkit API. *

* Generally, most server packets are executed on the main thread, whereas client packets are all executed * asynchronously. * * @return TRUE if we are, FALSE otherwise. */ public boolean isAsync() { return !Bukkit.isPrimaryThread(); } /** * Retrieves the packet that will be sent to the player. * * @return Packet to send to the player. */ public PacketContainer getPacket() { return packet; } /** * Replace the packet that will be sent to the player. * * @param packet - the packet that will be sent instead. */ public void setPacket(PacketContainer packet) { if (readOnly) { throw new IllegalStateException("The packet event is read-only."); } if (packet == null) { throw new IllegalArgumentException("Cannot set packet to NULL. Use setCancelled() instead."); } // Change warnings final PacketType oldType = this.packet.getType(); final PacketType newType = packet.getType(); if (this.packet != null && !Objects.equal(oldType, newType)) { // Only report this once if (CHANGE_WARNINGS.put(oldType, newType)) { ProtocolLibrary.getErrorReporter().reportWarning(this, Report.newBuilder(REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING). messageParam(PluginContext.getPluginCaller(new Exception()), oldType, newType). build()); } } this.packet = packet; } /** * Retrieves the packet ID. *

* Deprecated: Use {@link #getPacketType()} instead. * * @return The current packet ID. */ @Deprecated public int getPacketID() { return packet.getId(); } /** * Retrieve the packet type. * * @return The type. */ public PacketType getPacketType() { return packet.getType(); } /** * Retrieves whether or not the packet should be cancelled. * * @return TRUE if it should be cancelled, FALSE otherwise. */ @Override public boolean isCancelled() { return cancel; } /** * Sets whether or not the packet should be cancelled. Uncancelling is possible. *

* Warning: A cancelled packet should never be re-transmitted. Use the asynchronous * packet manager if you need to perform extensive processing. It should also be used if you need to synchronize with * the main thread. *

* This ensures that other plugins can work with the same packet. *

* An asynchronous listener can also delay a packet indefinitely without having to block its thread. * * @param cancel - TRUE if it should be cancelled, FALSE otherwise. */ @Override public void setCancelled(boolean cancel) { if (readOnly) { throw new IllegalStateException("The packet event is read-only."); } this.cancel = 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, this.getPacketType()); } 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"); } private WeakReference getPlayerReference() { Player player = playerReference.get(); if (player instanceof TemporaryPlayer) { Player updated = player.getPlayer(); if (updated != null && !(updated instanceof TemporaryPlayer)) { playerReference.clear(); playerReference = new WeakReference<>(updated); } } return playerReference; } /** * Retrieves the player that has sent the packet or is receiving it. * * @return The player associated with this event. */ public Player getPlayer() { return getPlayerReference().get(); } /** * Whether or not the player in this PacketEvent is temporary (i.e. they don't have a true player instance yet). * Temporary players have a limited subset of methods that may be used: *

*

* Anything else will throw an UnsupportedOperationException. Use this check before calling other methods when * dealing with packets early in the login sequence or if you get the aforementioned exception. * * @return True if the player is temporary, false if not. */ public boolean isPlayerTemporary() { return getPlayer() instanceof TemporaryPlayer; } /** * 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. *

* Most listeners can deduce this by noting which listener method was invoked. * * @return TRUE if the packet was created by the server, FALSE if it was created by a client. */ public boolean isServerPacket() { return serverPacket; } /** * Retrieve the asynchronous marker. *

* If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following * asynchronous event, the marker is used to correctly pass the packet around to the different threads. *

* Note that if there are no asynchronous events that can receive this packet, the marker is NULL. * * @return The current asynchronous marker, or NULL. */ public AsyncMarker getAsyncMarker() { return asyncMarker; } /** * Set the asynchronous marker. *

* If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled to be * processed asynchronously with the given settings. *

* Note that if there are no asynchronous events that can receive this packet, the marker should be NULL. * * @param asyncMarker - the new asynchronous marker, or NULL. * @throws IllegalStateException If the current event is asynchronous. */ public void setAsyncMarker(AsyncMarker asyncMarker) { if (isAsynchronous()) { throw new IllegalStateException("The marker is immutable for asynchronous events"); } if (readOnly) { throw new IllegalStateException("The packet event is read-only."); } this.asyncMarker = asyncMarker; } /** * Determine if the current packet event is read only. *

* This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However, it is * still possible to modify the packet itself, as it would require too many resources to verify its integrity. *

* Thus, the packet is considered immutable if the packet event is read only. * * @return TRUE if it is, FALSE otherwise. */ public boolean isReadOnly() { return readOnly; } /** * Set the read-only state of this packet event. *

* This will be reset for every packet listener. * * @param readOnly - TRUE if it is read-only, FALSE otherwise. */ public void setReadOnly(boolean readOnly) { this.readOnly = readOnly; } /** * Determine if the packet event has been executed asynchronously or not. * * @return TRUE if this packet event is asynchronous, FALSE otherwise. */ public boolean isAsynchronous() { return asynchronous; } /** * Schedule a packet for sending or receiving after the current packet event is successful. *

* The packet will be added to {@link NetworkMarker#getScheduledPackets()}. * * @param scheduled - the packet to transmit or receive. */ public void schedule(ScheduledPacket scheduled) { getNetworkMarker().getScheduledPackets().add(scheduled); } /** * Unschedule a specific packet. * * @param scheduled - the scheduled packet. * @return TRUE if it was unscheduled, FALSE otherwise. */ public boolean unschedule(ScheduledPacket scheduled) { if (networkMarker != null) { return networkMarker.getScheduledPackets().remove(scheduled); } return false; } private void writeObject(ObjectOutputStream output) throws IOException { // Default serialization output.defaultWriteObject(); // Write the name of the player (or NULL if it's not set) Player player = getPlayer(); output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null); } private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { // Default deserialization input.defaultReadObject(); final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject(); // Better than nothing if (serialized != null) { // Store it, to prevent weak reference from cleaning up the reference Player offlinePlayer = serialized.getPlayer(); playerReference = new WeakReference<>(offlinePlayer); } } /** * Returns the packet event corresponding to the bundle if this packet is sent as a part of a bundle, t. Otherwise, null. * @return Corresponding packet event or null. */ @Nullable public PacketEvent getBundle() { return bundle; } @Override public String toString() { return "PacketEvent[player=" + getPlayer() + ", packet=" + packet + "]"; } }