Add support for Java serialization of PacketEvent. It might be useful.

This commit is contained in:
Kristian S. Stangeland 2012-09-25 23:09:02 +02:00
parent 7f69c0204d
commit e0c03186c3
3 changed files with 316 additions and 6 deletions

View File

@ -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<Object> structureModifier;
protected transient StructureModifier<Object> 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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -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<String, Method> lookup = new ConcurrentHashMap<String, Method>();
/**
* 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<String, Object> 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.
* <p>
* 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();
}
}