Merge branch 'master' into gh-pages

This commit is contained in:
Kristian S. Stangeland 2012-11-13 17:46:06 +01:00
commit 5c2e692a90
25 changed files with 1833 additions and 290 deletions

View File

@ -4,7 +4,7 @@
<groupId>com.comphenix.protocol</groupId> <groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId> <artifactId>ProtocolLib</artifactId>
<name>ProtocolLib</name> <name>ProtocolLib</name>
<version>1.5.1</version> <version>1.6.0</version>
<description>Provides read/write access to the Minecraft protocol.</description> <description>Provides read/write access to the Minecraft protocol.</description>
<url>http://dev.bukkit.org/server-mods/protocollib/</url> <url>http://dev.bukkit.org/server-mods/protocollib/</url>
<developers> <developers>

View File

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.comphenix.protocol</groupId> <groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId> <artifactId>ProtocolLib</artifactId>
<version>1.5.1</version> <version>1.6.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<description>Provides read/write access to the Minecraft protocol.</description> <description>Provides read/write access to the Minecraft protocol.</description>

View File

@ -19,6 +19,9 @@ import com.comphenix.protocol.reflect.compiler.StructureCompiler;
import com.comphenix.protocol.reflect.instances.CollectionGenerator; import com.comphenix.protocol.reflect.instances.CollectionGenerator;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; import com.comphenix.protocol.reflect.instances.PrimitiveGenerator;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
/** /**
* Used to fix ClassLoader leaks that may lead to filling up the permanent generation. * Used to fix ClassLoader leaks that may lead to filling up the permanent generation.
@ -45,7 +48,8 @@ class CleanupStaticMembers {
BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class, BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class,
PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class,
BackgroundCompiler.class, StructureCompiler.class, BackgroundCompiler.class, StructureCompiler.class,
ObjectCloner.class, Packets.Server.class, Packets.Client.class ObjectCloner.class, Packets.Server.class, Packets.Client.class,
ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class
}; };
String[] internalClasses = { String[] internalClasses = {

View File

@ -5,6 +5,8 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import com.comphenix.protocol.error.ErrorReporter;
/** /**
* Base class for all our commands. * Base class for all our commands.
* *
@ -17,12 +19,14 @@ abstract class CommandBase implements CommandExecutor {
private String permission; private String permission;
private String name; private String name;
private int minimumArgumentCount; private int minimumArgumentCount;
private ErrorReporter reporter;
public CommandBase(String permission, String name) { public CommandBase(ErrorReporter reporter, String permission, String name) {
this(permission, name, 0); this(reporter, permission, name, 0);
} }
public CommandBase(String permission, String name, int minimumArgumentCount) { public CommandBase(ErrorReporter reporter, String permission, String name, int minimumArgumentCount) {
this.reporter = reporter;
this.name = name; this.name = name;
this.permission = permission; this.permission = permission;
this.minimumArgumentCount = minimumArgumentCount; this.minimumArgumentCount = minimumArgumentCount;
@ -30,22 +34,58 @@ abstract class CommandBase implements CommandExecutor {
@Override @Override
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) { public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// Make sure we're dealing with the correct command try {
if (!command.getName().equalsIgnoreCase(name)) { // Make sure we're dealing with the correct command
return false; if (!command.getName().equalsIgnoreCase(name)) {
} return false;
if (!sender.hasPermission(permission)) { }
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command."); if (permission != null && !sender.hasPermission(permission)) {
return true; sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
} return true;
}
// Check argument length // Check argument length
if (args != null && args.length >= minimumArgumentCount) { if (args != null && args.length >= minimumArgumentCount) {
return handleCommand(sender, args); return handleCommand(sender, args);
} else { } else {
return false; return false;
}
} catch (Exception e) {
reporter.reportDetailed(this, "Cannot execute command " + name, e, sender, label, args);
return true;
} }
} }
/**
* Retrieve the permission necessary to execute this command.
* @return The permission, or NULL if not needed.
*/
public String getPermission() {
return permission;
}
/**
* Retrieve the primary name of this command.
* @return Primary name.
*/
public String getName() {
return name;
}
/**
* Retrieve the error reporter.
* @return Error reporter.
*/
protected ErrorReporter getReporter() {
return reporter;
}
/**
* Main implementation of this command.
* @param sender - command sender.
* @param args
* @return
*/
protected abstract boolean handleCommand(CommandSender sender, String[] args); protected abstract boolean handleCommand(CommandSender sender, String[] args);
} }

View File

