Handling the case where someone is writing a NULL element to

a equivalent converter.
This commit is contained in:
Kristian S. Stangeland 2012-10-11 23:57:45 +02:00
parent 8bd7f75a6d
commit 57add8e26f
2 changed files with 444 additions and 409 deletions

View File

@ -1,379 +1,414 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.events; package com.comphenix.protocol.events;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.WorldType; import org.bukkit.WorldType;
import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.injector.StructureCache; import com.comphenix.protocol.injector.StructureCache;
import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import net.minecraft.server.Packet; import net.minecraft.server.Packet;
/** /**
* Represents a Minecraft packet indirectly. * Represents a Minecraft packet indirectly.
* *
* @author Kristian * @author Kristian
*/ */
public class PacketContainer implements Serializable { public class PacketContainer implements Serializable {
/** /**
* Generated by Eclipse. * Generated by Eclipse.
*/ */
private static final long serialVersionUID = 2074805748222377230L; private static final long serialVersionUID = 2074805748222377230L;
protected int id; protected int id;
protected transient Packet handle; protected transient Packet handle;
// Current structure modifier // Current structure modifier
protected transient StructureModifier<Object> structureModifier; protected transient StructureModifier<Object> structureModifier;
// Check whether or not certain classes exists // Check whether or not certain classes exists
private static boolean hasWorldType = false; private static boolean hasWorldType = false;
// The getEntity method // The getEntity method
private static Method getEntity; private static Method getEntity;
// Support for serialization // Support for serialization
private static Method writeMethod; private static Method writeMethod;
private static Method readMethod; private static Method readMethod;
static { static {
try { try {
Class.forName("net.minecraft.server.WorldType"); Class.forName("net.minecraft.server.WorldType");
hasWorldType = true; hasWorldType = true;
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
} }
} }
/** /**
* Creates a packet container for a new packet. * Creates a packet container for a new packet.
* @param id - ID of the packet to create. * @param id - ID of the packet to create.
*/ */
public PacketContainer(int id) { public PacketContainer(int id) {
this(id, StructureCache.newPacket(id)); this(id, StructureCache.newPacket(id));
} }
/** /**
* Creates a packet container for an existing packet. * Creates a packet container for an existing packet.
* @param id - ID of the given packet. * @param id - ID of the given packet.
* @param handle - contained packet. * @param handle - contained packet.
*/ */
public PacketContainer(int id, Packet handle) { public PacketContainer(int id, Packet handle) {
this(id, handle, StructureCache.getStructure(id).withTarget(handle)); this(id, handle, StructureCache.getStructure(id).withTarget(handle));
} }
/** /**
* Creates a packet container for an existing packet. * Creates a packet container for an existing packet.
* @param id - ID of the given packet. * @param id - ID of the given packet.
* @param handle - contained packet. * @param handle - contained packet.
* @param structure - structure modifier. * @param structure - structure modifier.
*/ */
public PacketContainer(int id, Packet handle, StructureModifier<Object> structure) { public PacketContainer(int id, Packet handle, StructureModifier<Object> structure) {
if (handle == null) if (handle == null)
throw new IllegalArgumentException("handle cannot be null."); throw new IllegalArgumentException("handle cannot be null.");
this.id = id; this.id = id;
this.handle = handle; this.handle = handle;
this.structureModifier = structure; this.structureModifier = structure;
} }
/** /**
* Retrieves the underlying Minecraft packet. * Retrieves the underlying Minecraft packet.
* @return Underlying Minecraft packet. * @return Underlying Minecraft packet.
*/ */
public Packet getHandle() { public Packet getHandle() {
return handle; return handle;
} }
/** /**
* Retrieves the generic structure modifier for this packet. * Retrieves the generic structure modifier for this packet.
* @return Structure modifier. * @return Structure modifier.
*/ */
public StructureModifier<Object> getModifier() { public StructureModifier<Object> getModifier() {
return structureModifier; return structureModifier;
} }
/** /**
* Retrieves a read/write structure for every field with the given type. * Retrieves a read/write structure for every field with the given type.
* @param primitiveType - the type to find. * @param primitiveType - the type to find.
* @return A modifier for this specific type. * @return A modifier for this specific type.
*/ */
public <T> StructureModifier<T> getSpecificModifier(Class<T> primitiveType) { public <T> StructureModifier<T> getSpecificModifier(Class<T> primitiveType) {
return structureModifier.withType(primitiveType); return structureModifier.withType(primitiveType);
} }
/** /**
* Retrieves a read/write structure for ItemStack. * Retrieves a read/write structure for ItemStack.
* <p> * <p>
* This modifier will automatically marshall between the Bukkit ItemStack and the * This modifier will automatically marshall between the Bukkit ItemStack and the
* internal Minecraft ItemStack. * internal Minecraft ItemStack.
* @return A modifier for ItemStack fields. * @return A modifier for ItemStack fields.
*/ */
public StructureModifier<ItemStack> getItemModifier() { public StructureModifier<ItemStack> getItemModifier() {
// Convert from and to the Bukkit wrapper // Convert from and to the Bukkit wrapper
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class, new EquivalentConverter<ItemStack>() { return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class,
public Object getGeneric(ItemStack specific) { getIgnoreNull(new EquivalentConverter<ItemStack>() {
return toStackNMS(specific); public Object getGeneric(ItemStack specific) {
} return toStackNMS(specific);
}
@Override
public ItemStack getSpecific(Object generic) { @Override
return new CraftItemStack((net.minecraft.server.ItemStack) generic); public ItemStack getSpecific(Object generic) {
} return new CraftItemStack((net.minecraft.server.ItemStack) generic);
}
@Override
public Class<ItemStack> getSpecificType() { @Override
return ItemStack.class; public Class<ItemStack> getSpecificType() {
} return ItemStack.class;
}); }
} }));
}
/**
* Retrieves a read/write structure for arrays of ItemStacks. /**
* <p> * Retrieves a read/write structure for arrays of ItemStacks.
* This modifier will automatically marshall between the Bukkit ItemStack and the * <p>
* internal Minecraft ItemStack. * This modifier will automatically marshall between the Bukkit ItemStack and the
* @return A modifier for ItemStack array fields. * internal Minecraft ItemStack.
*/ * @return A modifier for ItemStack array fields.
public StructureModifier<ItemStack[]> getItemArrayModifier() { */
// Convert to and from the Bukkit wrapper public StructureModifier<ItemStack[]> getItemArrayModifier() {
return structureModifier.<ItemStack[]>withType(net.minecraft.server.ItemStack[].class, new EquivalentConverter<ItemStack[]>() { // Convert to and from the Bukkit wrapper
public Object getGeneric(ItemStack[] specific) { return structureModifier.<ItemStack[]>withType(
net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length]; net.minecraft.server.ItemStack[].class,
getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
// Unwrap every item
for (int i = 0; i < result.length; i++) { public Object getGeneric(ItemStack[] specific) {
result[i] = toStackNMS(specific[i]); net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length];
}
return result; // Unwrap every item
} for (int i = 0; i < result.length; i++) {
result[i] = toStackNMS(specific[i]);
@Override }
public ItemStack[] getSpecific(Object generic) { return result;
net.minecraft.server.ItemStack[] input = (net.minecraft.server.ItemStack[]) generic; }
ItemStack[] result = new ItemStack[input.length];
@Override
// Add the wrapper public ItemStack[] getSpecific(Object generic) {
for (int i = 0; i < result.length; i++) { net.minecraft.server.ItemStack[] input = (net.minecraft.server.ItemStack[]) generic;
result[i] = new CraftItemStack(input[i]); ItemStack[] result = new ItemStack[input.length];
}
return result; // Add the wrapper
} for (int i = 0; i < result.length; i++) {
result[i] = new CraftItemStack(input[i]);
@Override }
public Class<ItemStack[]> getSpecificType() { return result;
return ItemStack[].class; }
}
}); @Override
} public Class<ItemStack[]> getSpecificType() {
return ItemStack[].class;
/** }
* Convert an item stack to the NMS equivalent. }));
* @param stack - Bukkit stack to convert. }
* @return A bukkit stack.
*/ /**
private net.minecraft.server.ItemStack toStackNMS(ItemStack stack) { * Convert an item stack to the NMS equivalent.
// We must be prepared for an object that simply implements ItemStcak * @param stack - Bukkit stack to convert.
if (stack instanceof CraftItemStack) { * @return A bukkit stack.
return ((CraftItemStack) stack).getHandle(); */
} else { private net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
return (new CraftItemStack(stack)).getHandle(); // We must be prepared for an object that simply implements ItemStcak
} if (stack instanceof CraftItemStack) {
} return ((CraftItemStack) stack).getHandle();
} else {
/** return (new CraftItemStack(stack)).getHandle();
* Retrieves a read/write structure for the world type enum. }
* <p> }
* This modifier will automatically marshall between the Bukkit world type and the
* internal Minecraft world type. /**
* @return A modifier for world type fields. * Retrieves a read/write structure for the world type enum.
*/ * <p>
public StructureModifier<WorldType> getWorldTypeModifier() { * This modifier will automatically marshall between the Bukkit world type and the
* internal Minecraft world type.
if (!hasWorldType) { * @return A modifier for world type fields.
// We couldn't find the Minecraft equivalent */
return structureModifier.withType(null); public StructureModifier<WorldType> getWorldTypeModifier() {
}
if (!hasWorldType) {
// Convert to and from the Bukkit wrapper // We couldn't find the Minecraft equivalent
return structureModifier.<WorldType>withType(net.minecraft.server.WorldType.class, new EquivalentConverter<WorldType>() { return structureModifier.withType(null);
@Override }
public Object getGeneric(WorldType specific) {
return net.minecraft.server.WorldType.getType(specific.getName()); // Convert to and from the Bukkit wrapper
} return structureModifier.<WorldType>withType(
net.minecraft.server.WorldType.class,
@Override getIgnoreNull(new EquivalentConverter<WorldType>() {
public WorldType getSpecific(Object generic) {
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic; @Override
return WorldType.getByName(type.name()); public Object getGeneric(WorldType specific) {
} return net.minecraft.server.WorldType.getType(specific.getName());
}
@Override
public Class<WorldType> getSpecificType() { @Override
return WorldType.class; public WorldType getSpecific(Object generic) {
} net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
}); return WorldType.getByName(type.name());
} }
/** @Override
* Retrieves a read/write structure for entity objects. public Class<WorldType> getSpecificType() {
* <p> return WorldType.class;
* Note that entities are transmitted by integer ID, and the type may not be enough }
* to distinguish between entities and other values. Thus, this structure modifier }));
* MAY return null or invalid entities for certain fields. Using the correct index }
* is essential.
* /**
* @return A modifier entity types. * Retrieves a read/write structure for entity objects.
*/ * <p>
public StructureModifier<Entity> getEntityModifier(World world) { * Note that entities are transmitted by integer ID, and the type may not be enough
* to distinguish between entities and other values. Thus, this structure modifier
final Object worldServer = ((CraftWorld) world).getHandle(); * MAY return null or invalid entities for certain fields. Using the correct index
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class; * is essential.
final World worldCopy = world; *
* @return A modifier entity types.
if (getEntity == null) */
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters( public StructureModifier<Entity> getEntityModifier(World world) {
"getEntity", nmsEntityClass, new Class[] { int.class });
final Object worldServer = ((CraftWorld) world).getHandle();
// Convert to and from the Bukkit wrapper final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
return structureModifier.<Entity>withType(int.class, new EquivalentConverter<Entity>() { final World worldCopy = world;
@Override
public Object getGeneric(Entity specific) { if (getEntity == null)
// Simple enough getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
return specific.getEntityId(); "getEntity", nmsEntityClass, new Class[] { int.class });
}
// Convert to and from the Bukkit wrapper
@Override return structureModifier.<Entity>withType(
public Entity getSpecific(Object generic) { int.class,
try { getIgnoreNull(new EquivalentConverter<Entity>() {
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
getEntity.invoke(worldServer, generic); @Override
Integer id = (Integer) generic; public Object getGeneric(Entity specific) {
// Simple enough
// Attempt to get the Bukkit entity return specific.getEntityId();
if (nmsEntity != null) { }
return nmsEntity.getBukkitEntity();
} else { @Override
// Maybe it's a player that's just logged in? Try a search public Entity getSpecific(Object generic) {
for (Player player : worldCopy.getPlayers()) { try {
if (player.getEntityId() == id) { net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
return player; getEntity.invoke(worldServer, generic);
} Integer id = (Integer) generic;
}
// Attempt to get the Bukkit entity
System.out.println("Entity doesn't exist."); if (nmsEntity != null) {
return null; return nmsEntity.getBukkitEntity();
} } else {
// Maybe it's a player that's just logged in? Try a search
} catch (IllegalArgumentException e) { for (Player player : worldCopy.getPlayers()) {
throw new RuntimeException("Incorrect arguments detected.", e); if (player.getEntityId() == id) {
} catch (IllegalAccessException e) { return player;
throw new RuntimeException("Cannot read field due to a security limitation.", e); }
} catch (InvocationTargetException e) { }
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
} System.out.println("Entity doesn't exist.");
} return null;
}
@Override
public Class<Entity> getSpecificType() { } catch (IllegalArgumentException e) {
return Entity.class; throw new RuntimeException("Incorrect arguments detected.", e);
} } catch (IllegalAccessException e) {
}); throw new RuntimeException("Cannot read field due to a security limitation.", e);
} } catch (InvocationTargetException e) {
throw new RuntimeException("Error occured in Minecraft method.", e.getCause());
/** }
* Retrieves the ID of this packet. }
* @return Packet ID.
*/ @Override
public int getID() { public Class<Entity> getSpecificType() {
return id; return Entity.class;
} }
}));
private void writeObject(ObjectOutputStream output) throws IOException { }
// Default serialization
output.defaultWriteObject(); private <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
// Automatically wrap all parameters to the delegate with a NULL check
// We'll take care of NULL packets as well return new EquivalentConverter<TType>() {
output.writeBoolean(handle != null); public Object getGeneric(TType specific) {
if (specific != null)
// Retrieve the write method by reflection return delegate.getGeneric(specific);
if (writeMethod == null) else
writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class); return null;
}
try {
// Call the write-method @Override
writeMethod.invoke(handle, new DataOutputStream(output)); public TType getSpecific(Object generic) {
} catch (IllegalArgumentException e) { if (generic != null)
throw new IOException("Minecraft packet doesn't support DataOutputStream", e); return delegate.getSpecific(generic);
} catch (IllegalAccessException e) { else
throw new RuntimeException("Insufficient security privileges.", e); return null;
} catch (InvocationTargetException e) { }
throw new IOException("Could not serialize Minecraft packet.", e);
} @Override
} public Class<TType> getSpecificType() {
return delegate.getSpecificType();
private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException { }
// Default deserialization };
input.defaultReadObject(); }
// Get structure modifier /**
structureModifier = StructureCache.getStructure(id); * Retrieves the ID of this packet.
* @return Packet ID.
// Don't read NULL packets */
if (input.readBoolean()) { public int getID() {
return id;
// Create a default instance of the packet }
handle = StructureCache.newPacket(id);
private void writeObject(ObjectOutputStream output) throws IOException {
// Retrieve the read method by reflection // Default serialization
if (readMethod == null) output.defaultWriteObject();
readMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("read", DataInputStream.class);
// We'll take care of NULL packets as well
// Call the read method output.writeBoolean(handle != null);
try {
readMethod.invoke(handle, new DataInputStream(input)); // Retrieve the write method by reflection
} catch (IllegalArgumentException e) { if (writeMethod == null)
throw new IOException("Minecraft packet doesn't support DataInputStream", e); writeMethod = FuzzyReflection.fromObject(handle).getMethodByParameters("write", DataOutputStream.class);
} catch (IllegalAccessException e) {
throw new RuntimeException("Insufficient security privileges.", e); try {
} catch (InvocationTargetException e) { // Call the write-method
throw new IOException("Could not deserialize Minecraft packet.", e); writeMethod.invoke(handle, new DataOutputStream(output));
} } catch (IllegalArgumentException e) {
throw new IOException("Minecraft packet doesn't support DataOutputStream", e);
// And we're done } catch (IllegalAccessException e) {
structureModifier = structureModifier.withTarget(handle); 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

@ -1,30 +1,30 @@
/* /*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland * Copyright (C) 2012 Kristian S. Stangeland
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * 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 * GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version. * 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; * 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. * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details. * 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; * 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 * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA * 02111-1307 USA
*/ */
package com.comphenix.protocol.reflect; package com.comphenix.protocol.reflect;
/** /**
* Interface that converts generic objects into types and back. * Interface that converts generic objects into types and back.
* *
* @author Kristian * @author Kristian
* @param <TType> The specific type. * @param <TType> The specific type.
*/ */
public interface EquivalentConverter<TType> { public interface EquivalentConverter<TType> {
public TType getSpecific(Object generic); public TType getSpecific(Object generic);
public Object getGeneric(TType specific); public Object getGeneric(TType specific);
public Class<TType> getSpecificType(); public Class<TType> getSpecificType();
} }