From 5b279660e0fa93324c0a46c53f736ae5ebd74173 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 4 Nov 2012 16:13:47 +0100 Subject: [PATCH 01/19] Increment to 1.5.2-SNAPSHOT for development on the next version. --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 685bbf5f..1d9f7229 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.5.1 + 1.5.2-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 78a71f62..8959c510 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.5.1 +version: 1.5.2-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 0108c3390e79f72f04d676fbac25ddf1326836b8 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 4 Nov 2012 16:14:07 +0100 Subject: [PATCH 02/19] Add exception handlers to all the commands. --- .../com/comphenix/protocol/CommandBase.java | 74 ++++++++++++++----- .../com/comphenix/protocol/CommandPacket.java | 5 +- .../comphenix/protocol/CommandProtocol.java | 23 ++++-- .../comphenix/protocol/ProtocolLibrary.java | 32 +++++--- 4 files changed, 95 insertions(+), 39 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java index be72d60f..3128e6c5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java @@ -5,6 +5,8 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import com.comphenix.protocol.error.ErrorReporter; + /** * Base class for all our commands. * @@ -17,12 +19,14 @@ abstract class CommandBase implements CommandExecutor { private String permission; private String name; private int minimumArgumentCount; - - public CommandBase(String permission, String name) { - this(permission, name, 0); + private ErrorReporter reporter; + + public CommandBase(ErrorReporter reporter, String permission, String name) { + 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.permission = permission; this.minimumArgumentCount = minimumArgumentCount; @@ -30,22 +34,58 @@ abstract class CommandBase implements CommandExecutor { @Override public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - // Make sure we're dealing with the correct command - if (!command.getName().equalsIgnoreCase(name)) { - return false; - } - if (!sender.hasPermission(permission)) { - sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command."); + try { + // Make sure we're dealing with the correct command + if (!command.getName().equalsIgnoreCase(name)) { + return false; + } + if (permission != null && !sender.hasPermission(permission)) { + sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command."); + return true; + } + + // Check argument length + if (args != null && args.length >= minimumArgumentCount) { + return handleCommand(sender, args); + } else { + return false; + } + + } catch (Exception e) { + reporter.reportDetailed(this, "Cannot execute command " + name, e, sender, label, args); return true; } - - // Check argument length - if (args != null && args.length >= minimumArgumentCount) { - return handleCommand(sender, args); - } else { - return false; - } } + /** + * 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); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 86eccd23..159bbb75 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -76,11 +76,10 @@ class CommandPacket extends CommandBase { private AbstractIntervalTree clientListeners = createTree(ConnectionSide.CLIENT_SIDE); private AbstractIntervalTree serverListeners = createTree(ConnectionSide.SERVER_SIDE); - public CommandPacket(Plugin plugin, Logger logger, ErrorReporter reporter, ProtocolManager manager) { - super(CommandBase.PERMISSION_ADMIN, NAME, 2); + public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, ProtocolManager manager) { + super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2); this.plugin = plugin; this.logger = logger; - this.reporter = reporter; this.manager = manager; this.chatter = new ChatExtensions(manager); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java index 494d6f84..8d6f62d5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java @@ -4,6 +4,7 @@ import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.metrics.Updater; import com.comphenix.protocol.metrics.Updater.UpdateResult; import com.comphenix.protocol.metrics.Updater.UpdateType; @@ -22,9 +23,9 @@ class CommandProtocol extends CommandBase { private Plugin plugin; private Updater updater; private ProtocolConfig config; - - public CommandProtocol(Plugin plugin, Updater updater, ProtocolConfig config) { - super(CommandBase.PERMISSION_ADMIN, NAME, 1); + + public CommandProtocol(ErrorReporter reporter, Plugin plugin, Updater updater, ProtocolConfig config) { + super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1); this.plugin = plugin; this.updater = updater; this.config = config; @@ -51,8 +52,12 @@ class CommandProtocol extends CommandBase { plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() { @Override public void run() { - UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true); - sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString()); + try { + 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() { @Override public void run() { - UpdateResult result = updater.update(UpdateType.DEFAULT, true); - sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString()); + try { + 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); + } } }); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 1a1d5db3..aa32d4be 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -75,6 +75,7 @@ public class ProtocolLibrary extends JavaPlugin { // Updater private Updater updater; + private boolean updateDisabled; // Logger private Logger logger; @@ -108,8 +109,8 @@ public class ProtocolLibrary extends JavaPlugin { reporter.addGlobalParameter("manager", protocolManager); // Initialize command handlers - commandProtocol = new CommandProtocol(this, updater, config); - commandPacket = new CommandPacket(this, logger, reporter, protocolManager); + commandProtocol = new CommandProtocol(reporter, this, updater, config); + commandPacket = new CommandPacket(reporter, this, logger, protocolManager); // Send logging information to player listeners too broadcastUsers(PERMISSION_INFO); @@ -223,7 +224,9 @@ public class ProtocolLibrary extends JavaPlugin { manager.sendProcessedPackets(tickCounter++, true); // Check for updates too - checkUpdates(); + if (!updateDisabled) { + checkUpdates(); + } } }, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY); @@ -238,15 +241,20 @@ public class ProtocolLibrary extends JavaPlugin { // Ignore milliseconds - it's pointless long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND; - // Should we update? - if (currentTime > config.getAutoLastTime() + config.getAutoDelay()) { - // Initiate the update as if it came from the console - if (config.isAutoDownload()) - commandProtocol.updateVersion(getServer().getConsoleSender()); - else if (config.isAutoNotify()) - commandProtocol.checkVersion(getServer().getConsoleSender()); - else - commandProtocol.updateFinished(); + try { + // Should we update? + if (currentTime > config.getAutoLastTime() + config.getAutoDelay()) { + // Initiate the update as if it came from the console + if (config.isAutoDownload()) + commandProtocol.updateVersion(getServer().getConsoleSender()); + else if (config.isAutoNotify()) + commandProtocol.checkVersion(getServer().getConsoleSender()); + else + commandProtocol.updateFinished(); + } + } catch (Exception e) { + reporter.reportDetailed(this, "Cannot perform automatic updates.", e); + updateDisabled = true; } } From 5e6a6f6a9596d89643744a874a493b89a82af530 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 5 Nov 2012 00:05:16 +0100 Subject: [PATCH 03/19] Fix the syntax of the packet debug message. --- ProtocolLib/dependency-reduced-pom.xml | 2 +- .../main/java/com/comphenix/protocol/CommandPacket.java | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index dc9bd600..2cab33a2 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.5.1 + 1.5.2-SNAPSHOT Provides read/write access to the Minecraft protocol. http://dev.bukkit.org/server-mods/protocollib/ diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 159bbb75..8bfac14d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -377,10 +377,10 @@ class CommandPacket extends CommandBase { } private void printInformation(PacketEvent event) { - String verb = side.isForClient() ? "Received" : "Sent"; - String shortDescription = String.format( - "%s %s (%s) from %s", - verb, + String format = side.isForClient() ? + "Received %s (%s) from %s" : + "Sent %s (%s) to %s"; + String shortDescription = String.format(format, Packets.getDeclaredName(event.getPacketID()), event.getPacketID(), event.getPlayer().getName() From 003bc927a444ab74b6fbd1a55fe1da8bc8d184a7 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 6 Nov 2012 18:07:06 +0100 Subject: [PATCH 04/19] Set default update rate to 43200 seconds instead. --- .../java/com/comphenix/protocol/CommandProtocol.java | 2 +- .../java/com/comphenix/protocol/ProtocolConfig.java | 11 +++-------- .../java/com/comphenix/protocol/ProtocolLibrary.java | 5 ++++- .../comphenix/protocol/async/PacketSendingQueue.java | 1 - 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java index 8d6f62d5..fa20d46d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java @@ -86,7 +86,7 @@ class CommandProtocol extends CommandBase { */ public void updateFinished() { long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND; - + config.setAutoLastTime(currentTime); config.saveAll(); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java index 09e5313d..078262c7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -24,7 +24,7 @@ class ProtocolConfig { private static final String UPDATER_LAST_TIME = "last"; // Defaults - private static final long DEFAULT_UPDATER_DELAY = 60; + private static final long DEFAULT_UPDATER_DELAY = 43200; private Plugin plugin; private Configuration config; @@ -47,7 +47,7 @@ class ProtocolConfig { */ public void reloadConfig() { this.config = plugin.getConfig(); - loadSections(true); + loadSections(!loadingSections); } /** @@ -55,9 +55,6 @@ class ProtocolConfig { * @param copyDefaults - whether or not to copy configuration defaults. */ private void loadSections(boolean copyDefaults) { - if (loadingSections) - return; - if (config != null) { global = config.getConfigurationSection(SECTION_GLOBAL); } @@ -72,10 +69,8 @@ class ProtocolConfig { if (config != null) config.options().copyDefaults(true); plugin.saveDefaultConfig(); - config = plugin.getConfig(); - + plugin.reloadConfig(); loadingSections = false; - loadSections(false); // Inform the user System.out.println("[ProtocolLib] Created default configuration."); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index aa32d4be..b97b35f9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -128,6 +128,7 @@ public class ProtocolLibrary extends JavaPlugin { @Override public void reloadConfig() { super.reloadConfig(); + // Reload configuration if (config != null) { config.reloadConfig(); @@ -242,8 +243,10 @@ public class ProtocolLibrary extends JavaPlugin { long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND; try { + long updateTime = config.getAutoLastTime() + config.getAutoDelay(); + // Should we update? - if (currentTime > config.getAutoLastTime() + config.getAutoDelay()) { + if (currentTime > updateTime) { // Initiate the update as if it came from the console if (config.isAutoDownload()) commandProtocol.updateVersion(getServer().getConsoleSender()); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java index 698c2def..3b1a7426 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java @@ -27,7 +27,6 @@ import org.bukkit.entity.Player; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.PlayerLoggedOutException; -import com.comphenix.protocol.injector.SortedPacketListenerList; import com.comphenix.protocol.reflect.FieldAccessException; /** From da7cd0b10d558c9dcf63715875138e21c7341026 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 10 Nov 2012 07:58:44 +0100 Subject: [PATCH 05/19] Reference the correct permissions. --- ProtocolLib/src/main/resources/plugin.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 8959c510..0bdc7271 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -11,12 +11,12 @@ commands: protocol: description: Performs administrative tasks regarding ProtocolLib. usage: / config|check|update - permission: experiencemod.admin + permission: protocol.admin permission-message: You don't have packet: description: Add or remove a simple packet listener. usage: / add|remove|names client|server [ID start]-[ID stop] [detailed] - permission: experiencemod.admin + permission: protocol.admin permission-message: You don't have permissions: From 79786b819255c9674afb2b09a41cfa22e5723ca5 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 11 Nov 2012 00:57:16 +0100 Subject: [PATCH 06/19] Use a default connection side in the packet command. --- .../com/comphenix/protocol/CommandPacket.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 8bfac14d..56cb2c29 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -77,7 +77,7 @@ class CommandPacket extends CommandBase { private AbstractIntervalTree serverListeners = createTree(ConnectionSide.SERVER_SIDE); public CommandPacket(ErrorReporter reporter, Plugin plugin, Logger logger, ProtocolManager manager) { - super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2); + super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 1); this.plugin = plugin; this.logger = logger; this.manager = manager; @@ -227,6 +227,12 @@ class CommandPacket extends CommandBase { // Perform commands 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); } else if (subCommand == SubCommand.REMOVE) { executeRemoveCommand(sender, side, detailed, ranges); @@ -437,7 +443,13 @@ class CommandPacket extends CommandBase { // The trees will manage the listeners for us if (listener != null) { - getListenerTree(side).put(idStart, idStop, listener); + if (side.isForClient()) + clientListeners.put(idStart, idStop, listener); + else if (side.isForServer()) + serverListeners.put(idStart, idStop, listener); + else + throw new IllegalArgumentException("Not a legal connection side."); + return listener; } else { throw new IllegalArgumentException("No packets found in the range " + idStart + " - " + idStop + "."); @@ -448,18 +460,14 @@ class CommandPacket extends CommandBase { ConnectionSide side, int idStart, int idStop, boolean detailed) { // The interval tree will automatically remove the listeners for us - return getListenerTree(side).remove(idStart, idStop); - } - - private AbstractIntervalTree getListenerTree(ConnectionSide side) { if (side.isForClient()) - return clientListeners; + return clientListeners.remove(idStart, idStop); else if (side.isForServer()) - return serverListeners; + return serverListeners.remove(idStart, idStop); else throw new IllegalArgumentException("Not a legal connection side."); } - + private SubCommand parseCommand(String[] args, int index) { String text = args[index].toLowerCase(); From addc62457ad9ddbbefca890a56e1f12c8bed75d9 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 11 Nov 2012 01:06:11 +0100 Subject: [PATCH 07/19] Handle multiple connection sides properly. --- .../com/comphenix/protocol/CommandPacket.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 56cb2c29..d09189e6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -30,8 +30,10 @@ import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.PrettyPrinter; import com.comphenix.protocol.utility.ChatExtensions; import com.google.common.collect.DiscreteDomains; +import com.google.common.collect.Maps; import com.google.common.collect.Range; import com.google.common.collect.Ranges; +import com.google.common.collect.Sets; /** * Handles the "packet" debug command. @@ -313,18 +315,19 @@ class CommandPacket extends CommandBase { } private Set getValidPackets(ConnectionSide side) throws FieldAccessException { + HashSet supported = Sets.newHashSet(); + if (side.isForClient()) - return Packets.Client.getSupported(); + supported.addAll(Packets.Client.getSupported()); else if (side.isForServer()) - return Packets.Server.getSupported(); - else - throw new IllegalArgumentException("Illegal side: " + side); + supported.addAll(Packets.Server.getSupported()); + return supported; } private Set getNamedPackets(ConnectionSide side) { Set valids = null; - Set result = null; + Set result = Sets.newHashSet(); try { valids = getValidPackets(side); @@ -334,11 +337,9 @@ class CommandPacket extends CommandBase { // Check connection side if (side.isForClient()) - result = Packets.Client.getRegistry().values(); - else if (side.isForServer()) - result = Packets.Server.getRegistry().values(); - else - throw new IllegalArgumentException("Illegal side: " + side); + result.addAll(Packets.Client.getRegistry().values()); + if (side.isForServer()) + result.addAll(Packets.Server.getRegistry().values()); // Remove invalid packets result.retainAll(valids); @@ -445,11 +446,8 @@ class CommandPacket extends CommandBase { if (listener != null) { if (side.isForClient()) clientListeners.put(idStart, idStop, listener); - else if (side.isForServer()) + if (side.isForServer()) serverListeners.put(idStart, idStop, listener); - else - throw new IllegalArgumentException("Not a legal connection side."); - return listener; } else { throw new IllegalArgumentException("No packets found in the range " + idStart + " - " + idStop + "."); @@ -459,15 +457,16 @@ class CommandPacket extends CommandBase { public Set.Entry> removePacketListeners( ConnectionSide side, int idStart, int idStop, boolean detailed) { + HashSet.Entry> result = Sets.newHashSet(); + // The interval tree will automatically remove the listeners for us if (side.isForClient()) - return clientListeners.remove(idStart, idStop); - else if (side.isForServer()) - return serverListeners.remove(idStart, idStop); - else - throw new IllegalArgumentException("Not a legal connection side."); + result.addAll(clientListeners.remove(idStart, idStop)); + if (side.isForServer()) + result.addAll(serverListeners.remove(idStart, idStop)); + return result; } - + private SubCommand parseCommand(String[] args, int index) { String text = args[index].toLowerCase(); From 76d27017deb31669c8888fe2d07fd2070b65231d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 11 Nov 2012 01:22:17 +0100 Subject: [PATCH 08/19] Allow the structure modifier to read final fields. --- .../protocol/reflect/StructureModifier.java | 23 +++++++++++++++---- .../reflect/compiler/StructureCompiler.java | 21 +++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java index 2665dc21..cd232710 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java @@ -152,6 +152,18 @@ public class StructureModifier { } } + /** + * 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()); + } + /** * Writes the value of a field given its index. * @param fieldIndex - index of the field. @@ -424,9 +436,10 @@ public class StructureModifier { for (Field field : fields) { Class type = field.getType(); + int modifier = field.getModifiers(); - // First, ignore primitive fields - if (!type.isPrimitive()) { + // First, ignore primitive fields and final fields + if (!type.isPrimitive() && !Modifier.isFinal(modifier)) { // Next, see if we actually can generate a default value if (generator.getDefault(type) != null) { // If so, require it @@ -449,9 +462,9 @@ public class StructureModifier { for (Field field : FuzzyReflection.fromClass(type, true).getFields()) { int mod = field.getModifiers(); - // Ignore static, final and "abstract packet" fields - if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) && ( - superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude) + // Ignore static and "abstract packet" fields + if (!Modifier.isStatic(mod) && + (superclassExclude == null || !field.getDeclaringClass().equals(superclassExclude) )) { result.add(field); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java index 10ceaa72..266bb45e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/StructureCompiler.java @@ -272,6 +272,10 @@ public final class StructureCompiler { return Modifier.isPublic(field.getModifiers()); } + private boolean isNonFinal(Field field) { + return !Modifier.isFinal(field.getModifiers()); + } + private void createFields(ClassWriter cw, String targetSignature) { FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null); typedField.visitEnd(); @@ -305,7 +309,8 @@ public final class StructureCompiler { 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); String typeDescriptor = Type.getDescriptor(outputType); String inputPath = inputType.getName().replace('.', '/'); @@ -318,8 +323,8 @@ public final class StructureCompiler { else mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); - // Only write to public fields - if (isPublic(fields.get(i))) { + // Only write to public non-final fields + if (isPublic(field) && isNonFinal(field)) { mv.visitVarInsn(Opcodes.ALOAD, 3); mv.visitVarInsn(Opcodes.ALOAD, 2); @@ -328,7 +333,7 @@ public final class StructureCompiler { else boxingHelper.unbox(Type.getType(outputType)); - mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, fields.get(i).getName(), typeDescriptor); + mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, field.getName(), typeDescriptor); } else { // 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); 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); mv.visitLabel(labels[i]); @@ -398,9 +405,9 @@ public final class StructureCompiler { mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); // Note that byte code cannot access non-public fields - if (isPublic(fields.get(i))) { + if (isPublic(field)) { 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)); } else { From 0b292af3b12393b27145a4c1fb5c1f0553d4a802 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 11 Nov 2012 02:09:45 +0100 Subject: [PATCH 09/19] Add the ability to write to final fields, even if it is compiled. --- .../com/comphenix/protocol/CommandPacket.java | 1 - .../protocol/reflect/ObjectCloner.java | 6 +- .../protocol/reflect/StructureModifier.java | 88 +++++++++++++++++-- .../compiler/CompiledStructureModifier.java | 24 +++++ 4 files changed, 111 insertions(+), 8 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index d09189e6..dbe85114 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -30,7 +30,6 @@ import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.PrettyPrinter; import com.comphenix.protocol.utility.ChatExtensions; import com.google.common.collect.DiscreteDomains; -import com.google.common.collect.Maps; import com.google.common.collect.Range; import com.google.common.collect.Ranges; import com.google.common.collect.Sets; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java index a9807c32..5475e60d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/ObjectCloner.java @@ -66,8 +66,10 @@ public class ObjectCloner { // Copy every field try { for (int i = 0; i < modifierSource.size(); i++) { - Object value = modifierSource.read(i); - modifierDest.write(i, value); + if (!modifierDest.isReadOnly(i)) { + Object value = modifierSource.read(i); + modifierDest.write(i, value); + } // System.out.println(String.format("Writing value %s to %s", // value, modifier.getFields().get(i).getName())); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java index cd232710..31715877 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java @@ -61,6 +61,17 @@ public class StructureModifier { // Whether or subclasses should handle conversion 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. * @param targetType - the structure to modify. @@ -68,10 +79,22 @@ public class StructureModifier { * @param requireDefault - whether or not we will be using writeDefaults(). */ 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 fields = getFields(targetType, superclassExclude); Map defaults = requireDefault ? generateDefaultFields(fields) : new HashMap(); - initialize(targetType, Object.class, fields, defaults, null, new ConcurrentHashMap()); + initialize(targetType, Object.class, fields, defaults, null, + new ConcurrentHashMap(), useStructureCompiler); } /** @@ -87,7 +110,8 @@ public class StructureModifier { */ protected void initialize(StructureModifier other) { 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 { protected void initialize(Class targetType, Class fieldType, List data, Map defaultFields, EquivalentConverter converter, Map 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 data, Map defaultFields, + EquivalentConverter converter, Map subTypeCache, + boolean useStructureCompiler) { + this.targetType = targetType; this.fieldType = fieldType; this.data = data; this.defaultFields = defaultFields; this.converter = converter; this.subtypeCache = subTypeCache; + this.useStructureCompiler = useStructureCompiler; } /** @@ -164,6 +207,40 @@ public class StructureModifier { return Modifier.isFinal(data.get(fieldIndex).getModifiers()); } + /** + * Set whether or not a field should be treated as read only. + *

+ * 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. * @param fieldIndex - index of the field. @@ -293,7 +370,7 @@ public class StructureModifier { subtypeCache.put(fieldType, result); // Automatically compile the structure modifier - if (BackgroundCompiler.getInstance() != null) + if (useStructureCompiler && BackgroundCompiler.getInstance() != null) BackgroundCompiler.getInstance().scheduleCompilation(subtypeCache, fieldType); } } @@ -365,7 +442,8 @@ public class StructureModifier { StructureModifier result = new StructureModifier(); result.initialize(targetType, fieldType, filtered, defaults, - converter, new ConcurrentHashMap()); + converter, new ConcurrentHashMap(), + useStructureCompiler); return result; } @@ -378,7 +456,7 @@ public class StructureModifier { StructureModifier copy = new StructureModifier(); // Create a new instance - copy.initialize(targetType, fieldType, data, defaultFields, converter, subtypeCache); + copy.initialize(this); copy.target = target; return copy; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java index 602815f8..8641a6a9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java @@ -19,10 +19,12 @@ package com.comphenix.protocol.reflect.compiler; import java.lang.reflect.Field; import java.util.Map; +import java.util.Set; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.google.common.collect.Sets; /** * Represents a compiled structure modifier. @@ -34,11 +36,33 @@ public abstract class CompiledStructureModifier extends StructureModifie // Used to compile instances of structure modifiers protected StructureCompiler compiler; + // Fields that originally were read only + private Set exempted; + public CompiledStructureModifier() { super(); 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 @SuppressWarnings("unchecked") @Override From 5bb6f7649a07feb496d1ac166ca4a98700db021a Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 11 Nov 2012 06:04:08 +0100 Subject: [PATCH 10/19] Corrected a bug in a PacketAdapter constructor discovered by Folipurba. --- .../main/java/com/comphenix/protocol/events/PacketAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java index 056fe089..aa13233f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java @@ -104,7 +104,7 @@ public abstract class PacketAdapter implements PacketListener { * @param packets - the packet IDs the listener is looking for. */ 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); } /** From a567721114ee356eea95282ab120adcc18a53afa Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 11 Nov 2012 07:24:45 +0100 Subject: [PATCH 11/19] Add the ability to read and write ChunkPositions. --- .../protocol/events/PacketContainer.java | 90 +++++++++++ .../protocol/wrappers/ChunkPosition.java | 146 ++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 5ab389c2..af181463 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -25,6 +25,9 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import org.bukkit.Bukkit; import org.bukkit.Server; @@ -40,6 +43,8 @@ import com.comphenix.protocol.injector.StructureCache; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.comphenix.protocol.wrappers.ChunkPosition; import net.minecraft.server.Packet; @@ -327,6 +332,91 @@ public class PacketContainer implements Serializable { })); } + /** + * Retrieves a read/write structure for chunk positions. + * @return A modifier for a ChunkPosition. + */ + public StructureModifier getPositionModifier() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + net.minecraft.server.ChunkPosition.class, + ChunkPosition.getConverter()); + } + + /** + * Retrieves a read/write structure for collections of chunk positions. + *

+ * This modifier will automatically marshall between the visible ProtocolLib ChunkPosition and the + * internal Minecraft ChunkPosition. + * @return A modifier for ChunkPosition array fields. + */ + public StructureModifier> getPositionCollectionModifier() { + final EquivalentConverter converter = ChunkPosition.getConverter(); + + // Convert to and from the ProtocolLib wrapper + final StructureModifier> modifier = structureModifier.withType( + Collection.class, + getIgnoreNull(new EquivalentConverter>() { + private Class collectionType; + + @SuppressWarnings("unchecked") + @Override + public List getSpecific(Object generic) { + if (generic instanceof Collection) { + List positions = new ArrayList(); + + // Save the type + collectionType = generic.getClass(); + + // Copy everything to a new list + for (Object item : (Collection) generic) { + ChunkPosition result = converter.getSpecific(item); + + if (item != null) + positions.add(result); + } + return positions; + } + + // Not valid + return null; + } + + @SuppressWarnings("unchecked") + @Override + public Object getGeneric(List specific) { + // Just go by the first field + if (collectionType == null) { + collectionType = structureModifier.withType(Collection.class).getFields().get(0).getType(); + } + + Collection newContainer = (Collection) DefaultInstances.DEFAULT.getDefault(collectionType); + + // Convert each object + for (ChunkPosition position : specific) { + Object converted = converter.getGeneric(position); + + if (position == null) + newContainer.add(null); + else if (converted != null) + newContainer.add(converted); + } + return newContainer; + } + + @SuppressWarnings("unchecked") + @Override + public Class> getSpecificType() { + // Damn you Java + Class dummy = List.class; + return (Class>) dummy; + } + } + )); + + return modifier; + } + private EquivalentConverter getIgnoreNull(final EquivalentConverter delegate) { // Automatically wrap all parameters to the delegate with a NULL check return new EquivalentConverter() { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java new file mode 100644 index 00000000..7cbd5840 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java @@ -0,0 +1,146 @@ +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; + +/** + * Wraps a immutable net.minecraft.server.ChunkPosition, which represents a integer 3D vector. + * + * @author Kristian + */ +public class ChunkPosition { + // 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 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; + } + + /** + * Used to convert between NMS ChunkPosition and the wrapper instance. + * @return + */ + public static EquivalentConverter getConverter() { + return new EquivalentConverter() { + @Override + public Object getGeneric(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(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 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); + } +} From ad69b0caac4715811f3608c407124995d95f31b5 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 13 Nov 2012 14:34:07 +0100 Subject: [PATCH 12/19] Added the ability to read data watchers and watchable object lists. --- .../protocol/CleanupStaticMembers.java | 8 +- .../protocol/events/PacketContainer.java | 272 ++--------- .../protocol/reflect/EquivalentConverter.java | 19 +- .../protocol/reflect/StructureModifier.java | 11 +- .../compiler/CompiledStructureModifier.java | 2 +- .../protocol/wrappers/BukkitConverters.java | 307 +++++++++++++ .../protocol/wrappers/ChunkPosition.java | 54 ++- .../wrappers/WrappedChunkCoordinate.java | 105 +++++ .../protocol/wrappers/WrappedDataWatcher.java | 434 ++++++++++++++++++ .../wrappers/WrappedWatchableObject.java | 160 +++++++ 10 files changed, 1141 insertions(+), 231 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java index dbc2e819..91e86148 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -19,6 +19,8 @@ import com.comphenix.protocol.reflect.compiler.StructureCompiler; import com.comphenix.protocol.reflect.instances.CollectionGenerator; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; +import com.comphenix.protocol.wrappers.ChunkPosition; +import com.comphenix.protocol.wrappers.WrappedDataWatcher; /** * Used to fix ClassLoader leaks that may lead to filling up the permanent generation. @@ -45,7 +47,8 @@ class CleanupStaticMembers { BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class, PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.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 }; String[] internalClasses = { @@ -62,7 +65,8 @@ class CleanupStaticMembers { "com.comphenix.protocol.injector.ReadPacketModifier", "com.comphenix.protocol.injector.StructureCache", "com.comphenix.protocol.reflect.compiler.BoxingHelper", - "com.comphenix.protocol.reflect.compiler.MethodDescriptor" + "com.comphenix.protocol.reflect.compiler.MethodDescriptor", + "com.comphenix.protocol.wrappers.WrappedWatchableObject" }; resetClasses(publicClasses); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index af181463..e9ce9714 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -25,26 +25,22 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.bukkit.Bukkit; -import org.bukkit.Server; import org.bukkit.World; import org.bukkit.WorldType; -import org.bukkit.craftbukkit.CraftWorld; -import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import com.comphenix.protocol.injector.StructureCache; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.StructureModifier; -import com.comphenix.protocol.reflect.instances.DefaultInstances; +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; @@ -66,24 +62,10 @@ public class PacketContainer implements Serializable { // Current structure modifier protected transient StructureModifier structureModifier; - // Check whether or not certain classes exists - private static boolean hasWorldType = false; - - // The getEntity method - private static Method getEntity; - // Support for serialization private static Method writeMethod; private static Method readMethod; - - static { - try { - Class.forName("net.minecraft.server.WorldType"); - hasWorldType = true; - } catch (ClassNotFoundException e) { - } - } - + /** * Creates a packet container for a new packet. * @param id - ID of the packet to create. @@ -150,22 +132,8 @@ public class PacketContainer implements Serializable { */ public StructureModifier getItemModifier() { // Convert to and from the Bukkit wrapper - return structureModifier.withType(net.minecraft.server.ItemStack.class, - getIgnoreNull(new EquivalentConverter() { - 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 getSpecificType() { - return ItemStack.class; - } - })); + return structureModifier.withType( + net.minecraft.server.ItemStack.class, BukkitConverters.getItemStackConverter()); } /** @@ -176,17 +144,21 @@ public class PacketContainer implements Serializable { * @return A modifier for ItemStack array fields. */ public StructureModifier getItemArrayModifier() { + + final EquivalentConverter stackConverter = BukkitConverters.getItemStackConverter(); + // Convert to and from the Bukkit wrapper return structureModifier.withType( net.minecraft.server.ItemStack[].class, - getIgnoreNull(new EquivalentConverter() { + BukkitConverters.getIgnoreNull(new EquivalentConverter() { - public Object getGeneric(ItemStack[] specific) { + public Object getGeneric(ClassgenericType, ItemStack[] specific) { net.minecraft.server.ItemStack[] result = new net.minecraft.server.ItemStack[specific.length]; // Unwrap every item 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; } @@ -198,7 +170,7 @@ public class PacketContainer implements Serializable { // Add the wrapper for (int i = 0; i < result.length; i++) { - result[i] = new CraftItemStack(input[i]); + result[i] = stackConverter.getSpecific(input[i]); } return result; } @@ -210,20 +182,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. *

@@ -232,33 +190,21 @@ public class PacketContainer implements Serializable { * @return A modifier for world type fields. */ public StructureModifier getWorldTypeModifier() { - - if (!hasWorldType) { - // We couldn't find the Minecraft equivalent - return structureModifier.withType(null); - } - // Convert to and from the Bukkit wrapper return structureModifier.withType( net.minecraft.server.WorldType.class, - getIgnoreNull(new EquivalentConverter() { - - @Override - public Object getGeneric(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 getSpecificType() { - return WorldType.class; - } - })); + BukkitConverters.getWorldTypeConverter()); + } + + /** + * Retrieves a read/write structure for data watchers. + * @return A modifier for data watchers. + */ + public StructureModifier getDataWatcherModifier() { + // Convert to and from the Bukkit wrapper + return structureModifier.withType( + net.minecraft.server.DataWatcher.class, + BukkitConverters.getDataWatcherConverter()); } /** @@ -272,64 +218,9 @@ public class PacketContainer implements Serializable { * @return A modifier entity types. */ public StructureModifier 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 return structureModifier.withType( - int.class, - getIgnoreNull(new EquivalentConverter() { - - @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 getSpecificType() { - return Entity.class; - } - })); + int.class, BukkitConverters.getEntityConverter(world)); } /** @@ -348,100 +239,35 @@ public class PacketContainer implements Serializable { *

* This modifier will automatically marshall between the visible ProtocolLib ChunkPosition and the * internal Minecraft ChunkPosition. - * @return A modifier for ChunkPosition array fields. + * @return A modifier for ChunkPosition list fields. */ public StructureModifier> getPositionCollectionModifier() { - final EquivalentConverter converter = ChunkPosition.getConverter(); - // Convert to and from the ProtocolLib wrapper - final StructureModifier> modifier = structureModifier.withType( + return structureModifier.withType( Collection.class, - getIgnoreNull(new EquivalentConverter>() { - private Class collectionType; - - @SuppressWarnings("unchecked") - @Override - public List getSpecific(Object generic) { - if (generic instanceof Collection) { - List positions = new ArrayList(); - - // Save the type - collectionType = generic.getClass(); - - // Copy everything to a new list - for (Object item : (Collection) generic) { - ChunkPosition result = converter.getSpecific(item); - - if (item != null) - positions.add(result); - } - return positions; - } - - // Not valid - return null; - } - - @SuppressWarnings("unchecked") - @Override - public Object getGeneric(List specific) { - // Just go by the first field - if (collectionType == null) { - collectionType = structureModifier.withType(Collection.class).getFields().get(0).getType(); - } - - Collection newContainer = (Collection) DefaultInstances.DEFAULT.getDefault(collectionType); - - // Convert each object - for (ChunkPosition position : specific) { - Object converted = converter.getGeneric(position); - - if (position == null) - newContainer.add(null); - else if (converted != null) - newContainer.add(converted); - } - return newContainer; - } - - @SuppressWarnings("unchecked") - @Override - public Class> getSpecificType() { - // Damn you Java - Class dummy = List.class; - return (Class>) dummy; - } - } - )); - - return modifier; + BukkitConverters.getListConverter( + net.minecraft.server.ChunkPosition.class, + ChunkPosition.getConverter()) + ); } - private EquivalentConverter getIgnoreNull(final EquivalentConverter delegate) { - // Automatically wrap all parameters to the delegate with a NULL check - return new EquivalentConverter() { - public Object getGeneric(TType specific) { - if (specific != null) - return delegate.getGeneric(specific); - else - return null; - } - - @Override - public TType getSpecific(Object generic) { - if (generic != null) - return delegate.getSpecific(generic); - else - return null; - } - - @Override - public Class getSpecificType() { - return delegate.getSpecificType(); - } - }; + /** + * Retrieves a read/write structure for collections of watchable objects. + *

+ * This modifier will automatically marshall between the visible WrappedWatchableObject and the + * internal Minecraft WatchableObject. + * @return A modifier for watchable object list fields. + */ + public StructureModifier> getWatchableCollectionModifier() { + // Convert to and from the ProtocolLib wrapper + return structureModifier.withType( + Collection.class, + BukkitConverters.getListConverter( + net.minecraft.server.WatchableObject.class, + BukkitConverters.getWatchableObjectConverter()) + ); } - + /** * Retrieves the ID of this packet. * @return Packet ID. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/EquivalentConverter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/EquivalentConverter.java index a03b5508..63733abe 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/EquivalentConverter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/EquivalentConverter.java @@ -24,8 +24,25 @@ package com.comphenix.protocol.reflect; * @param The specific type. */ public interface EquivalentConverter { + /** + * 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 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 getSpecificType(); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java index 31715877..556af330 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java @@ -256,7 +256,7 @@ public class StructureModifier { throw new IllegalStateException("Cannot write to a NULL target."); // Use the converter, if it exists - Object obj = needConversion() ? converter.getGeneric(value) : value; + Object obj = needConversion() ? converter.getGeneric(getFieldType(fieldIndex), value) : value; try { FieldUtils.writeField(data.get(fieldIndex), target, obj, true); @@ -268,6 +268,15 @@ public class StructureModifier { 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. * @return TRUE if we should, FALSE otherwise. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java index 8641a6a9..45bca0f9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/compiler/CompiledStructureModifier.java @@ -108,7 +108,7 @@ public abstract class CompiledStructureModifier extends StructureModifie @Override public StructureModifier write(int index, Object value) throws FieldAccessException { if (converter != null) - value = converter.getGeneric((TField) value); + value = converter.getGeneric(getFieldType(index), (TField) value); return writeGenerated(index, value); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java new file mode 100644 index 00000000..ee7faf9f --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -0,0 +1,307 @@ +package com.comphenix.protocol.wrappers; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +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.Bukkit; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.WorldType; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.FuzzyReflection; +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; + + // The getEntity method + private static Method getEntity; + + static { + try { + Class.forName("net.minecraft.server.WorldType"); + hasWorldType = true; + } catch (ClassNotFoundException e) { + } + } + + public static EquivalentConverter> getListConverter(final Class genericItemType, final EquivalentConverter itemConverter) { + // Convert to and from the wrapper + return getIgnoreNull(new EquivalentConverter>() { + @SuppressWarnings("unchecked") + @Override + public List getSpecific(Object generic) { + if (generic instanceof Collection) { + List items = new ArrayList(); + + // Copy everything to a new list + for (Object item : (Collection) 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 specific) { + Collection newContainer = (Collection) 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> getSpecificType() { + // Damn you Java + Class dummy = List.class; + return (Class>) dummy; + } + } + ); + } + + /** + * Retrieve a converter for watchable objects and the respective wrapper. + * @return A watchable object converter. + */ + public static EquivalentConverter getWatchableObjectConverter() { + return getIgnoreNull(new EquivalentConverter() { + @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 getSpecificType() { + return WrappedWatchableObject.class; + } + }); + } + + /** + * Retrieve a converter for the NMS DataWatcher class and our wrapper. + * @return A DataWatcher converter. + */ + public static EquivalentConverter getDataWatcherConverter() { + return getIgnoreNull(new EquivalentConverter() { + @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 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 getWorldTypeConverter() { + // Check that we can actually use this converter + if (!hasWorldType) + return null; + + return getIgnoreNull(new EquivalentConverter() { + @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 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 getEntityConverter(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 }); + + return getIgnoreNull(new EquivalentConverter() { + + @Override + public Object getGeneric(Class genericType, 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 getSpecificType() { + return Entity.class; + } + }); + } + + /** + * Retrieve the converter used to convert NMS ItemStacks to Bukkit's ItemStack. + * @return Item stack converter. + */ + public static EquivalentConverter getItemStackConverter() { + return getIgnoreNull(new EquivalentConverter() { + 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 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 EquivalentConverter getIgnoreNull(final EquivalentConverter delegate) { + // Automatically wrap all parameters to the delegate with a NULL check + return new EquivalentConverter() { + 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 getSpecificType() { + return delegate.getSpecificType(); + } + }; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java index 7cbd5840..e1066fbb 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/ChunkPosition.java @@ -8,11 +8,17 @@ import com.comphenix.protocol.reflect.StructureModifier; import com.google.common.base.Objects; /** - * Wraps a immutable net.minecraft.server.ChunkPosition, which represents a integer 3D vector. + * 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; @@ -74,14 +80,56 @@ public class ChunkPosition { 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 + * @return A new converter. */ public static EquivalentConverter getConverter() { return new EquivalentConverter() { @Override - public Object getGeneric(ChunkPosition specific) { + public Object getGeneric(Class genericType, ChunkPosition specific) { return new net.minecraft.server.ChunkPosition(specific.x, specific.z, specific.z); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java new file mode 100644 index 00000000..27926db2 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java @@ -0,0 +1,105 @@ +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 { + + /** + * If TRUE, NULLs should be put before non-null instances of this class. + */ + private static final boolean LARGER_THAN_NULL = true; + + protected ChunkCoordinates handle; + + public WrappedChunkCoordinate(ChunkCoordinates handle) { + if (handle == null) + throw new IllegalArgumentException("handle cannot be NULL"); + this.handle = handle; + } + + 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; + } + + /** + * 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(); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java new file mode 100644 index 00000000..5267b4dd --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -0,0 +1,434 @@ +package com.comphenix.protocol.wrappers; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +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.inventory.ItemStack; + +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, Integer> typeMap; + + // Fields + private static Field valueMapField; + private static Field readWriteLockField; + + // Methods + private static Method createKeyValueMethod; + private static Method updateKeyValueMethod; + private static Method getKeyValueMethod; + + /** + * 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 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); + } + } + + /** + * 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, 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 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 indexSet() throws FieldAccessException { + try { + getReadWriteLock().readLock().lock(); + return new HashSet(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 getWatchableObjectMap() throws FieldAccessException { + if (watchableObjects == null) { + try { + watchableObjects = (Map) FieldUtils.readField(valueMapField, handle, true); + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot read watchable object field.", e); + } + } + return watchableObjects; + } + + /** + * 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, 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 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 + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java new file mode 100644 index 00000000..d9895f9a --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java @@ -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 baseModifier; + + protected WatchableObject handle; + protected StructureModifier 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(WatchableObject.class, null, false); + } + } + + /** + * Retrieve the correct super type of the current value. + * @return Super type. + * @throws FieldAccessException Unable to read values. + */ + public Class getValueType() 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.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.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.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.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 (!getValueType().isAssignableFrom(newValue.getClass())) + throw new IllegalArgumentException("Object " + newValue + " must be of type " + getValueType().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.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.withType(boolean.class).read(0); + } +} From fec2734fe2e14769770a675f2cb73bdf67b598e1 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 13 Nov 2012 15:11:54 +0100 Subject: [PATCH 13/19] Added a couple of useful methods to the wrappers. --- .../protocol/CleanupStaticMembers.java | 8 ++- .../protocol/wrappers/WrappedDataWatcher.java | 68 +++++++++++++++++++ .../wrappers/WrappedWatchableObject.java | 6 +- 3 files changed, 76 insertions(+), 6 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java index 91e86148..1d6fa6da 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -19,8 +19,10 @@ import com.comphenix.protocol.reflect.compiler.StructureCompiler; import com.comphenix.protocol.reflect.instances.CollectionGenerator; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; +import com.comphenix.protocol.wrappers.BukkitConverters; 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. @@ -48,7 +50,8 @@ class CleanupStaticMembers { PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, BackgroundCompiler.class, StructureCompiler.class, ObjectCloner.class, Packets.Server.class, Packets.Client.class, - ChunkPosition.class, WrappedDataWatcher.class + ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class, + BukkitConverters.class }; String[] internalClasses = { @@ -65,8 +68,7 @@ class CleanupStaticMembers { "com.comphenix.protocol.injector.ReadPacketModifier", "com.comphenix.protocol.injector.StructureCache", "com.comphenix.protocol.reflect.compiler.BoxingHelper", - "com.comphenix.protocol.reflect.compiler.MethodDescriptor", - "com.comphenix.protocol.wrappers.WrappedWatchableObject" + "com.comphenix.protocol.reflect.compiler.MethodDescriptor" }; resetClasses(publicClasses); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java index 5267b4dd..b5451328 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -3,6 +3,7 @@ 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; @@ -10,6 +11,7 @@ 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.reflect.FieldAccessException; @@ -42,6 +44,9 @@ public class WrappedDataWatcher { private static Method updateKeyValueMethod; private static Method getKeyValueMethod; + // Entity methods + private static Field entityDataField; + /** * Whether or not this class has already been initialized. */ @@ -80,6 +85,20 @@ public class WrappedDataWatcher { } } + /** + * 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 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. @@ -223,6 +242,32 @@ public class WrappedDataWatcher { } } + /** + * Retrieve every watchable object in this watcher. + * @return Every watchable object. + * @throws FieldAccessException If reflection failed. + */ + public List getWatchableObjects() throws FieldAccessException { + try { + getReadWriteLock().readLock().lock(); + + List result = new ArrayList(); + + // 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. @@ -361,6 +406,29 @@ public class WrappedDataWatcher { 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(Entity.class, true).getFieldByType("datawatcher", DataWatcher.class); + + try { + Object nsmWatcher = FieldUtils.readField(entityDataField, 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. */ diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java index d9895f9a..7323f273 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedWatchableObject.java @@ -53,7 +53,7 @@ public class WrappedWatchableObject { * @return Super type. * @throws FieldAccessException Unable to read values. */ - public Class getValueType() throws FieldAccessException { + public Class getType() throws FieldAccessException { if (typeClass == null) { typeClass = WrappedDataWatcher.getTypeClass(getTypeID()); @@ -120,8 +120,8 @@ public class WrappedWatchableObject { // Verify a few quick things if (newValue == null) throw new IllegalArgumentException("Cannot watch a NULL value."); - if (!getValueType().isAssignableFrom(newValue.getClass())) - throw new IllegalArgumentException("Object " + newValue + " must be of type " + getValueType().getName()); + 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) From 528468a3421be5741e637f3c20c769dec7786af8 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 13 Nov 2012 16:10:56 +0100 Subject: [PATCH 14/19] Fixed a bug preventing the entity modifier from reading entities. --- .../protocol/CleanupStaticMembers.java | 4 +- .../comphenix/protocol/ProtocolManager.java | 10 ++ .../protocol/injector/EntityUtilities.java | 101 ++++++++++++------ .../injector/PacketFilterManager.java | 6 ++ .../protocol/wrappers/BukkitConverters.java | 50 +++------ .../protocol/wrappers/WrappedDataWatcher.java | 8 +- 6 files changed, 106 insertions(+), 73 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java index 1d6fa6da..f04aebdc 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -19,7 +19,6 @@ import com.comphenix.protocol.reflect.compiler.StructureCompiler; import com.comphenix.protocol.reflect.instances.CollectionGenerator; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; -import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.ChunkPosition; import com.comphenix.protocol.wrappers.WrappedDataWatcher; import com.comphenix.protocol.wrappers.WrappedWatchableObject; @@ -50,8 +49,7 @@ class CleanupStaticMembers { PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, BackgroundCompiler.class, StructureCompiler.class, ObjectCloner.class, Packets.Server.class, Packets.Client.class, - ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class, - BukkitConverters.class + ChunkPosition.class, WrappedDataWatcher.class, WrappedWatchableObject.class }; String[] internalClasses = { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolManager.java index f23e20c7..cbdc93f1 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolManager.java @@ -20,6 +20,7 @@ package com.comphenix.protocol; import java.util.List; import java.util.Set; +import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; @@ -105,6 +106,15 @@ public interface ProtocolManager extends PacketStream { */ public void updateEntity(Entity entity, List 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. * @return Every filtered server packet. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java index 8dbd1567..9671c6ce 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/EntityUtilities.java @@ -27,6 +27,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import net.minecraft.server.EntityTrackerEntry; + import org.bukkit.World; import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.entity.Entity; @@ -83,8 +85,51 @@ class EntityUtilities { * */ public static void updateEntity(Entity entity, List observers) throws FieldAccessException { - - World world = entity.getWorld(); + try { + //EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId()); + Object trackerEntry = getEntityTrackerEntry(entity.getWorld(), 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 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(); // We have to rely on the class naming here. @@ -148,41 +193,37 @@ class EntityUtilities { } } } - + + // Wrap exceptions try { - //EntityTrackerEntry trackEntity = (EntityTrackerEntry) tracker.trackedEntities.get(entity.getEntityId()); - 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 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); - + return hashGetMethod.invoke(trackedEntities, entityID); } 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 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); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index 29ee6f9d..988e773b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -34,6 +34,7 @@ import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import org.bukkit.Server; +import org.bukkit.World; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -565,6 +566,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok 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. * @param players - list of players to inject. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java index ee7faf9f..c8bc5935 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/BukkitConverters.java @@ -1,7 +1,6 @@ package com.comphenix.protocol.wrappers; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -9,18 +8,16 @@ import java.util.List; import net.minecraft.server.DataWatcher; import net.minecraft.server.WatchableObject; -import org.bukkit.Bukkit; -import org.bukkit.Server; import org.bukkit.World; import org.bukkit.WorldType; -import org.bukkit.craftbukkit.CraftWorld; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.entity.Entity; -import org.bukkit.entity.Player; 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.FuzzyReflection; +import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.instances.DefaultInstances; /** @@ -32,9 +29,6 @@ public class BukkitConverters { // Check whether or not certain classes exists private static boolean hasWorldType = false; - // The getEntity method - private static Method getEntity; - static { try { Class.forName("net.minecraft.server.WorldType"); @@ -183,13 +177,10 @@ public class BukkitConverters { * @return A converter between the underlying NMS entity and Bukkit's wrapper. */ public static EquivalentConverter getEntityConverter(World world) { - final Object worldServer = ((CraftWorld) world).getHandle(); - final Class nmsEntityClass = net.minecraft.server.Entity.class; + final World container = world; + final WeakReference managerRef = + new WeakReference(ProtocolLibrary.getProtocolManager()); - if (getEntity == null) - getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters( - "getEntity", nmsEntityClass, new Class[] { int.class }); - return getIgnoreNull(new EquivalentConverter() { @Override @@ -201,34 +192,17 @@ public class BukkitConverters { @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(); + // Use the + if (id != null && managerRef.get() != null) { + return managerRef.get().getEntityFromID(container, id); } 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()); + } catch (FieldAccessException e) { + throw new RuntimeException("Cannot retrieve entity from ID.", e); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java index b5451328..f8326c6c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedDataWatcher.java @@ -14,6 +14,7 @@ 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; @@ -414,10 +415,13 @@ public class WrappedDataWatcher { */ public static WrappedDataWatcher getEntityWatcher(Entity entity) throws FieldAccessException { if (entityDataField == null) - entityDataField = FuzzyReflection.fromClass(Entity.class, true).getFieldByType("datawatcher", DataWatcher.class); + entityDataField = FuzzyReflection.fromClass(net.minecraft.server.Entity.class, true). + getFieldByType("datawatcher", DataWatcher.class); + BukkitUnwrapper unwrapper = new BukkitUnwrapper(); + try { - Object nsmWatcher = FieldUtils.readField(entityDataField, entity, true); + Object nsmWatcher = FieldUtils.readField(entityDataField, unwrapper.unwrapItem(entity), true); if (nsmWatcher != null) return new WrappedDataWatcher((DataWatcher) nsmWatcher); From 7f5288dea6e013547380c6a0ae619ecca8d1102b Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 13 Nov 2012 16:23:19 +0100 Subject: [PATCH 15/19] Add getIntegers(), getLongs(), ect. to PacketContainer. --- .../protocol/events/PacketContainer.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index e9ce9714..5d62e953 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -123,6 +123,61 @@ public class PacketContainer implements Serializable { return structureModifier.withType(primitiveType); } + /** + * Retrieves a read/write structure for every byte field. + * @return A modifier for every byte field. + */ + public StructureModifier getBytes() { + return structureModifier.withType(byte.class); + } + + /** + * Retrieves a read/write structure for every short field. + * @return A modifier for every short field. + */ + public StructureModifier getShorts() { + return structureModifier.withType(short.class); + } + + /** + * Retrieves a read/write structure for every integer field. + * @return A modifier for every integer field. + */ + public StructureModifier getIntegers() { + return structureModifier.withType(int.class); + } + /** + * Retrieves a read/write structure for every long field. + * @return A modifier for every long field. + */ + public StructureModifier getLongs() { + return structureModifier.withType(long.class); + } + + /** + * Retrieves a read/write structure for every float field. + * @return A modifier for every float field. + */ + public StructureModifier getFloat() { + return structureModifier.withType(float.class); + } + + /** + * Retrieves a read/write structure for every double field. + * @return A modifier for every double field. + */ + public StructureModifier getDoubles() { + return structureModifier.withType(double.class); + } + + /** + * Retrieves a read/write structure for every String field. + * @return A modifier for every String field. + */ + public StructureModifier getStrings() { + return structureModifier.withType(String.class); + } + /** * Retrieves a read/write structure for ItemStack. *

From 80b99fccd9b002287c313f52986c78d8f00b9224 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 13 Nov 2012 16:43:52 +0100 Subject: [PATCH 16/19] Added some array methods too. --- .../protocol/events/PacketContainer.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 5d62e953..4150acba 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -178,6 +178,22 @@ public class PacketContainer implements Serializable { 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 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 getByteArrays() { + return structureModifier.withType(byte[].class); + } + /** * Retrieves a read/write structure for ItemStack. *

From 235e6eeed2dc516ff9eedd90952261fa7a52a048 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 13 Nov 2012 16:49:49 +0100 Subject: [PATCH 17/19] Made it possible to convert between a chunk position and a coordinate. --- .../wrappers/WrappedChunkCoordinate.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java index 27926db2..14a42b0f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedChunkCoordinate.java @@ -18,12 +18,44 @@ public class WrappedChunkCoordinate implements Comparable Date: Tue, 13 Nov 2012 17:08:50 +0100 Subject: [PATCH 18/19] Bumping version to 1.6.0 --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 1d9f7229..f1cce832 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.5.2-SNAPSHOT + 1.6.0 jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 0bdc7271..9e7da5b7 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.5.2-SNAPSHOT +version: 1.6.0 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From c2d79c142f43c6f2d306e8fe4639e183aa8fb902 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 13 Nov 2012 17:45:49 +0100 Subject: [PATCH 19/19] Generated changes. --- ProtocolLib/dependency-reduced-pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index 2cab33a2..52eae950 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.5.2-SNAPSHOT + 1.6.0 Provides read/write access to the Minecraft protocol. http://dev.bukkit.org/server-mods/protocollib/