@ -32,6 +32,7 @@ import com.comphenix.protocol.utility.ChatExtensions;
import com.google.common.collect.DiscreteDomains; import com.google.common.collect.DiscreteDomains;
import com.google.common.collect.Range; import com.google.common.collect.Range;
import com.google.common.collect.Ranges; import com.google.common.collect.Ranges;
import com.google.common.collect.Sets;
/** /**
* Handles the "packet" debug command. * Handles the "packet" debug command.
@ -76,11 +77,10 @@ class CommandPacket extends CommandBase {
private AbstractIntervalTree<Integer, DetailedPacketListener> clientListeners = createTree(ConnectionSide.CLIENT_SIDE); private AbstractIntervalTree<Integer, DetailedPacketListener> clientListeners = createTree(ConnectionSide.CLIENT_SIDE);
private AbstractIntervalTree<Integer, DetailedPacketListener> serverListeners = createTree(ConnectionSide.SERVER_SIDE); private AbstractIntervalTree<Integer, DetailedPacketListener> serverListeners = createTree(ConnectionSide.SERVER_SIDE);
public CommandPacket(Plugin plugin, Logger logger, ErrorReporter reporter, ProtocolManager manager) { public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, ProtocolManager manager) {
super(CommandBase.PERMISSION_ADMIN, NAME, 2); super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
this.plugin = plugin; this.plugin = plugin;
this.logger = logger; this.logger = logger;
this.reporter = reporter;
this.manager = manager; this.manager = manager;
this.chatter = new ChatExtensions(manager); this.chatter = new ChatExtensions(manager);
} }
@ -228,6 +228,12 @@ class CommandPacket extends CommandBase {
// Perform commands // Perform commands
if (subCommand == SubCommand.ADD) { if (subCommand == SubCommand.ADD) {
// The add command is dangerous - don't default on the connection side
if (args.length == 1) {
sender.sendMessage(ChatColor.RED + "Please specify a connectionn side.");
return false;
}
executeAddCommand(sender, side, detailed, ranges); executeAddCommand(sender, side, detailed, ranges);
} else if (subCommand == SubCommand.REMOVE) { } else if (subCommand == SubCommand.REMOVE) {
executeRemoveCommand(sender, side, detailed, ranges); executeRemoveCommand(sender, side, detailed, ranges);
@ -308,18 +314,19 @@ class CommandPacket extends CommandBase {
} }
private Set<Integer> getValidPackets(ConnectionSide side) throws FieldAccessException { private Set<Integer> getValidPackets(ConnectionSide side) throws FieldAccessException {
HashSet<Integer> supported = Sets.newHashSet();
if (side.isForClient()) if (side.isForClient())
return Packets.Client.getSupported(); supported.addAll(Packets.Client.getSupported());
else if (side.isForServer()) else if (side.isForServer())
return Packets.Server.getSupported(); supported.addAll(Packets.Server.getSupported());
else return supported;
throw new IllegalArgumentException("Illegal side: " + side);
} }
private Set<Integer> getNamedPackets(ConnectionSide side) { private Set<Integer> getNamedPackets(ConnectionSide side) {
Set<Integer> valids = null; Set<Integer> valids = null;
Set<Integer> result = null; Set<Integer> result = Sets.newHashSet();
try { try {
valids = getValidPackets(side); valids = getValidPackets(side);
@ -329,11 +336,9 @@ class CommandPacket extends CommandBase {
// Check connection side // Check connection side
if (side.isForClient()) if (side.isForClient())
result = Packets.Client.getRegistry().values(); result.addAll(Packets.Client.getRegistry().values());
else if (side.isForServer()) if (side.isForServer())
result = Packets.Server.getRegistry().values(); result.addAll(Packets.Server.getRegistry().values());
else
throw new IllegalArgumentException("Illegal side: " + side);
// Remove invalid packets // Remove invalid packets
result.retainAll(valids); result.retainAll(valids);
@ -378,10 +383,10 @@ class CommandPacket extends CommandBase {
} }
private void printInformation(PacketEvent event) { private void printInformation(PacketEvent event) {
String verb = side.isForClient() ? "Received" : "Sent"; String format = side.isForClient() ?
String shortDescription = String.format( "Received %s (%s) from %s" :
"%s %s (%s) from %s", "Sent %s (%s) to %s";
verb, String shortDescription = String.format(format,
Packets.getDeclaredName(event.getPacketID()), Packets.getDeclaredName(event.getPacketID()),
event.getPacketID(), event.getPacketID(),
event.getPlayer().getName() event.getPlayer().getName()
@ -438,7 +443,10 @@ class CommandPacket extends CommandBase {
// The trees will manage the listeners for us // The trees will manage the listeners for us
if (listener != null) { if (listener != null) {
getListenerTree(side).put(idStart, idStop, listener); if (side.isForClient())
clientListeners.put(idStart, idStop, listener);
if (side.isForServer())
serverListeners.put(idStart, idStop, listener);
return listener; return listener;
} else { } else {
throw new IllegalArgumentException("No packets found in the range " + idStart + " - " + idStop + "."); throw new IllegalArgumentException("No packets found in the range " + idStart + " - " + idStop + ".");
@ -448,17 +456,14 @@ class CommandPacket extends CommandBase {
public Set<AbstractIntervalTree<Integer, DetailedPacketListener>.Entry> removePacketListeners( public Set<AbstractIntervalTree<Integer, DetailedPacketListener>.Entry> removePacketListeners(
ConnectionSide side, int idStart, int idStop, boolean detailed) { ConnectionSide side, int idStart, int idStop, boolean detailed) {
// The interval tree will automatically remove the listeners for us HashSet<AbstractIntervalTree<Integer, DetailedPacketListener>.Entry> result = Sets.newHashSet();
return getListenerTree(side).remove(idStart, idStop);
}
private AbstractIntervalTree<Integer, DetailedPacketListener> getListenerTree(ConnectionSide side) { // The interval tree will automatically remove the listeners for us
if (side.isForClient()) if (side.isForClient())
return clientListeners; result.addAll(clientListeners.remove(idStart, idStop));
else if (side.isForServer()) if (side.isForServer())
return serverListeners; result.addAll(serverListeners.remove(idStart, idStop));
else return result;
throw new IllegalArgumentException("Not a legal connection side.");
} }
private SubCommand parseCommand(String[] args, int index) { private SubCommand parseCommand(String[] args, int index) {

View File

@ -4,6 +4,7 @@ import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.metrics.Updater; import com.comphenix.protocol.metrics.Updater;
import com.comphenix.protocol.metrics.Updater.UpdateResult; import com.comphenix.protocol.metrics.Updater.UpdateResult;
import com.comphenix.protocol.metrics.Updater.UpdateType; import com.comphenix.protocol.metrics.Updater.UpdateType;
@ -23,8 +24,8 @@ class CommandProtocol extends CommandBase {
private Updater updater; private Updater updater;
private ProtocolConfig config; private ProtocolConfig config;
public CommandProtocol(Plugin plugin, Updater updater, ProtocolConfig config) { public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) {
super(CommandBase.PERMISSION_ADMIN, NAME, 1); super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1);
this.plugin = plugin; this.plugin = plugin;
this.updater = updater; this.updater = updater;
this.config = config; this.config = config;
@ -51,8 +52,12 @@ class CommandProtocol extends CommandBase {
plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() { plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() {
@Override @Override
public void run() { public void run() {
UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true); try {
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString()); UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true);
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
} catch (Exception e) {
getReporter().reportDetailed(this, "Cannot check updates for ProtocolLib.", e, sender);
}
} }
}); });
@ -64,8 +69,12 @@ class CommandProtocol extends CommandBase {
plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() { plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() {
@Override @Override
public void run() { public void run() {
UpdateResult result = updater.update(UpdateType.DEFAULT, true); try {
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString()); UpdateResult result = updater.update(UpdateType.DEFAULT, true);
sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString());
} catch (Exception e) {
getReporter().reportDetailed(this, "Cannot update ProtocolLib.", e, sender);
}
} }
}); });

View File

@ -24,7 +24,7 @@ class ProtocolConfig {
private static final String UPDATER_LAST_TIME = "last"; private static final String UPDATER_LAST_TIME = "last";
// Defaults // Defaults
private static final long DEFAULT_UPDATER_DELAY = 60; private static final long DEFAULT_UPDATER_DELAY = 43200;
private Plugin plugin; private Plugin plugin;
private Configuration config; private Configuration config;
@ -47,7 +47,7 @@ class ProtocolConfig {
*/ */
public void reloadConfig() { public void reloadConfig() {
this.config = plugin.getConfig(); this.config = plugin.getConfig();
loadSections(true); loadSections(!loadingSections);
} }
/** /**
@ -55,9 +55,6 @@ class ProtocolConfig {
* @param copyDefaults - whether or not to copy configuration defaults. * @param copyDefaults - whether or not to copy configuration defaults.
*/ */
private void loadSections(boolean copyDefaults) { private void loadSections(boolean copyDefaults) {
if (loadingSections)
return;
if (config != null) { if (config != null) {
global = config.getConfigurationSection(SECTION_GLOBAL); global = config.getConfigurationSection(SECTION_GLOBAL);
} }
@ -72,10 +69,8 @@ class ProtocolConfig {
if (config != null) if (config != null)
config.options().copyDefaults(true); config.options().copyDefaults(true);
plugin.saveDefaultConfig(); plugin.saveDefaultConfig();
config = plugin.getConfig(); plugin.reloadConfig();
loadingSections = false; loadingSections = false;
loadSections(false);
// Inform the user // Inform the user
System.out.println("[ProtocolLib] Created default configuration."); System.out.println("[ProtocolLib] Created default configuration.");

View File

@ -75,6 +75,7 @@ public class ProtocolLibrary extends JavaPlugin {
// Updater // Updater
private Updater updater; private Updater updater;
private boolean updateDisabled;
// Logger // Logger
private Logger logger; private Logger logger;
@ -108,8 +109,8 @@ public class ProtocolLibrary extends JavaPlugin {
reporter.addGlobalParameter("manager", protocolManager); reporter.addGlobalParameter("manager", protocolManager);
// Initialize command handlers // Initialize command handlers
commandProtocol = new CommandProtocol(this, updater, config); commandProtocol = new CommandProtocol(reporter, this, updater, config);
commandPacket = new CommandPacket(this, logger, reporter, protocolManager); commandPacket = new CommandPacket(reporter, this, logger, protocolManager);
// Send logging information to player listeners too // Send logging information to player listeners too
broadcastUsers(PERMISSION_INFO); broadcastUsers(PERMISSION_INFO);
@ -127,6 +128,7 @@ public class ProtocolLibrary extends JavaPlugin {
@Override @Override
public void reloadConfig() { public void reloadConfig() {
super.reloadConfig(); super.reloadConfig();
// Reload configuration // Reload configuration
if (config != null) { if (config != null) {
config.reloadConfig(); config.reloadConfig();
@ -223,7 +225,9 @@ public class ProtocolLibrary extends JavaPlugin {
manager.sendProcessedPackets(tickCounter++, true); manager.sendProcessedPackets(tickCounter++, true);
// Check for updates too // Check for updates too
checkUpdates(); if (!updateDisabled) {
checkUpdates();
}
} }
}, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY); }, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY);
@ -238,15 +242,22 @@ public class ProtocolLibrary extends JavaPlugin {
// Ignore milliseconds - it's pointless // Ignore milliseconds - it's pointless
long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND; long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND;
// Should we update? try {
if (currentTime > config.getAutoLastTime() + config.getAutoDelay()) { long updateTime = config.getAutoLastTime() + config.getAutoDelay();
// Initiate the update as if it came from the console
if (config.isAutoDownload()) // Should we update?
commandProtocol.updateVersion(getServer().getConsoleSender()); if (currentTime > updateTime) {
else if (config.isAutoNotify()) // Initiate the update as if it came from the console
commandProtocol.checkVersion(getServer().getConsoleSender()); if (config.isAutoDownload())
else commandProtocol.updateVersion(getServer().getConsoleSender());
commandProtocol.updateFinished(); else if (config.isAutoNotify())
commandProtocol.checkVersion(getServer().getConsoleSender());
else
commandProtocol.updateFinished();
}
} catch (Exception e) {
reporter.reportDetailed(this, "Cannot perform automatic updates.", e);
updateDisabled = true;
} }
} }

View File

@ -20,6 +20,7 @@ package com.comphenix.protocol;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.bukkit.World;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@ -105,6 +106,15 @@ public interface ProtocolManager extends PacketStream {
*/ */
public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException; public void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException;
/**
* Retrieve the associated entity.
* @param container - the world the entity belongs to.
* @param id - the unique ID of the entity.
* @return The associated entity.
* @throws FieldAccessException Reflection failed.
*/
public Entity getEntityFromID(World container, int id) throws FieldAccessException;
/** /**
* Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners. * Retrieves a immutable set containing the ID of the sent server packets that will be observed by listeners.
* @return Every filtered server packet. * @return Every filtered server packet.

View File

@ -27,7 +27,6 @@ import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PlayerLoggedOutException; import com.comphenix.protocol.injector.PlayerLoggedOutException;
import com.comphenix.protocol.injector.SortedPacketListenerList;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
/** /**

View File

@ -104,7 +104,7 @@ public abstract class PacketAdapter implements PacketListener {
* @param packets - the packet IDs the listener is looking for. * @param packets - the packet IDs the listener is looking for.
*/ */
public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) { public PacketAdapter(Plugin plugin, ConnectionSide connectionSide, GamePhase gamePhase, Integer... packets) {
this(plugin, connectionSide, ListenerPriority.NORMAL, GamePhase.PLAYING, packets); this(plugin, connectionSide, ListenerPriority.NORMAL, gamePhase, packets);
} }
/** /**

View File

@ -25,21 +25,22 @@ 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 java.util.Collection;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.Server;
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.inventory.CraftItemStack;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
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 com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ChunkPosition;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import net.minecraft.server.Packet; import net.minecraft.server.Packet;
@ -61,24 +62,10 @@ public class PacketContainer implements Serializable {
// Current structure modifier // Current structure modifier
protected transient StructureModifier<Object> structureModifier; protected transient StructureModifier<Object> structureModifier;
// Check whether or not certain classes exists
private static boolean hasWorldType = false;
// The getEntity method
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 {
try {
Class.forName("net.minecraft.server.WorldType");
hasWorldType = true;
} 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.
@ -136,6 +123,77 @@ public class PacketContainer implements Serializable {
return structureModifier.withType(primitiveType); return structureModifier.withType(primitiveType);
} }
/**
* Retrieves a read/write structure for every byte field.
* @return A modifier for every byte field.
*/
public StructureModifier<Byte> getBytes() {
return structureModifier.withType(byte.class);
}
/**
* Retrieves a read/write structure for every short field.
* @return A modifier for every short field.
*/
public StructureModifier<Short> getShorts() {
return structureModifier.withType(short.class);
}
/**
* Retrieves a read/write structure for every integer field.
* @return A modifier for every integer field.
*/
public StructureModifier<Integer> getIntegers() {
return structureModifier.withType(int.class);
}
/**
* Retrieves a read/write structure for every long field.
* @return A modifier for every long field.
*/
public StructureModifier<Long> getLongs() {
return structureModifier.withType(long.class);
}
/**
* Retrieves a read/write structure for every float field.
* @return A modifier for every float field.
*/
public StructureModifier<Float> getFloat() {
return structureModifier.withType(float.class);
}
/**
* Retrieves a read/write structure for every double field.
* @return A modifier for every double field.
*/
public StructureModifier<Double> getDoubles() {
return structureModifier.withType(double.class);
}
/**
* Retrieves a read/write structure for every String field.
* @return A modifier for every String field.
*/
public StructureModifier<String> getStrings() {
return structureModifier.withType(String.class);
}
/**
* Retrieves a read/write structure for every String array field.
* @return A modifier for every String array field.
*/
public StructureModifier<String[]> getStringArrays() {
return structureModifier.withType(String[].class);
}
/**
* Retrieves a read/write structure for every byte array field.
* @return A modifier for every byte array field.
*/
public StructureModifier<byte[]> getByteArrays() {
return structureModifier.withType(byte[].class);
}
/** /**
* Retrieves a read/write structure for ItemStack. * Retrieves a read/write structure for ItemStack.
* <p> * <p>
@ -145,22 +203,8 @@ public class PacketContainer implements Serializable {
*/ */
public StructureModifier<ItemStack> getItemModifier() { public StructureModifier<ItemStack> getItemModifier() {
// Convert to and from the Bukkit wrapper // Convert to and from the Bukkit wrapper
return structureModifier.<ItemStack>withType(net.minecraft.server.ItemStack.class, return structureModifier.<ItemStack>withType(
getIgnoreNull(new EquivalentConverter<ItemStack>() { net.minecraft.server.ItemStack.class, BukkitConverters.getItemStackConverter());
public Object getGeneric(ItemStack specific) {
return toStackNMS(specific);
}
@Override
public ItemStack getSpecific(Object generic) {
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
}
@Override
public Class<ItemStack> getSpecificType() {
return ItemStack.class;
}
}));
} }
/** /**
@ -171,17 +215,21 @@ public class PacketContainer implements Serializable {
* @return A modifier for ItemStack array fields. * @return A modifier for ItemStack array fields.
*/ */
public StructureModifier<ItemStack[]> getItemArrayModifier() { public StructureModifier<ItemStack[]> getItemArrayModifier() {
final EquivalentConverter<ItemStack> stackConverter = BukkitConverters.getItemStackConverter();
// Convert to and from the Bukkit wrapper // Convert to and from the Bukkit wrapper
return structureModifier.<ItemStack[]>withType( return structureModifier.<ItemStack[]>withType(
net.minecraft.server.ItemStack[].class, net.minecraft.server.ItemStack[].class,
getIgnoreNull(new EquivalentConverter<ItemStack[]>() { BukkitConverters.getIgnoreNull(new EquivalentConverter<ItemStack[]>() {
public Object getGeneric(ItemStack[] specific) { public Object getGeneric(Class<?>genericType, ItemStack[] specific) {
net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length]; net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length];
// Unwrap every item // Unwrap every item
for (int i = 0; i < result.length; i++) { for (int i = 0; i < result.length; i++) {
result[i] = toStackNMS(specific[i]); result[i] = (net.minecraft.server.ItemStack) stackConverter.getGeneric(
net.minecraft.server.ItemStack.class, specific[i]);
} }
return result; return result;
} }
@ -193,7 +241,7 @@ public class PacketContainer implements Serializable {
// Add the wrapper // Add the wrapper
for (int i = 0; i < result.length; i++) { for (int i = 0; i < result.length; i++) {
result[i] = new CraftItemStack(input[i]); result[i] = stackConverter.getSpecific(input[i]);
} }
return result; return result;
} }
@ -205,20 +253,6 @@ public class PacketContainer implements Serializable {
})); }));
} }
/**
* 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) {
// 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. * Retrieves a read/write structure for the world type enum.
* <p> * <p>
@ -227,33 +261,21 @@ public class PacketContainer implements Serializable {
* @return A modifier for world type fields. * @return A modifier for world type fields.
*/ */
public StructureModifier<WorldType> getWorldTypeModifier() { public StructureModifier<WorldType> getWorldTypeModifier() {
if (!hasWorldType) {
// We couldn't find the Minecraft equivalent
return structureModifier.withType(null);
}
// Convert to and from the Bukkit wrapper // Convert to and from the Bukkit wrapper
return structureModifier.<WorldType>withType( return structureModifier.<WorldType>withType(
net.minecraft.server.WorldType.class, net.minecraft.server.WorldType.class,
getIgnoreNull(new EquivalentConverter<WorldType>() { BukkitConverters.getWorldTypeConverter());
}
@Override /**
public Object getGeneric(WorldType specific) { * Retrieves a read/write structure for data watchers.
return net.minecraft.server.WorldType.getType(specific.getName()); * @return A modifier for data watchers.
} */
public StructureModifier<WrappedDataWatcher> getDataWatcherModifier() {
@Override // Convert to and from the Bukkit wrapper
public WorldType getSpecific(Object generic) { return structureModifier.<WrappedDataWatcher>withType(
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic; net.minecraft.server.DataWatcher.class,
return WorldType.getByName(type.name()); BukkitConverters.getDataWatcherConverter());
}
@Override
public Class<WorldType> getSpecificType() {
return WorldType.class;
}
}));
} }
/** /**
@ -267,89 +289,54 @@ public class PacketContainer implements Serializable {
* @return A modifier entity types. * @return A modifier entity types.
*/ */
public StructureModifier<Entity> getEntityModifier(World world) { public StructureModifier<Entity> getEntityModifier(World world) {
final Object worldServer = ((CraftWorld) world).getHandle();
final Class<?> nmsEntityClass = net.minecraft.server.Entity.class;
if (getEntity == null)
getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters(
"getEntity", nmsEntityClass, new Class[] { int.class });
// Convert to and from the Bukkit wrapper // Convert to and from the Bukkit wrapper
return structureModifier.<Entity>withType( return structureModifier.<Entity>withType(
int.class, int.class, BukkitConverters.getEntityConverter(world));
getIgnoreNull(new EquivalentConverter<Entity>() {
@Override
public Object getGeneric(Entity specific) {
// Simple enough
return specific.getEntityId();
}
@Override
public Entity getSpecific(Object generic) {
try {
net.minecraft.server.Entity nmsEntity = (net.minecraft.server.Entity)
getEntity.invoke(worldServer, generic);
Integer id = (Integer) generic;
// Attempt to get the Bukkit entity
if (nmsEntity != null) {
return nmsEntity.getBukkitEntity();
} else {
Server server = Bukkit.getServer();
// Maybe it's a player that has just logged in? Try a search
if (server != null) {
for (Player player : server.getOnlinePlayers()) {
if (player.getEntityId() == id) {
return player;
}
}
}
return null;
}
} catch (IllegalArgumentException e) {
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());
}
}
@Override
public Class<Entity> getSpecificType() {
return Entity.class;
}
}));
} }
private <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) { /**
// Automatically wrap all parameters to the delegate with a NULL check * Retrieves a read/write structure for chunk positions.
return new EquivalentConverter<TType>() { * @return A modifier for a ChunkPosition.
public Object getGeneric(TType specific) { */
if (specific != null) public StructureModifier<ChunkPosition> getPositionModifier() {
return delegate.getGeneric(specific); // Convert to and from the Bukkit wrapper
else return structureModifier.withType(
return null; net.minecraft.server.ChunkPosition.class,
} ChunkPosition.getConverter());
}
@Override /**
public TType getSpecific(Object generic) { * Retrieves a read/write structure for collections of chunk positions.
if (generic != null) * <p>
return delegate.getSpecific(generic); * This modifier will automatically marshall between the visible ProtocolLib ChunkPosition and the
else * internal Minecraft ChunkPosition.
return null; * @return A modifier for ChunkPosition list fields.
} */
public StructureModifier<List<ChunkPosition>> getPositionCollectionModifier() {
// Convert to and from the ProtocolLib wrapper
return structureModifier.withType(
Collection.class,
BukkitConverters.getListConverter(
net.minecraft.server.ChunkPosition.class,
ChunkPosition.getConverter())
);
}
@Override /**
public Class<TType> getSpecificType() { * Retrieves a read/write structure for collections of watchable objects.
return delegate.getSpecificType(); * <p>
} * This modifier will automatically marshall between the visible WrappedWatchableObject and the
}; * internal Minecraft WatchableObject.
* @return A modifier for watchable object list fields.
*/
public StructureModifier<List<WrappedWatchableObject>> getWatchableCollectionModifier() {
// Convert to and from the ProtocolLib wrapper
return structureModifier.withType(
Collection.class,
BukkitConverters.getListConverter(
net.minecraft.server.WatchableObject.class,
BukkitConverters.getWatchableObjectConverter())
);
} }
/** /**

View File

@ -27,6 +27,8 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import net.minecraft.server.EntityTrackerEntry;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.CraftWorld;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
@ -83,8 +85,51 @@ class EntityUtilities {
* *
*/ */
public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException { public static void updateEntity(Entity entity, List<Player> observers) throws FieldAccessException {
try {
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId());
Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), entity.getEntityId());
World world = entity.getWorld(); if (trackedPlayersField == null) {
// This one is fairly easy
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
// Phew, finally there.
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
List<Object> nmsPlayers = unwrapBukkit(observers);
// trackEntity.trackedPlayers.clear();
trackedPlayers.removeAll(nmsPlayers);
// We have to rely on a NAME once again. Damn it.
if (scanPlayersMethod == null) {
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
}
//trackEntity.scanPlayers(server.players);
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
} catch (IllegalArgumentException e) {
throw e;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Exception occurred in Minecraft.", e);
} catch (SecurityException e) {
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e);
} catch (NoSuchMethodException e) {
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", e);
}
}
/**
* Retrieve the entity tracker entry given a ID.
* @param world - world server.
* @param entityID - entity ID.
* @return The entity tracker entry.
* @throws FieldAccessException
*/
private static Object getEntityTrackerEntry(World world, int entityID) throws FieldAccessException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Object worldServer = ((CraftWorld) world).getHandle(); Object worldServer = ((CraftWorld) world).getHandle();
// We have to rely on the class naming here. // We have to rely on the class naming here.
@ -149,40 +194,36 @@ class EntityUtilities {
} }
} }
// Wrap exceptions
try { try {
//EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId()); return hashGetMethod.invoke(trackedEntities, entityID);
Object trackerEntry = hashGetMethod.invoke(trackedEntities, entity.getEntityId());
if (trackedPlayersField == null) {
// This one is fairly easy
trackedPlayersField = FuzzyReflection.fromObject(trackerEntry).getFieldByType("java\\.util\\..*");
}
// Phew, finally there.
Collection<?> trackedPlayers = (Collection<?>) FieldUtils.readField(trackedPlayersField, trackerEntry, false);
List<Object> nmsPlayers = unwrapBukkit(observers);
// trackEntity.trackedPlayers.clear();
trackedPlayers.removeAll(nmsPlayers);
// We have to rely on a NAME once again. Damn it.
if (scanPlayersMethod == null) {
scanPlayersMethod = trackerEntry.getClass().getMethod("scanPlayers", List.class);
}
//trackEntity.scanPlayers(server.players);
scanPlayersMethod.invoke(trackerEntry, nmsPlayers);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw e; throw e;
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e); throw new FieldAccessException("Security limitation prevents access to 'get' method in IntHashMap", e);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
throw new RuntimeException("Exception occurred in Minecraft.", e); throw new RuntimeException("Exception occurred in Minecraft.", e);
} catch (SecurityException e) { }
throw new FieldAccessException("Security limitation prevents access to 'scanPlayers' method in trackerEntry.", e); }
} catch (NoSuchMethodException e) {
throw new FieldAccessException("Canot find 'scanPlayers' method. Is ProtocolLib up to date?", e); /**
* Retrieve entity from a ID, even it it's newly created.
* @return The asssociated entity.
* @throws FieldAccessException Reflection error.
*/
public static Entity getEntityFromID(World world, int entityID) throws FieldAccessException {
try {
EntityTrackerEntry trackerEntry = (EntityTrackerEntry) getEntityTrackerEntry(world, entityID);
// Handle NULL cases
if (trackerEntry != null && trackerEntry.tracker != null) {
return trackerEntry.tracker.getBukkitEntity();
} else {
return null;
}
} catch (Exception e) {
throw new FieldAccessException("Cannot find entity from ID.", e);
} }
} }

