From e0c03186c3e0aed2bf43339eb49953896a4729eb Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 25 Sep 2012 23:09:02 +0200 Subject: [PATCH] Add support for Java serialization of PacketEvent. It might be useful. --- .../protocol/events/PacketContainer.java | 79 ++++++- .../protocol/events/PacketEvent.java | 27 ++- .../events/SerializedOfflinePlayer.java | 216 ++++++++++++++++++ 3 files changed, 316 insertions(+), 6 deletions(-) create mode 100644 ProtocolLib/src/com/comphenix/protocol/events/SerializedOfflinePlayer.java diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketContainer.java index 09207da9..34bd0a49 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketContainer.java @@ -17,6 +17,12 @@ package com.comphenix.protocol.events; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -40,13 +46,18 @@ import net.minecraft.server.Packet; * * @author Kristian */ -public class PacketContainer { +public class PacketContainer implements Serializable { - protected Packet handle; - protected int id; + /** + * Generated by Eclipse. + */ + private static final long serialVersionUID = 2074805748222377230L; + protected int id; + protected transient Packet handle; + // Current structure modifier - protected StructureModifier structureModifier; + protected transient StructureModifier structureModifier; // Check whether or not certain classes exists private static boolean hasWorldType = false; @@ -54,6 +65,10 @@ public class PacketContainer { // The getEntity method private static Method getEntity; + // Support for serialization + private static Method writeMethod; + private static Method readMethod; + static { try { Class.forName("net.minecraft.server.WorldType"); @@ -291,4 +306,60 @@ public class PacketContainer { public int getID() { return id; } + + private void writeObject(ObjectOutputStream output) throws IOException { + // Default serialization + output.defaultWriteObject(); + + // We'll take care of NULL packets as well + output.writeBoolean(handle != null); + + // Retrieve the write method by reflection + if (writeMethod == null) + writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class); + + try { + // Call the write-method + writeMethod.invoke(handle, new DataOutputStream(output)); + } catch (IllegalArgumentException e) { + throw new IOException("Minecraft packet doesn't support DataOutputStream", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Insufficient security privileges.", e); + } catch (InvocationTargetException e) { + throw new IOException("Could not serialize Minecraft packet.", e); + } + } + + private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { + // Default deserialization + input.defaultReadObject(); + + // Get structure modifier + structureModifier = StructureCache.getStructure(id); + + // Don't read NULL packets + if (input.readBoolean()) { + + // Create a default instance of the packet + handle = StructureCache.newPacket(id); + + // Retrieve the read method by reflection + if (readMethod == null) + readMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("read", DataInputStream.class); + + // Call the read method + try { + readMethod.invoke(handle, new DataInputStream(input)); + } catch (IllegalArgumentException e) { + throw new IOException("Minecraft packet doesn't support DataInputStream", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Insufficient security privileges.", e); + } catch (InvocationTargetException e) { + throw new IOException("Could not deserialize Minecraft packet.", e); + } + + // And we're done + structureModifier = structureModifier.withTarget(handle); + } + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java index e4734e17..956227ee 100644 --- a/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java +++ b/ProtocolLib/src/com/comphenix/protocol/events/PacketEvent.java @@ -17,6 +17,9 @@ package com.comphenix.protocol.events; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.EventObject; import org.bukkit.entity.Player; @@ -28,11 +31,11 @@ public class PacketEvent extends EventObject implements Cancellable { */ private static final long serialVersionUID = -5360289379097430620L; + private transient Player player; private PacketContainer packet; - private Player player; private boolean serverPacket; private boolean cancel; - + /** * Use the static constructors to create instances of this event. * @param source - the event source. @@ -125,4 +128,24 @@ public class PacketEvent extends EventObject implements Cancellable { public boolean isServerPacket() { return serverPacket; } + + private void writeObject(ObjectOutputStream output) throws IOException { + // Default serialization + output.defaultWriteObject(); + + // Write the name of the player (or NULL if it's not set) + output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null); + } + + private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { + // Default deserialization + input.defaultReadObject(); + + final SerializedOfflinePlayer offlinePlayer = (SerializedOfflinePlayer) input.readObject(); + + if (offlinePlayer != null) { + // Better than nothing + player = offlinePlayer.getProxyPlayer(); + } + } } diff --git a/ProtocolLib/src/com/comphenix/protocol/events/SerializedOfflinePlayer.java b/ProtocolLib/src/com/comphenix/protocol/events/SerializedOfflinePlayer.java new file mode 100644 index 00000000..4e222e57 --- /dev/null +++ b/ProtocolLib/src/com/comphenix/protocol/events/SerializedOfflinePlayer.java @@ -0,0 +1,216 @@ +package com.comphenix.protocol.events; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.OfflinePlayer; +import org.bukkit.World; +import org.bukkit.entity.Player; + +/** + * Represents a player object that can be serialized by Java. + * + * @author Kristian + */ +class SerializedOfflinePlayer implements OfflinePlayer, Serializable { + + /** + * Generated by Eclipse. + */ + private static final long serialVersionUID = -2728976288470282810L; + + private transient Location bedSpawnLocation; + + // Relevant data about an offline player + private String name; + private long firstPlayed; + private long lastPlayed; + private boolean operator; + private boolean banned; + private boolean playedBefore; + private boolean online; + private boolean whitelisted; + + // Proxy helper + private static Map lookup = new ConcurrentHashMap(); + + /** + * Constructor used by serialization. + */ + public SerializedOfflinePlayer() { + // Do nothing + } + + /** + * Initialize this serializable offline player from another player. + * @param offline - another player. + */ + public SerializedOfflinePlayer(OfflinePlayer offline) { + this.name = offline.getName(); + this.firstPlayed = offline.getFirstPlayed(); + this.lastPlayed = offline.getLastPlayed(); + this.operator = offline.isOp(); + this.banned = offline.isBanned(); + this.playedBefore = offline.hasPlayedBefore(); + this.online = offline.isOnline(); + this.whitelisted = offline.isWhitelisted(); + } + + @Override + public boolean isOp() { + return operator; + } + + @Override + public void setOp(boolean operator) { + this.operator = operator; + } + + @Override + public Map serialize() { + throw new UnsupportedOperationException(); + } + + @Override + public Location getBedSpawnLocation() { + return bedSpawnLocation; + } + + @Override + public long getFirstPlayed() { + return firstPlayed; + } + + @Override + public long getLastPlayed() { + return lastPlayed; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean hasPlayedBefore() { + return playedBefore; + } + + @Override + public boolean isBanned() { + return banned; + } + + @Override + public void setBanned(boolean banned) { + this.banned = banned; + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public boolean isWhitelisted() { + return whitelisted; + } + + @Override + public void setWhitelisted(boolean whitelisted) { + this.whitelisted = whitelisted; + } + + private void writeObject(ObjectOutputStream output) throws IOException { + output.defaultWriteObject(); + + // Serialize the bed spawn location + output.writeUTF(bedSpawnLocation.getWorld().getName()); + output.writeDouble(bedSpawnLocation.getX()); + output.writeDouble(bedSpawnLocation.getY()); + output.writeDouble(bedSpawnLocation.getZ()); + } + + private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { + input.defaultReadObject(); + + // Well, this is a problem + bedSpawnLocation = new Location( + getWorld(input.readUTF()), + input.readDouble(), + input.readDouble(), + input.readDouble() + ); + } + + private World getWorld(String name) { + try { + // Try to get the world at least + return Bukkit.getServer().getWorld(name); + } catch (Exception e) { + // Screw it + return null; + } + } + + @Override + public Player getPlayer() { + try { + // Try to get the real player underneath + return Bukkit.getServer().getPlayerExact(name); + } catch (Exception e) { + return getProxyPlayer(); + } + } + + /** + * Retrieve a player object that implements OfflinePlayer by refering to this object. + *

+ * All other methods cause an exception. + * @return Proxy object. + */ + public Player getProxyPlayer() { + + // Remember to initialize the method filter + if (lookup.size() == 0) { + // Add all public methods + for (Method method : OfflinePlayer.class.getMethods()) { + lookup.put(method.getName(), method); + } + } + + // MORE CGLIB magic! + Enhancer ex = new Enhancer(); + ex.setSuperclass(Player.class); + ex.setCallback(new MethodInterceptor() { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + + // There's no overloaded methods, so we don't care + Method offlineMethod = lookup.get(method.getName()); + + // Ignore all other methods + if (offlineMethod == null) { + throw new UnsupportedOperationException( + "The method " + method.getName() + " is not supported for offline players."); + } + + // Invoke our on method + return offlineMethod.invoke(SerializedOfflinePlayer.this, args); + } + }); + + return (Player) ex.create(); + } +}