View File

@ -34,6 +34,7 @@ import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.MethodProxy;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Entity; import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -565,6 +566,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok
EntityUtilities.updateEntity(entity, observers); EntityUtilities.updateEntity(entity, observers);
} }
@Override
public Entity getEntityFromID(World container, int id) throws FieldAccessException {
return EntityUtilities.getEntityFromID(container, id);
}
/** /**
* Initialize the packet injection for every player. * Initialize the packet injection for every player.
* @param players - list of players to inject. * @param players - list of players to inject.

View File

@ -24,8 +24,25 @@ package com.comphenix.protocol.reflect;
* @param <TType> The specific type. * @param <TType> The specific type.
*/ */
public interface EquivalentConverter<TType> { public interface EquivalentConverter<TType> {
/**
* Retrieve a copy of the specific type using an instance of the generic type.
* @param generic - the generic type.
* @return The new specific type.
*/
public TType getSpecific(Object generic); public TType getSpecific(Object generic);
public Object getGeneric(TType specific);
/**
* Retrieve a copy of the generic type from a specific type.
* @param genericType - class or super class of the generic type.
* @param specific - the specific type we need to copy.
* @return A copy of the specific type.
*/
public Object getGeneric(Class<?> genericType, TType specific);
/**
* Due to type erasion, we need to explicitly keep a reference to the specific type.
* @return The specific type.
*/
public Class<TType> getSpecificType(); public Class<TType> getSpecificType();
} }

View File

@ -66,8 +66,10 @@ public class ObjectCloner {
// Copy every field // Copy every field
try { try {
for (int i = 0; i < modifierSource.size(); i++) { for (int i = 0; i < modifierSource.size(); i++) {
Object value = modifierSource.read(i); if (!modifierDest.isReadOnly(i)) {
modifierDest.write(i, value); Object value = modifierSource.read(i);
modifierDest.write(i, value);
}
// System.out.println(String.format("Writing value %s to %s", // System.out.println(String.format("Writing value %s to %s",
// value, modifier.getFields().get(i).getName())); // value, modifier.getFields().get(i).getName()));

View File

@ -61,6 +61,17 @@ public class StructureModifier<TField> {
// Whether or subclasses should handle conversion // Whether or subclasses should handle conversion
protected boolean customConvertHandling; protected boolean customConvertHandling;
// Whether or not to automatically compile the structure modifier
protected boolean useStructureCompiler;
/**
* Creates a structure modifier.
* @param targetType - the structure to modify.
*/
public StructureModifier(Class targetType) {
this(targetType, null, true);
}
/** /**
* Creates a structure modifier. * Creates a structure modifier.
* @param targetType - the structure to modify. * @param targetType - the structure to modify.
@ -68,10 +79,22 @@ public class StructureModifier<TField> {
* @param requireDefault - whether or not we will be using writeDefaults(). * @param requireDefault - whether or not we will be using writeDefaults().
*/ */
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) { public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault) {
this(targetType, superclassExclude, requireDefault, true);
}
/**
* Creates a structure modifier.
* @param targetType - the structure to modify.
* @param superclassExclude - a superclass to exclude.
* @param requireDefault - whether or not we will be using writeDefaults().
* @param useStructureModifier - whether or not to automatically compile this structure modifier.
*/
public StructureModifier(Class targetType, Class superclassExclude, boolean requireDefault, boolean useStructureCompiler) {
List<Field> fields = getFields(targetType, superclassExclude); List<Field> fields = getFields(targetType, superclassExclude);
Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>(); Map<Field, Integer> defaults = requireDefault ? generateDefaultFields(fields) : new HashMap<Field, Integer>();
initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap<Class, StructureModifier>()); initialize(targetType, Object.class, fields, defaults, null,
new ConcurrentHashMap<Class, StructureModifier>(), useStructureCompiler);
} }
/** /**
@ -87,7 +110,8 @@ public class StructureModifier<TField> {
*/ */
protected void initialize(StructureModifier<TField> other) { protected void initialize(StructureModifier<TField> other) {
initialize(other.targetType, other.fieldType, other.data, initialize(other.targetType, other.fieldType, other.data,
other.defaultFields, other.converter, other.subtypeCache); other.defaultFields, other.converter, other.subtypeCache,
other.useStructureCompiler);
} }
/** /**
@ -102,12 +126,31 @@ public class StructureModifier<TField> {
protected void initialize(Class targetType, Class fieldType, protected void initialize(Class targetType, Class fieldType,
List<Field> data, Map<Field, Integer> defaultFields, List<Field> data, Map<Field, Integer> defaultFields,
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) { EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache) {
initialize(targetType, fieldType, data, defaultFields, converter, subTypeCache, true);
}
/**
* Initialize every field of this class.
* @param targetType - type of the object we're reading and writing from.
* @param fieldType - the common type of the fields we're modifying.
* @param data - list of fields to modify.
* @param defaultFields - list of fields that will be automatically initialized.
* @param converter - converts between the common field type and the actual type the consumer expects.
* @param subTypeCache - a structure modifier cache.
* @param useStructureModifier - whether or not to automatically compile this structure modifier.
*/
protected void initialize(Class targetType, Class fieldType,
List<Field> data, Map<Field, Integer> defaultFields,
EquivalentConverter<TField> converter, Map<Class, StructureModifier> subTypeCache,
boolean useStructureCompiler) {
this.targetType = targetType; this.targetType = targetType;
this.fieldType = fieldType; this.fieldType = fieldType;
this.data = data; this.data = data;
this.defaultFields = defaultFields; this.defaultFields = defaultFields;
this.converter = converter; this.converter = converter;
this.subtypeCache = subTypeCache; this.subtypeCache = subTypeCache;
this.useStructureCompiler = useStructureCompiler;
} }
/** /**
@ -152,6 +195,52 @@ public class StructureModifier<TField> {
} }
} }
/**
* Determine whether or not a field is read-only (final).
* @param fieldIndex - index of the field.
* @return TRUE if the field by the given index is read-only, FALSE otherwise.
*/
public boolean isReadOnly(int fieldIndex) {
if (fieldIndex < 0 || fieldIndex >= data.size())
new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
return Modifier.isFinal(data.get(fieldIndex).getModifiers());
}
/**
* Set whether or not a field should be treated as read only.
* <p>
* Note that changing the read-only state to TRUE will only work if the current
* field was recently read-only or the current structure modifier hasn't been compiled yet.
*
* @param fieldIndex - index of the field.
* @param value - TRUE if this field should be read only, FALSE otherwise.
* @throws FieldAccessException If we cannot modify the read-only status.
*/
public void setReadOnly(int fieldIndex, boolean value) throws FieldAccessException {
if (fieldIndex < 0 || fieldIndex >= data.size())
new IllegalArgumentException("Index parameter is not within [0 - " + data.size() + ")");
try {
StructureModifier.setFinalState(data.get(fieldIndex), value);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot write read only status due to a security limitation.", e);
}
}
/**
* Alter the final status of a field.
* @param field - the field to change.
* @param isReadOnly - TRUE if the field should be read only, FALSE otherwise.
* @throws IllegalAccessException If an error occured.
*/
protected static void setFinalState(Field field, boolean isReadOnly) throws IllegalAccessException {
if (isReadOnly)
FieldUtils.writeField((Object) field, "modifiers", field.getModifiers() | Modifier.FINAL, true);
else
FieldUtils.writeField((Object) field, "modifiers", field.getModifiers() & ~Modifier.FINAL, true);
}
/** /**
* Writes the value of a field given its index. * Writes the value of a field given its index.
* @param fieldIndex - index of the field. * @param fieldIndex - index of the field.
@ -167,7 +256,7 @@ public class StructureModifier<TField> {
throw new IllegalStateException("Cannot write to a NULL target."); throw new IllegalStateException("Cannot write to a NULL target.");
// Use the converter, if it exists // Use the converter, if it exists
Object obj = needConversion() ? converter.getGeneric(value) : value; Object obj = needConversion() ? converter.getGeneric(getFieldType(fieldIndex), value) : value;
try { try {
FieldUtils.writeField(data.get(fieldIndex), target, obj, true); FieldUtils.writeField(data.get(fieldIndex), target, obj, true);
@ -179,6 +268,15 @@ public class StructureModifier<TField> {
return this; return this;
} }
/**
* Retrieve the type of a specified field.
* @param index - the index.
* @return The type of the given field.
*/
protected Class<?> getFieldType(int index) {
return data.get(index).getType();
}
/** /**
* Whether or not we should use the converter instance. * Whether or not we should use the converter instance.
* @return TRUE if we should, FALSE otherwise. * @return TRUE if we should, FALSE otherwise.
@ -281,7 +379,7 @@ public class StructureModifier<TField> {
subtypeCache.put(fieldType, result); subtypeCache.put(fieldType, result);
// Automatically compile the structure modifier // Automatically compile the structure modifier
if (BackgroundCompiler.getInstance() != null) if (useStructureCompiler && BackgroundCompiler.getInstance() != null)
BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType); BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType);
} }
} }
@ -353,7 +451,8 @@ public class StructureModifier<TField> {
StructureModifier<T> result = new StructureModifier<T>(); StructureModifier<T> result = new StructureModifier<T>();
result.initialize(targetType, fieldType, filtered, defaults, result.initialize(targetType, fieldType, filtered, defaults,
converter, new ConcurrentHashMap<Class, StructureModifier>()); converter, new ConcurrentHashMap<Class, StructureModifier>(),
useStructureCompiler);
return result; return result;
} }
@ -366,7 +465,7 @@ public class StructureModifier<TField> {
StructureModifier<TField> copy = new StructureModifier<TField>(); StructureModifier<TField> copy = new StructureModifier<TField>();
// Create a new instance // Create a new instance
copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache); copy.initialize(this);
copy.target = target; copy.target = target;
return copy; return copy;
} }
@ -424,9 +523,10 @@ public class StructureModifier<TField> {
for (Field field : fields) { for (Field field : fields) {
Class<?> type = field.getType(); Class<?> type = field.getType();
int modifier = field.getModifiers();
// First, ignore primitive fields // First, ignore primitive fields and final fields
if (!type.isPrimitive()) { if (!type.isPrimitive() && !Modifier.isFinal(modifier)) {
// Next, see if we actually can generate a default value // Next, see if we actually can generate a default value
if (generator.getDefault(type) != null) { if (generator.getDefault(type) != null) {
// If so, require it // If so, require it
@ -449,9 +549,9 @@ public class StructureModifier<TField> {
for (Field field : FuzzyReflection.fromClass(type, true).getFields()) { for (Field field : FuzzyReflection.fromClass(type, true).getFields()) {
int mod = field.getModifiers(); int mod = field.getModifiers();
// Ignore static, final and "abstract packet" fields // Ignore static and "abstract packet" fields
if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && ( if (!Modifier.isStatic(mod) &&
superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude) (superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude)
)) { )) {
result.add(field); result.add(field);

View File

@ -19,10 +19,12 @@ package com.comphenix.protocol.reflect.compiler;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Map; import java.util.Map;
import java.util.Set;
import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.DefaultInstances;
import com.google.common.collect.Sets;
/** /**
* Represents a compiled structure modifier. * Represents a compiled structure modifier.
@ -34,11 +36,33 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
// Used to compile instances of structure modifiers // Used to compile instances of structure modifiers
protected StructureCompiler compiler; protected StructureCompiler compiler;
// Fields that originally were read only
private Set<Integer> exempted;
public CompiledStructureModifier() { public CompiledStructureModifier() {
super(); super();
customConvertHandling = true; customConvertHandling = true;
} }
@Override
public void setReadOnly(int fieldIndex, boolean value) throws FieldAccessException {
// We can remove the read-only status
if (isReadOnly(fieldIndex) && !value) {
if (exempted == null)
exempted = Sets.newHashSet();
exempted.add(fieldIndex);
}
// We can only make a certain kind of field read only
if (!isReadOnly(fieldIndex) && value) {
if (exempted == null || !exempted.contains(fieldIndex)) {
throw new IllegalStateException("Cannot make compiled field " + fieldIndex + " read only.");
}
}
super.setReadOnly(fieldIndex, value);
}
// Speed up the default writer // Speed up the default writer
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
@ -84,7 +108,7 @@ public abstract class CompiledStructureModifier<TField> extends StructureModifie
@Override @Override
public StructureModifier<TField> write(int index, Object value) throws FieldAccessException { public StructureModifier<TField> write(int index, Object value) throws FieldAccessException {
if (converter != null) if (converter != null)
value = converter.getGeneric((TField) value); value = converter.getGeneric(getFieldType(index), (TField) value);
return writeGenerated(index, value); return writeGenerated(index, value);
} }

View File

@ -272,6 +272,10 @@ public final class StructureCompiler {
return Modifier.isPublic(field.getModifiers()); return Modifier.isPublic(field.getModifiers());
} }
private boolean isNonFinal(Field field) {
return !Modifier.isFinal(field.getModifiers());
}
private void createFields(ClassWriter cw, String targetSignature) { private void createFields(ClassWriter cw, String targetSignature) {
FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null); FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null);
typedField.visitEnd(); typedField.visitEnd();
@ -305,7 +309,8 @@ public final class StructureCompiler {
for (int i = 0; i < fields.size(); i++) { for (int i = 0; i < fields.size(); i++) {
Class<?> outputType = fields.get(i).getType(); Field field = fields.get(i);
Class<?> outputType = field.getType();
Class<?> inputType = Primitives.wrap(outputType); Class<?> inputType = Primitives.wrap(outputType);
String typeDescriptor = Type.getDescriptor(outputType); String typeDescriptor = Type.getDescriptor(outputType);
String inputPath = inputType.getName().replace('.', '/'); String inputPath = inputType.getName().replace('.', '/');
@ -318,8 +323,8 @@ public final class StructureCompiler {
else else
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// Only write to public fields // Only write to public non-final fields
if (isPublic(fields.get(i))) { if (isPublic(field) && isNonFinal(field)) {
mv.visitVarInsn(Opcodes.ALOAD, 3); mv.visitVarInsn(Opcodes.ALOAD, 3);
mv.visitVarInsn(Opcodes.ALOAD, 2); mv.visitVarInsn(Opcodes.ALOAD, 2);
@ -328,7 +333,7 @@ public final class StructureCompiler {
else else
boxingHelper.unbox(Type.getType(outputType)); boxingHelper.unbox(Type.getType(outputType));
mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, fields.get(i).getName(), typeDescriptor); mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, field.getName(), typeDescriptor);
} else { } else {
// Use reflection. We don't have a choice, unfortunately. // Use reflection. We don't have a choice, unfortunately.
@ -386,7 +391,9 @@ public final class StructureCompiler {
mv.visitTableSwitchInsn(0, fields.size() - 1, errorLabel, labels); mv.visitTableSwitchInsn(0, fields.size() - 1, errorLabel, labels);
for (int i = 0; i < fields.size(); i++) { for (int i = 0; i < fields.size(); i++) {
Class<?> outputType = fields.get(i).getType();
Field field = fields.get(i);
Class<?> outputType = field.getType();
String typeDescriptor = Type.getDescriptor(outputType); String typeDescriptor = Type.getDescriptor(outputType);
mv.visitLabel(labels[i]); mv.visitLabel(labels[i]);
@ -398,9 +405,9 @@ public final class StructureCompiler {
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// Note that byte code cannot access non-public fields // Note that byte code cannot access non-public fields
if (isPublic(fields.get(i))) { if (isPublic(field)) {
mv.visitVarInsn(Opcodes.ALOAD, 2); mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitFieldInsn(Opcodes.GETFIELD, targetName, fields.get(i).getName(), typeDescriptor); mv.visitFieldInsn(Opcodes.GETFIELD, targetName, field.getName(), typeDescriptor);
boxingHelper.box(Type.getType(outputType)); boxingHelper.box(Type.getType(outputType));
} else { } else {

View File

@ -0,0 +1,281 @@
package com.comphenix.protocol.wrappers;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import net.minecraft.server.DataWatcher;
import net.minecraft.server.WatchableObject;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.instances.DefaultInstances;
/**
* Contains several useful equivalent converters for normal Bukkit types.
*
* @author Kristian
*/
public class BukkitConverters {
// Check whether or not certain classes exists
private static boolean hasWorldType = false;
static {
try {
Class.forName("net.minecraft.server.WorldType");
hasWorldType = true;
} catch (ClassNotFoundException e) {
}
}
public static <T> EquivalentConverter<List<T>> getListConverter(final Class<?> genericItemType, final EquivalentConverter<T> itemConverter) {
// Convert to and from the wrapper
return getIgnoreNull(new EquivalentConverter<List<T>>() {
@SuppressWarnings("unchecked")
@Override
public List<T> getSpecific(Object generic) {
if (generic instanceof Collection) {
List<T> items = new ArrayList<T>();
// Copy everything to a new list
for (Object item : (Collection<Object>) generic) {
T result = itemConverter.getSpecific(item);
if (item != null)
items.add(result);
}
return items;
}
// Not valid
return null;
}
@SuppressWarnings("unchecked")
@Override
public Object getGeneric(Class<?> genericType, List<T> specific) {
Collection<Object> newContainer = (Collection<Object>) DefaultInstances.DEFAULT.getDefault(genericType);
// Convert each object
for (T position : specific) {
Object converted = itemConverter.getGeneric(genericItemType, position);
if (position == null)
newContainer.add(null);
else if (converted != null)
newContainer.add(converted);
}
return newContainer;
}
@SuppressWarnings("unchecked")
@Override
public Class<List<T>> getSpecificType() {
// Damn you Java
Class<?> dummy = List.class;
return (Class<List<T>>) dummy;
}
}
);
}
/**
* Retrieve a converter for watchable objects and the respective wrapper.
* @return A watchable object converter.
*/
public static EquivalentConverter<WrappedWatchableObject> getWatchableObjectConverter() {
return getIgnoreNull(new EquivalentConverter<WrappedWatchableObject>() {
@Override
public Object getGeneric(Class<?> genericType, WrappedWatchableObject specific) {
return specific.getHandle();
}
public WrappedWatchableObject getSpecific(Object generic) {
if (generic instanceof WatchableObject)
return new WrappedWatchableObject((WatchableObject) generic);
else if (generic instanceof WrappedWatchableObject)
return (WrappedWatchableObject) generic;
else
throw new IllegalArgumentException("Unrecognized type " + generic.getClass());
};
@Override
public Class<WrappedWatchableObject> getSpecificType() {
return WrappedWatchableObject.class;
}
});
}
/**
* Retrieve a converter for the NMS DataWatcher class and our wrapper.
* @return A DataWatcher converter.
*/
public static EquivalentConverter<WrappedDataWatcher> getDataWatcherConverter() {
return getIgnoreNull(new EquivalentConverter<WrappedDataWatcher>() {
@Override
public Object getGeneric(Class<?> genericType, WrappedDataWatcher specific) {
return specific.getHandle();
}
@Override
public WrappedDataWatcher getSpecific(Object generic) {
if (generic instanceof DataWatcher)
return new WrappedDataWatcher((DataWatcher) generic);
else if (generic instanceof WrappedDataWatcher)
return (WrappedDataWatcher) generic;
else
throw new IllegalArgumentException("Unrecognized type " + generic.getClass());
}
@Override
public Class<WrappedDataWatcher> getSpecificType() {
return WrappedDataWatcher.class;
}
});
}
/**
* Retrieve a converter for Bukkit's world type enum and the NMS equivalent.
* @return A world type enum converter.
*/
public static EquivalentConverter<WorldType> getWorldTypeConverter() {
// Check that we can actually use this converter
if (!hasWorldType)
return null;
return getIgnoreNull(new EquivalentConverter<WorldType>() {
@Override
public Object getGeneric(Class<?> genericType, WorldType specific) {
return net.minecraft.server.WorldType.getType(specific.getName());
}
@Override
public WorldType getSpecific(Object generic) {
net.minecraft.server.WorldType type = (net.minecraft.server.WorldType) generic;
return WorldType.getByName(type.name());
}
@Override
public Class<WorldType> getSpecificType() {
return WorldType.class;
}
});
}
/**
* Retrieve a converter for NMS entities and Bukkit entities.
* @param world - the current world.
* @return A converter between the underlying NMS entity and Bukkit's wrapper.
*/
public static EquivalentConverter<Entity> getEntityConverter(World world) {
final World container = world;
final WeakReference<ProtocolManager> managerRef =
new WeakReference<ProtocolManager>(ProtocolLibrary.getProtocolManager());
return getIgnoreNull(new EquivalentConverter<Entity>() {
@Override
public Object getGeneric(Class<?> genericType, Entity specific) {
// Simple enough
return specific.getEntityId();
}
@Override
public Entity getSpecific(Object generic) {
try {
Integer id = (Integer) generic;
// Use the
if (id != null && managerRef.get() != null) {
return managerRef.get().getEntityFromID(container, id);
} else {
return null;
}
} catch (FieldAccessException e) {
throw new RuntimeException("Cannot retrieve entity from ID.", e);
}
}
@Override
public Class<Entity> getSpecificType() {
return Entity.class;
}
});
}
/**
* Retrieve the converter used to convert NMS ItemStacks to Bukkit's ItemStack.
* @return Item stack converter.
*/
public static EquivalentConverter<ItemStack> getItemStackConverter() {
return getIgnoreNull(new EquivalentConverter<ItemStack>() {
public Object getGeneric(Class<?> genericType, ItemStack specific) {
return toStackNMS(specific);
}
@Override
public ItemStack getSpecific(Object generic) {
return new CraftItemStack((net.minecraft.server.ItemStack) generic);
}
@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 static net.minecraft.server.ItemStack toStackNMS(ItemStack stack) {
// 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();
}
}
/**
* Wraps a given equivalent converter in NULL checks, ensuring that such values are ignored.
* @param delegate - the underlying equivalent converter.
* @return A equivalent converter that ignores NULL values.
*/
public static <TType> EquivalentConverter<TType> getIgnoreNull(final EquivalentConverter<TType> delegate) {
// Automatically wrap all parameters to the delegate with a NULL check
return new EquivalentConverter<TType>() {
public Object getGeneric(Class<?> genericType, TType specific) {
if (specific != null)
return delegate.getGeneric(genericType, specific);
else
return null;
}
@Override
public TType getSpecific(Object generic) {
if (generic != null)
return delegate.getSpecific(generic);
else
return null;
}
@Override
public Class<TType> getSpecificType() {
return delegate.getSpecificType();
}
};
}
}

View File

@ -0,0 +1,194 @@
package com.comphenix.protocol.wrappers;
import org.bukkit.util.Vector;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.base.Objects;
/**
* Copies a immutable net.minecraft.server.ChunkPosition, which represents a integer 3D vector.
*
* @author Kristian
*/
public class ChunkPosition {
/**
* Represents the null (0, 0, 0) origin.
*/
public static ChunkPosition ORIGIN = new ChunkPosition(0, 0, 0);
// Use protected members, like Bukkit
protected final int x;
protected final int y;
protected final int z;
// Used to access a ChunkPosition, in case it's names are changed
private static StructureModifier<Integer> intModifier;
/**
* Construct an immutable 3D vector.
*/
public ChunkPosition(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
/**
* Construct an immutable integer 3D vector from a mutable Bukkit vector.
* @param vector - the mutable real Bukkit vector to copy.
*/
public ChunkPosition(Vector vector) {
if (vector == null)
throw new IllegalArgumentException("Vector cannot be NULL.");
this.x = vector.getBlockX();
this.y = vector.getBlockY();
this.z = vector.getBlockZ();
}
/**
* Convert this instance to an equivalent real 3D vector.
* @return Real 3D vector.
*/
public Vector toVector() {
return new Vector(x, y, z);
}
/**
* Retrieve the x-coordinate.
* @return X coordinate.
*/
public int getX() {
return x;
}
/**
* Retrieve the y-coordinate.
* @return Y coordinate.
*/
public int getY() {
return y;
}
/**
* Retrieve the z-coordinate.
* @return Z coordinate.
*/
public int getZ() {
return z;
}
/**
* Adds the current position and a given position together, producing a result position.
* @param other - the other position.
* @return The new result position.
*/
public ChunkPosition add(ChunkPosition other) {
if (other == null)
throw new IllegalArgumentException("other cannot be NULL");
return new ChunkPosition(x + other.x, y + other.y, z + other.z);
}
/**
* Adds the current position and a given position together, producing a result position.
* @param other - the other position.
* @return The new result position.
*/
public ChunkPosition subtract(ChunkPosition other) {
if (other == null)
throw new IllegalArgumentException("other cannot be NULL");
return new ChunkPosition(x - other.x, y - other.y, z - other.z);
}
/**
* Multiply each dimension in the current position by the given factor.
* @param factor - multiplier.
* @return The new result.
*/
public ChunkPosition multiply(int factor) {
return new ChunkPosition(x * factor, y * factor, z * factor);
}
/**
* Divide each dimension in the current position by the given divisor.
* @param divisor - the divisor.
* @return The new result.
*/
public ChunkPosition divide(int divisor) {
if (divisor == 0)
throw new IllegalArgumentException("Cannot divide by null.");
return new ChunkPosition(x / divisor, y / divisor, z / divisor);
}
/**
* Used to convert between NMS ChunkPosition and the wrapper instance.
* @return A new converter.
*/
public static EquivalentConverter<ChunkPosition> getConverter() {
return new EquivalentConverter<ChunkPosition>() {
@Override
public Object getGeneric(Class<?> genericType, ChunkPosition specific) {
return new net.minecraft.server.ChunkPosition(specific.x, specific.z, specific.z);
}
@Override
public ChunkPosition getSpecific(Object generic) {
if (generic instanceof net.minecraft.server.ChunkPosition) {
net.minecraft.server.ChunkPosition other = (net.minecraft.server.ChunkPosition) generic;
try {
if (intModifier == null)
return new ChunkPosition(other.x, other.y, other.z);
} catch (LinkageError e) {
// It could happen. If it does, use a structure modifier instead
intModifier = new StructureModifier<Object>(other.getClass(), null, false).withType(int.class);
// Damn it all
if (intModifier.size() < 3) {
throw new IllegalStateException("Cannot read class " + other.getClass() + " for its integer fields.");
}
}
if (intModifier.size() >= 3) {
try {
return new ChunkPosition(intModifier.read(0), intModifier.read(1), intModifier.read(2));
} catch (FieldAccessException e) {
// This is an exeptional work-around, so we don't want to burden the caller with the messy details
throw new RuntimeException("Field access error.", e);
}
}
}
// Otherwise, return NULL
return null;
}
// Thanks Java Generics!
@Override
public Class<ChunkPosition> getSpecificType() {
return ChunkPosition.class;
}
};
}
@Override
public boolean equals(Object obj) {
// Fast checks
if (this == obj) return true;
if (obj == null) return false;
// Only compare objects of similar type
if (obj instanceof ChunkPosition) {
ChunkPosition other = (ChunkPosition) obj;
return x == other.x && y == other.y && z == other.z;
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(x, y, z);
}
}

View File

@ -0,0 +1,145 @@
package com.comphenix.protocol.wrappers;
import com.google.common.base.Objects;
import net.minecraft.server.ChunkCoordinates;
/**
* Allows access to a chunk coordinate.
*
* @author Kristian
*/
public class WrappedChunkCoordinate implements Comparable<WrappedChunkCoordinate> {
/**
* If TRUE, NULLs should be put before non-null instances of this class.
*/
private static final boolean LARGER_THAN_NULL = true;
protected ChunkCoordinates handle;
/**
* Create a new empty wrapper.
*/
public WrappedChunkCoordinate() {
this(new ChunkCoordinates());
}
/**
* Create a wrapper for a specific chunk coordinates.
* @param handle - the NMS chunk coordinates.
*/
public WrappedChunkCoordinate(ChunkCoordinates handle) {
if (handle == null)
throw new IllegalArgumentException("handle cannot be NULL");
this.handle = handle;
}
/**
* Create a wrapper with specific values.
* @param x - the x coordinate.
* @param y - the y coordinate.
* @param z - the z coordinate.
*/
public WrappedChunkCoordinate(int x, int y, int z) {
this();
setX(x);
setY(y);
setZ(z);
}
/**
* Create a chunk coordinate wrapper from a given position.
* @param position - the given position.
*/
public WrappedChunkCoordinate(ChunkPosition position) {
this(position.getX(), position.getY(), position.getZ());
}
public ChunkCoordinates getHandle() {
return handle;
}
/**
* Retrieve the x coordinate of the underlying coordiate.
* @return The x coordinate.
*/
public int getX() {
return handle.x;
}
/**
* Set the x coordinate of the underlying coordiate.
* @param newX - the new x coordinate.
*/
public void setX(int newX) {
handle.x = newX;
}
/**
* Retrieve the y coordinate of the underlying coordiate.
* @return The y coordinate.
*/
public int getY() {
return handle.y;
}
/**
* Set the y coordinate of the underlying coordiate.
* @param newY - the new y coordinate.
*/
public void setY(int newY) {
handle.y = newY;
}
/**
* Retrieve the z coordinate of the underlying coordiate.
* @return The z coordinate.
*/
public int getZ() {
return handle.z;
}
/**
* Create an immutable chunk position from this coordinate.
* @return The new immutable chunk position.
*/
public ChunkPosition toPosition() {
return new ChunkPosition(getX(), getY(), getZ());
}
/**
* Set the z coordinate of the underlying coordiate.
* @param newZ - the new z coordinate.
*/
public void setZ(int newZ) {
handle.z = newZ;
}
@Override
public int compareTo(WrappedChunkCoordinate other) {
// We'll handle NULL objects too, unlike ChunkCoordinates
if (other.handle == null)
return LARGER_THAN_NULL ? -1 : 1;
else
return handle.compareTo(other.handle);
}
@Override
public boolean equals(Object other) {
if (other instanceof WrappedChunkCoordinate) {
WrappedChunkCoordinate wrapper = (WrappedChunkCoordinate) other;
return Objects.equal(handle, wrapper.handle);
}
// It's tempting to handle the ChunkCoordinate case too, but then
// the equals() method won't be commutative, causing a.equals(b) to
// be different to b.equals(a).
return false;
}
@Override
public int hashCode() {
return handle.hashCode();
}
}

View File

@ -0,0 +1,506 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import com.comphenix.protocol.injector.BukkitUnwrapper;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.google.common.base.Objects;
import net.minecraft.server.ChunkCoordinates;
import net.minecraft.server.DataWatcher;
import net.minecraft.server.WatchableObject;
/**
* Wraps a DataWatcher that is used to transmit arbitrary key-value pairs with a given entity.
*
* @author Kristian
*/
public class WrappedDataWatcher {
/**
* Used to assign integer IDs to given types.
*/
private static Map<Class<?>, Integer> typeMap;
// Fields
private static Field valueMapField;
private static Field readWriteLockField;
// Methods
private static Method createKeyValueMethod;
private static Method updateKeyValueMethod;
private static Method getKeyValueMethod;
// Entity methods
private static Field entityDataField;
/**
* Whether or not this class has already been initialized.
*/
private static boolean hasInitialized;
// The underlying DataWatcher we're modifying
protected DataWatcher handle;
// Lock
private ReadWriteLock readWriteLock;
// Map of watchable objects
private Map<Integer, Object> watchableObjects;
/**
* Initialize a new data watcher.
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
*/
public WrappedDataWatcher() {
// Just create a new watcher
this(new DataWatcher());
}
/**
* Create a wrapper for a given data watcher.
* @param dataWatcher - the data watcher to wrap.
* @throws FieldAccessException If we're unable to wrap a DataWatcher.
*/
public WrappedDataWatcher(DataWatcher handle) {
this.handle = handle;
try {
initialize();
} catch (FieldAccessException e) {
throw new RuntimeException("Cannot initialize wrapper.", e);
}
}
/**
* Create a new data watcher from a list of watchable objects.
* @param watchableObjects - list of watchable objects that will be copied.
* @throws FieldAccessException Unable to read watchable objects.
*/
public WrappedDataWatcher(List<WrappedWatchableObject> watchableObjects) throws FieldAccessException {
this();
// Fill the underlying map
for (WrappedWatchableObject watched : watchableObjects) {
setObject(watched.getIndex(), watched.getValue());
}
}
/**
* Retrieves the underlying data watcher.
* @return The underlying data watcher.
*/
public DataWatcher getHandle() {
return handle;
}
/**
* Retrieve the ID of a given type, if it's allowed to be watched.
* @return The ID, or NULL if it cannot be watched.
* @throws FieldAccessException If we cannot initialize the reflection machinery.
*/
public static Integer getTypeID(Class<?> clazz) throws FieldAccessException {
initialize();
return typeMap.get(clazz);
}
/**
* Retrieve the type of a given ID, if it's allowed to be watched.
* @return The type using a given ID, or NULL if it cannot be watched.
* @throws FieldAccessException If we cannot initialize the reflection machinery.
*/
public static Class<?> getTypeClass(int id) throws FieldAccessException {
initialize();
for (Map.Entry<Class<?>, Integer> entry : typeMap.entrySet()) {
if (Objects.equal(entry.getValue(), id)) {
return entry.getKey();
}
}
// Unknown class type
return null;
}
/**
* Get a watched byte.
* @param index - index of the watched byte.
* @return The watched byte, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public Byte getByte(int index) throws FieldAccessException {
return (Byte) getObjectRaw(index);
}
/**
* Get a watched short.
* @param index - index of the watched short.
* @return The watched short, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public Short getShort(int index) throws FieldAccessException {
return (Short) getObjectRaw(index);
}
/**
* Get a watched integer.
* @param index - index of the watched integer.
* @return The watched integer, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public Integer getInteger(int index) throws FieldAccessException {
return (Integer) getObjectRaw(index);
}
/**
* Get a watched float.
* @param index - index of the watched float.
* @return The watched float, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public Float getFloat(int index) throws FieldAccessException {
return (Float) getObjectRaw(index);
}
/**
* Get a watched string.
* @param index - index of the watched string.
* @return The watched string, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public String getString(int index) throws FieldAccessException {
return (String) getObjectRaw(index);
}
/**
* Get a watched string.
* @param index - index of the watched string.
* @return The watched string, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public ItemStack getItemStack(int index) throws FieldAccessException {
return (ItemStack) getObject(index);
}
/**
* Get a watched string.
* @param index - index of the watched string.
* @return The watched string, or NULL if this value doesn't exist.
* @throws FieldAccessException Cannot read underlying field.
*/
public WrappedChunkCoordinate getChunkCoordinate(int index) throws FieldAccessException {
return (WrappedChunkCoordinate) getObject(index);
}
/**
* Retrieve a watchable object by index.
* @param index - index of the object to retrieve.
* @return The watched object.
* @throws FieldAccessException Cannot read underlying field.
*/
public Object getObject(int index) throws FieldAccessException {
Object result = getObjectRaw(index);
// Handle the special cases too
if (result instanceof net.minecraft.server.ItemStack) {
return BukkitConverters.getItemStackConverter().getSpecific(result);
} else if (result instanceof ChunkCoordinates) {
return new WrappedChunkCoordinate((ChunkCoordinates) result);
} else {
return result;
}
}
/**
* Retrieve a watchable object by index.
* @param index - index of the object to retrieve.
* @return The watched object.
* @throws FieldAccessException Cannot read underlying field.
*/
private Object getObjectRaw(int index) throws FieldAccessException {
// The get method will take care of concurrency
WatchableObject watchable = getWatchedObject(index);
if (watchable != null) {
return new WrappedWatchableObject(watchable).getValue();
} else {
return null;
}
}
/**
* Retrieve every watchable object in this watcher.
* @return Every watchable object.
* @throws FieldAccessException If reflection failed.
*/
public List<WrappedWatchableObject> getWatchableObjects() throws FieldAccessException {
try {
getReadWriteLock().readLock().lock();
List<WrappedWatchableObject> result = new ArrayList<WrappedWatchableObject>();
// Add each watchable object to the list
for (Object watchable : getWatchableObjectMap().values()) {
if (watchable != null) {
result.add(new WrappedWatchableObject((WatchableObject) watchable));
} else {
result.add(null);
}
}
return result;
} finally {
getReadWriteLock().readLock().unlock();
}
}
/**
* Retrieve a copy of every index associated with a watched object.
* @return Every watched object index.
* @throws FieldAccessException If we're unable to read the underlying object.
*/
public Set<Integer> indexSet() throws FieldAccessException {
try {
getReadWriteLock().readLock().lock();
return new HashSet<Integer>(getWatchableObjectMap().keySet());
} finally {
getReadWriteLock().readLock().unlock();
}
}
/**
* Retrieve the number of watched objects.
* @return Watched object count.
* @throws FieldAccessException If we're unable to read the underlying object.
*/
public int size() throws FieldAccessException {
try {
getReadWriteLock().readLock().lock();
return getWatchableObjectMap().size();
} finally {
getReadWriteLock().readLock().unlock();
}
}
/**
* Set a watched byte.
* @param index - index of the watched byte.
* @param newValue - the new watched value.
* @throws FieldAccessException Cannot read underlying field.
*/
public void setObject(int index, Object newValue) throws FieldAccessException {
setObject(index, newValue, true);
}
/**
* Set a watched byte.
* @param index - index of the watched byte.
* @param newValue - the new watched value.
* @param update - whether or not to refresh every listening clients.
* @throws FieldAccessException Cannot read underlying field.
*/
public void setObject(int index, Object newValue, boolean update) throws FieldAccessException {
// Convert special cases
if (newValue instanceof WrappedChunkCoordinate)
newValue = ((WrappedChunkCoordinate) newValue).getHandle();
if (newValue instanceof ItemStack)
newValue = BukkitConverters.getItemStackConverter().getGeneric(
net.minecraft.server.ItemStack.class, (ItemStack) newValue);
// Next, set the object
setObjectRaw(index, newValue, update);
}
/**
* Set a watchable object by index.
* @param index - index of the object to retrieve.
* @param newValue - the new watched value.
* @param update - whether or not to refresh every listening clients.
* @return The watched object.
* @throws FieldAccessException Cannot read underlying field.
*/
private void setObjectRaw(int index, Object newValue, boolean update) throws FieldAccessException {
WatchableObject watchable;
try {
// Aquire write lock
getReadWriteLock().writeLock().lock();
watchable = getWatchedObject(index);
if (watchable != null) {
new WrappedWatchableObject(watchable).setValue(newValue, update);
}
} finally {
getReadWriteLock().writeLock().unlock();
}
}
private WatchableObject getWatchedObject(int index) throws FieldAccessException {
// We use the get-method first and foremost
if (getKeyValueMethod != null) {
try {
return (WatchableObject) getKeyValueMethod.invoke(handle, index);
} catch (Exception e) {
throw new FieldAccessException("Cannot invoke get key method for index " + index, e);
}
} else {
try {
getReadWriteLock().readLock().lock();
return (WatchableObject) getWatchableObjectMap().get(index);
} finally {
getReadWriteLock().readLock().unlock();
}
}
}
/**
* Retrieve the current read write lock.
* @return Current read write lock.
* @throws FieldAccessException If we're unable to read the underlying field.
*/
protected ReadWriteLock getReadWriteLock() throws FieldAccessException {
try {
// Cache the read write lock
if (readWriteLock != null)
return readWriteLock;
else if (readWriteLockField != null)
return readWriteLock = (ReadWriteLock) FieldUtils.readField(readWriteLockField, handle, true);
else
return readWriteLock = new ReentrantReadWriteLock();
} catch (IllegalAccessException e) {
throw new FieldAccessException("Unable to read lock field.", e);
}
}
/**
* Retrieve the underlying map of key values that stores watchable objects.
* @return A map of watchable objects.
* @throws FieldAccessException If we don't have permission to perform reflection.
*/
@SuppressWarnings("unchecked")
protected Map<Integer, Object> getWatchableObjectMap() throws FieldAccessException {
if (watchableObjects == null) {
try {
watchableObjects = (Map<Integer, Object>) FieldUtils.readField(valueMapField, handle, true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot read watchable object field.", e);
}
}
return watchableObjects;
}
/**
* Retrieve the data watcher associated with an entity.
* @param entity - the entity to read from.
* @return Associated data watcher.
* @throws FieldAccessException Reflection failed.
*/
public static WrappedDataWatcher getEntityWatcher(Entity entity) throws FieldAccessException {
if (entityDataField == null)
entityDataField = FuzzyReflection.fromClass(net.minecraft.server.Entity.class, true).
getFieldByType("datawatcher", DataWatcher.class);
BukkitUnwrapper unwrapper = new BukkitUnwrapper();
try {
Object nsmWatcher = FieldUtils.readField(entityDataField, unwrapper.unwrapItem(entity), true);
if (nsmWatcher != null)
return new WrappedDataWatcher((DataWatcher) nsmWatcher);
else
return null;
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access DataWatcher field.", e);
}
}
/**
* Invoked when a data watcher is first used.
*/
@SuppressWarnings("unchecked")
private static void initialize() throws FieldAccessException {
// This method should only be run once, even if an exception is thrown
if (!hasInitialized)
hasInitialized = true;
else
return;
FuzzyReflection fuzzy = FuzzyReflection.fromClass(DataWatcher.class, true);
for (Field lookup : fuzzy.getFieldListByType(Map.class)) {
if (Modifier.isStatic(lookup.getModifiers())) {
// This must be the type map
try {
typeMap = (Map<Class<?>, Integer>) FieldUtils.readStaticField(lookup, true);
} catch (IllegalAccessException e) {
throw new FieldAccessException("Cannot access type map field.", e);
}
} else {
// If not, then we're probably dealing with the value map
valueMapField = lookup;
}
}
try {
readWriteLockField = fuzzy.getFieldByType("readWriteLock", ReadWriteLock.class);
} catch (IllegalArgumentException e) {
// It's not a big deal
}
initializeMethods(fuzzy);
}
private static void initializeMethods(FuzzyReflection fuzzy) {
List<Method> candidates = fuzzy.getMethodListByParameters(Void.TYPE,
new Class<?>[] { int.class, Object.class});
for (Method method : candidates) {
if (!method.getName().startsWith("watch")) {
createKeyValueMethod = method;
} else {
updateKeyValueMethod = method;
}
}
// Did we succeed?
if (updateKeyValueMethod == null || createKeyValueMethod == null) {
// Go by index instead
if (candidates.size() > 1) {
createKeyValueMethod = candidates.get(0);
updateKeyValueMethod = candidates.get(1);
} else {
throw new IllegalStateException("Unable to find create and update watchable object. Update ProtocolLib.");
}
}
// Load the get-method
try {
getKeyValueMethod = fuzzy.getMethodByParameters(
"getWatchableObject", ".*WatchableObject", new String[] { int.class.getName() });
getKeyValueMethod.setAccessible(true);
} catch (IllegalArgumentException e) {
// Use fallback method
}
}
}

View File

@ -0,0 +1,160 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.FieldAccessException;
import com.comphenix.protocol.reflect.StructureModifier;
import net.minecraft.server.WatchableObject;
/**
* Represents a watchable object.
*
* @author Kristian
*/
public class WrappedWatchableObject {
// Whether or not the reflection machinery has been initialized
private static boolean hasInitialized;
// The field containing the value itself
private static StructureModifier<Object> baseModifier;
protected WatchableObject handle;
protected StructureModifier<Object> modifier;
// Type of the stored value
private Class<?> typeClass;
public WrappedWatchableObject(WatchableObject handle) {
initialize();
this.handle = handle;
this.modifier = baseModifier.withTarget(handle);
}
/**
* Retrieves the underlying watchable object.
* @return The underlying watchable object.
*/
public WatchableObject getHandle() {
return handle;
}
/**
* Initialize reflection machinery.
*/
private static void initialize() {
if (!hasInitialized) {
hasInitialized = true;
baseModifier = new StructureModifier<Object>(WatchableObject.class, null, false);
}
}
/**
* Retrieve the correct super type of the current value.
* @return Super type.
* @throws FieldAccessException Unable to read values.
*/
public Class<?> getType() throws FieldAccessException {
if (typeClass == null) {
typeClass = WrappedDataWatcher.getTypeClass(getTypeID());
if (typeClass == null) {
throw new IllegalStateException("Unrecognized data type: " + getTypeID());
}
}
return typeClass;
}
/**
* Retrieve the index of this watchable object. This is used to identify a value.
* @return Object index.
* @throws FieldAccessException Reflection failed.
*/
public int getIndex() throws FieldAccessException {
return modifier.<Integer>withType(int.class).read(1);
}
/**
* Set the the index of this watchable object.
* @param index - the new object index.
* @throws FieldAccessException Reflection failed.
*/
public void setIndex(int index) throws FieldAccessException {
modifier.<Integer>withType(int.class).write(1, index);
}
/**
* Retrieve the type ID of a watchable object.
* @return Type ID that identifies the type of the value.
* @throws FieldAccessException Reflection failed.
*/
public int getTypeID() throws FieldAccessException {
return modifier.<Integer>withType(int.class).read(0);
}
/**
* Set the type ID of a watchable object.
* @param id - the new ID.
* @throws FieldAccessException Reflection failed.
*/
public void setTypeID(int id) throws FieldAccessException {
modifier.<Integer>withType(int.class).write(0, id);
}
/**
* Update the value field.
* @param newValue - new value.
* @throws FieldAccessException Unable to use reflection.
*/
public void setValue(Object newValue) throws FieldAccessException {
setValue(newValue, true);
}
/**
* Update the value field.
* @param newValue - new value.
* @param updateClient - whether or not to update listening clients.
* @throws FieldAccessException Unable to use reflection.
*/
public void setValue(Object newValue, boolean updateClient) throws FieldAccessException {
// Verify a few quick things
if (newValue == null)
throw new IllegalArgumentException("Cannot watch a NULL value.");
if (!getType().isAssignableFrom(newValue.getClass()))
throw new IllegalArgumentException("Object " + newValue + " must be of type " + getType().getName());
// See if we should update the client to
if (updateClient)
setDirtyState(true);
// Use the modifier to set the value
modifier.withType(Object.class).write(0, newValue);
}
/**
* Read the value field.
* @return The watched value.
* @throws FieldAccessException Unable to use reflection.
*/
public Object getValue() throws FieldAccessException {
return modifier.withType(Object.class).read(0);
}
/**
* Set whether or not the value must be synchronized with the client.
* @param dirty - TRUE if the value should be synchronized, FALSE otherwise.
* @throws FieldAccessException Unable to use reflection.
*/
public void setDirtyState(boolean dirty) throws FieldAccessException {
modifier.<Boolean>withType(boolean.class).write(0, dirty);
}
/**
* Retrieve whether or not the value must be synchronized with the client.
* @return TRUE if the value should be synchronized, FALSE otherwise.
* @throws FieldAccessException Unable to use reflection.
*/
public boolean getDirtyState() throws FieldAccessException {
return modifier.<Boolean>withType(boolean.class).read(0);
}
}

View File

@ -1,5 +1,5 @@
name: ProtocolLib name: ProtocolLib
version: 1.5.1 version: 1.6.0
description: Provides read/write access to the Minecraft protocol. description: Provides read/write access to the Minecraft protocol.
author: Comphenix author: Comphenix
website: http://www.comphenix.net/ProtocolLib website: http://www.comphenix.net/ProtocolLib
@ -11,12 +11,12 @@ commands:
protocol: protocol:
description: Performs administrative tasks regarding ProtocolLib. description: Performs administrative tasks regarding ProtocolLib.
usage: /<command> config|check|update usage: /<command> config|check|update
permission: experiencemod.admin permission: protocol.admin
permission-message: You don't have <permission> permission-message: You don't have <permission>
packet: packet:
description: Add or remove a simple packet listener. description: Add or remove a simple packet listener.
usage: /<command> add|remove|names client|server [ID start]-[ID stop] [detailed] usage: /<command> add|remove|names client|server [ID start]-[ID stop] [detailed]
permission: experiencemod.admin permission: protocol.admin
permission-message: You don't have <permission> permission-message: You don't have <permission>
permissions: permissions: