From 69a5675161730c6f272c0ec0a8f3662f924f598c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 17 Oct 2012 09:53:59 +0200 Subject: [PATCH 01/54] Fixed a bug causing a StackOverflowException on packet construction. --- .../compiler/CompiledStructureModifier.java | 21 +++++++++++++++++++ .../reflect/compiler/StructureCompiler.java | 5 ++--- 2 files changed, 23 insertions(+), 3 deletions(-) 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 caea3167..602815f8 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 @@ -67,6 +67,16 @@ public abstract class CompiledStructureModifier extends StructureModifie else return (TField) result; } + + /** + * Read the given field index using reflection. + * @param index - index of field. + * @return Resulting value. + * @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints. + */ + protected Object readReflected(int index) throws FieldAccessException { + return super.read(index); + } protected abstract Object readGenerated(int fieldIndex) throws FieldAccessException; @@ -78,6 +88,17 @@ public abstract class CompiledStructureModifier extends StructureModifie return writeGenerated(index, value); } + /** + * Write the given field using reflection. + * @param index - index of field. + * @param value - new value. + * @throws FieldAccessException The field doesn't exist, or it cannot be accessed under the current security contraints. + */ + @SuppressWarnings("unchecked") + protected void writeReflected(int index, Object value) throws FieldAccessException { + super.write(index, (TField) value); + } + protected abstract StructureModifier writeGenerated(int index, Object value) throws FieldAccessException; @Override 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 14f8bdb2..fde9de08 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 @@ -335,8 +335,7 @@ public final class StructureCompiler { mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ILOAD, 1); mv.visitVarInsn(Opcodes.ALOAD, 2); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "write", "(ILjava/lang/Object;)L" + SUPER_CLASS + ";"); - mv.visitInsn(Opcodes.POP); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, COMPILED_CLASS, "writeReflected", "(ILjava/lang/Object;)V;"); } mv.visitJumpInsn(Opcodes.GOTO, returnLabel); @@ -408,7 +407,7 @@ public final class StructureCompiler { // We have to use reflection for private and protected fields. mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ILOAD, 1); - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "read", "(I)Ljava/lang/Object;"); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, COMPILED_CLASS, "readReflected", "(I)Ljava/lang/Object;"); } mv.visitInsn(Opcodes.ARETURN); From 90f2caa5e87b88fa26e6828a3c2a5a2d3178056f Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 17 Oct 2012 09:54:28 +0200 Subject: [PATCH 02/54] Incremented to 1.4.1 --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/java/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index b0f132f2..866baec1 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.4.0 + 1.4.1 jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/java/plugin.yml b/ProtocolLib/src/main/java/plugin.yml index 9d100284..d73ba95c 100644 --- a/ProtocolLib/src/main/java/plugin.yml +++ b/ProtocolLib/src/main/java/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.4.0 +version: 1.4.1 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From caed0dcb1106f6c6647e3e3a2830dabd4a90a0f7 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 19 Oct 2012 00:54:03 +0200 Subject: [PATCH 03/54] Removed the spammy "Entity does not exist" error. --- ProtocolLib/dependency-reduced-pom.xml | 2 +- .../java/com/comphenix/protocol/events/PacketContainer.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index c0332d8a..09e31779 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.4.0 + 1.4.1 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/events/PacketContainer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java index e1cf9b94..920611b5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -303,7 +303,6 @@ public class PacketContainer implements Serializable { } } - System.out.println("Entity doesn't exist."); return null; } From 331fc9419016c630c1fae1a00029d7972a4754ce Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 19 Oct 2012 16:27:49 +0200 Subject: [PATCH 04/54] Sending or receiving packets now work without listeners. --- .../protocol/injector/PacketFilterManager.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 2aef7f5b..b4af084f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; @@ -134,6 +135,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok private AtomicInteger phaseLoginCount = new AtomicInteger(0); private AtomicInteger phasePlayingCount = new AtomicInteger(0); + // Whether or not plugins are using the send/receive methods + private AtomicBoolean packetCreation = new AtomicBoolean(); + /** * Only create instances of this class if protocol lib is disabled. * @param unhookTask @@ -481,6 +485,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok throw new IllegalArgumentException("reciever cannot be NULL."); if (packet == null) throw new IllegalArgumentException("packet cannot be NULL."); + // We may have to enable player injection indefinitely after this + if (packetCreation.compareAndSet(false, true)) + incrementPhases(GamePhase.PLAYING); playerInjection.sendServerPacket(reciever, packet, filters); } @@ -492,11 +499,13 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok @Override public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException { - if (sender == null) throw new IllegalArgumentException("sender cannot be NULL."); if (packet == null) throw new IllegalArgumentException("packet cannot be NULL."); + // And here too + if (packetCreation.compareAndSet(false, true)) + incrementPhases(GamePhase.PLAYING); Packet mcPacket = packet.getHandle(); From 43c0b5d8f1d5d062ed13aff978cebe6a1a639d7b Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 21 Oct 2012 12:11:59 +0200 Subject: [PATCH 05/54] Don't save NULL injectors in a ConcurrentHashMap. --- .../protocol/injector/player/PlayerInjectionHandler.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index d6a69316..25aecf27 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -340,8 +340,10 @@ public class PlayerInjectionHandler { if (permanentHook != getPlayerHook(phase)) setPlayerHook(phase, tempHook); - // Save last injector - playerInjection.put(player, injector); + // Save injector + if (injector != null) { + playerInjection.put(player, injector); + } } return injector; From 63c468eff0b46b215021203997d336be4e0c3550 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 21 Oct 2012 16:33:41 +0200 Subject: [PATCH 06/54] Packet constructor can now handle primitive parameter types. --- .../protocol/injector/PacketConstructor.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java index c00c7f72..07b509f4 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java @@ -25,6 +25,7 @@ import net.minecraft.server.Packet; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.PrimitiveUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; @@ -176,7 +177,17 @@ public class PacketConstructor { // Determine if the types are similar if (params.length == types.length) { for (int i = 0; i < params.length; i++) { - if (!params[i].isAssignableFrom(types[i])) { + Class inputType = types[i]; + Class paramType = params[i]; + + // The input type is always wrapped + if (PrimitiveUtils.isPrimitive(paramType)) { + // Wrap it + paramType = PrimitiveUtils.wrap(paramType); + } + + // Compare assignability + if (!paramType.isAssignableFrom(inputType)) { return false; } } From c08106a9234a12be559fc44da03c57b3a9c15baf Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 21 Oct 2012 16:33:52 +0200 Subject: [PATCH 07/54] Increment to 1.4.2 snapshot. --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/java/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 866baec1..f1c2d940 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.4.1 + 1.4.2-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/java/plugin.yml b/ProtocolLib/src/main/java/plugin.yml index d73ba95c..242f994a 100644 --- a/ProtocolLib/src/main/java/plugin.yml +++ b/ProtocolLib/src/main/java/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.4.1 +version: 1.4.2 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From a51ad1f50f9d9f225012e9d73b5b9b05854b7d25 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 21 Oct 2012 20:31:20 +0200 Subject: [PATCH 08/54] Properly remove previous hook, even if socket is NULL. FIXES Ticket-1. --- ProtocolLib/dependency-reduced-pom.xml | 2 +- .../injector/player/NetLoginInjector.java | 27 ++++- .../injector/player/NetworkFieldInjector.java | 2 +- .../player/NetworkObjectInjector.java | 2 +- .../player/NetworkServerInjector.java | 2 +- .../player/PlayerInjectionHandler.java | 107 ++++++++++++++---- .../injector/player/PlayerInjector.java | 38 ++++++- .../player/TemporaryPlayerFactory.java | 5 +- .../reflect/instances/DefaultInstances.java | 1 - 9 files changed, 159 insertions(+), 27 deletions(-) diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index 09e31779..a9c613e4 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.4.1 + 1.4.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/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index 7f753fbb..0bc8981a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -63,6 +63,9 @@ class NetLoginInjector { InjectContainer container = (InjectContainer) fakePlayer; container.setInjector(injector); + // Save the login + injectedLogins.putIfAbsent(inserting, injector); + // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler return inserting; @@ -93,8 +96,30 @@ class NetLoginInjector { PlayerInjector injected = injectedLogins.get(removing); if (injected != null) { - injected.cleanupAll(); + PlayerInjector newInjector = null; + Player player = injected.getPlayer(); + + // Clean up list injectedLogins.remove(removing); + + // No need to clean up twice + if (injected.isClean()) + return; + + // Hack to clean up other references + newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager()); + + // Update NetworkManager + if (newInjector == null) { + injectionHandler.uninjectPlayer(player); + } else { + injectionHandler.uninjectPlayer(player, false); + + if (injected instanceof NetworkObjectInjector) + newInjector.setNetworkManager(injected.getNetworkManager(), true); + } + + //logger.warning("Using alternative cleanup method."); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java index d6c21dbb..10bf4f1d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java @@ -166,7 +166,7 @@ class NetworkFieldInjector extends PlayerInjector { } @SuppressWarnings("unchecked") - public void cleanupAll() { + protected void cleanHook() { // Clean up for (VolatileField overriden : overridenLists) { List minecraftList = (List) overriden.getOldValue(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java index e8d07dd8..9cf12a21 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java @@ -152,7 +152,7 @@ class NetworkObjectInjector extends PlayerInjector { } @Override - public void cleanupAll() { + protected void cleanHook() { // Clean up if (networkManagerRef != null && networkManagerRef.isCurrentSet()) { networkManagerRef.revertValue(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index 5f8330d0..9872947a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -232,7 +232,7 @@ public class NetworkServerInjector extends PlayerInjector { } @Override - public void cleanupAll() { + protected void cleanHook() { if (serverHandlerRef != null && serverHandlerRef.isCurrentSet()) { ObjectCloner.copyTo(serverHandlerRef.getValue(), serverHandlerRef.getOldValue(), serverHandler.getClass()); serverHandlerRef.revertValue(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index 25aecf27..14b3279a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -281,28 +281,27 @@ public class PlayerInjectionHandler { DataInputStream inputStream = injector.getInputStream(false); Socket socket = injector.getSocket(); - SocketAddress address = socket.getRemoteSocketAddress(); + SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null; // Make sure the current player is not logged out - if (socket.isClosed()) { + if (socket != null && socket.isClosed()) { throw new PlayerLoggedOutException(); } - PlayerInjector previous = addressLookup.get(address); + // Guard against NPE here too + PlayerInjector previous = address != null ? addressLookup.get(address) : null; // Close any previously associated hooks before we proceed if (previous != null) { - uninjectPlayer(previous.getPlayer()); - - // Remove the "hooked" network manager in our instance as well - if (previous instanceof NetworkObjectInjector) { - injector.setNetworkManager(previous.getNetworkManager(), true); - } + uninjectPlayer(previous.getPlayer(), false, true); } - + injector.injectManager(); - dataInputLookup.put(inputStream, injector); - addressLookup.put(address, injector); + + if (inputStream != null) + dataInputLookup.put(inputStream, injector); + if (address != null) + addressLookup.put(address, injector); break; } @@ -362,8 +361,30 @@ public class PlayerInjectionHandler { /** * Unregisters the given player. * @param player - player to unregister. + * @return TRUE if a player has been uninjected, FALSE otherwise. */ - public void uninjectPlayer(Player player) { + public boolean uninjectPlayer(Player player) { + return uninjectPlayer(player, true, false); + } + + /** + * Unregisters the given player. + * @param player - player to unregister. + * @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address. + * @return TRUE if a player has been uninjected, FALSE otherwise. + */ + public boolean uninjectPlayer(Player player, boolean removeAuxiliary) { + return uninjectPlayer(player, removeAuxiliary, false); + } + + /** + * Unregisters the given player. + * @param player - player to unregister. + * @param removeAuxiliary - TRUE to remove auxiliary information, such as input stream and address. + * @param prepareNextHook - whether or not we need to fix any lingering hooks. + * @return TRUE if a player has been uninjected, FALSE otherwise. + */ + private boolean uninjectPlayer(Player player, boolean removeAuxiliary, boolean prepareNextHook) { if (!hasClosed && player != null) { PlayerInjector injector = playerInjection.remove(player); @@ -373,26 +394,54 @@ public class PlayerInjectionHandler { InetSocketAddress address = player.getAddress(); injector.cleanupAll(); - dataInputLookup.remove(input); + // Remove the "hooked" network manager in our instance as well + if (prepareNextHook && injector instanceof NetworkObjectInjector) { + try { + PlayerInjector dummyInjector = getHookInstance(player, PlayerInjectHooks.NETWORK_SERVER_OBJECT); + dummyInjector.initializePlayer(player); + dummyInjector.setNetworkManager(injector.getNetworkManager(), true); + + } catch (IllegalAccessException e) { + // Let the user know + logger.log(Level.WARNING, "Unable to fully revert old injector. May cause conflicts.", e); + } + } - if (address != null) - addressLookup.remove(address); + // Clean up + if (removeAuxiliary) { + if (input != null) + dataInputLookup.remove(input); + if (address != null) + addressLookup.remove(address); + } + return true; } - } + } + + return false; } /** * Unregisters a player by the given address. + *

+ * If the server handler has been created before we've gotten a chance to unject the player, + * the method will try a workaround to remove the injected hook in the NetServerHandler. + * * @param address - address of the player to unregister. + * @param serverHandler - whether or not the net server handler has already been created. + * @return TRUE if a player has been uninjected, FALSE otherwise. */ - public void uninjectPlayer(InetSocketAddress address) { + public boolean uninjectPlayer(InetSocketAddress address) { if (!hasClosed && address != null) { PlayerInjector injector = addressLookup.get(address); // Clean up if (injector != null) - uninjectPlayer(injector.getPlayer()); + uninjectPlayer(injector.getPlayer(), false, true); + return true; } + + return false; } /** @@ -445,6 +494,26 @@ public class PlayerInjectionHandler { return playerInjection.get(player); } + /** + * Retrieve a player injector by looking for its NetworkManager. + * @param networkManager - current network manager. + * @return Related player injector. + */ + PlayerInjector getInjectorByNetworkHandler(Object networkManager) { + // That's not legal + if (networkManager == null) + return null; + + // O(n) is okay in this instance. This is only a backup solution. + for (PlayerInjector injector : playerInjection.values()) { + if (injector.getNetworkManager() == networkManager) + return injector; + } + + // None found + return null; + } + /** * Determine if the given listeners are valid. * @param listeners - listeners to check. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index 3e2457e9..739df11c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -23,6 +23,7 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Socket; +import java.net.SocketAddress; import java.util.logging.Level; import java.util.logging.Logger; @@ -104,6 +105,9 @@ abstract class PlayerInjector { // Scheduled action on the next packet event protected Runnable scheduledAction; + // Whether or not the injector has been cleaned + private boolean clean; + // Whether or not to update the current player on the first Packet1Login boolean updateOnLogin; Player updatedPlayer; @@ -256,6 +260,21 @@ abstract class PlayerInjector { } } + /** + * Retrieve the associated address of this player. + * @return The associated address. + * @throws IllegalAccessException If we're unable to read the socket field. + */ + public SocketAddress getAddress() throws IllegalAccessException { + Socket socket = getSocket(); + + // Guard against NULL + if (socket != null) + return socket.getRemoteSocketAddress(); + else + return null; + } + /** * Attempt to disconnect the current client. * @param message - the message to display. @@ -427,7 +446,24 @@ abstract class PlayerInjector { /** * Remove all hooks and modifications. */ - public abstract void cleanupAll(); + public final void cleanupAll() { + if (!clean) + cleanHook(); + clean = true; + } + + /** + * Override to add custom cleanup behavior. + */ + protected abstract void cleanHook(); + + /** + * Determine whether or not this hook has already been cleaned. + * @return TRUE if it has, FALSE otherwise. + */ + public boolean isClean() { + return clean; + } /** * Determine if this inject method can even be attempted. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java index bb1d6a96..b6afb6a8 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java @@ -76,13 +76,16 @@ class TemporaryPlayerFactory { String methodName = method.getName(); PlayerInjector injector = ((InjectContainer) obj).getInjector(); + if (injector == null) + throw new IllegalStateException("Unable to find injector."); + // Use the socket to get the address if (methodName.equalsIgnoreCase("getName")) return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]"; if (methodName.equalsIgnoreCase("getPlayer")) return injector.getUpdatedPlayer(); if (methodName.equalsIgnoreCase("getAddress")) - return injector.getSocket().getRemoteSocketAddress(); + return injector.getAddress(); if (methodName.equalsIgnoreCase("getServer")) return server; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java index 46505891..38066613 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/DefaultInstances.java @@ -206,7 +206,6 @@ public class DefaultInstances { // Just check if any of them are NULL for (Class type : types) { if (getDefaultInternal(type, providers, recursionLevel) == null) { - System.out.println(type.getName() + " is NULL!"); return true; } } From 01c481dc93ec7b33988e25bd2c0db900ffb35de2 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 21 Oct 2012 20:42:29 +0200 Subject: [PATCH 09/54] Release of 1.4.2 --- ProtocolLib/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index f1c2d940..8203765f 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.4.2-SNAPSHOT + 1.4.2 jar Provides read/write access to the Minecraft protocol. From a60dc3d778c2ed2fcfdabfa100425faf598c9cad Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 21 Oct 2012 22:39:56 +0200 Subject: [PATCH 10/54] Make the client packet reader thread-safe. --- .../protocol/injector/ReadPacketModifier.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java index 54a75b54..67b3fae5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java @@ -20,6 +20,8 @@ package com.comphenix.protocol.injector; import java.io.DataInputStream; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Collections; +import java.util.Map; import java.util.WeakHashMap; import com.comphenix.protocol.Packets; @@ -35,12 +37,15 @@ class ReadPacketModifier implements MethodInterceptor { @SuppressWarnings("rawtypes") private static Class[] parameters = { DataInputStream.class }; + // A cancel marker + private static final Object CANCEL_MARKER = new Object(); + // Common for all packets of the same type private PacketInjector packetInjector; private int packetID; // Whether or not a packet has been cancelled - private static WeakHashMap override = new WeakHashMap(); + private static Map override = Collections.synchronizedMap(new WeakHashMap()); public ReadPacketModifier(int packetID, PacketInjector packetInjector) { this.packetID = packetID; @@ -75,11 +80,12 @@ class ReadPacketModifier implements MethodInterceptor { return proxy.invokeSuper(thisObj, args); } - if (override.containsKey(thisObj)) { - Object overridenObject = override.get(thisObj); - + // Atomic retrieval + Object overridenObject = override.get(thisObj); + + if (overridenObject != null) { // This packet has been cancelled - if (overridenObject == null) { + if (overridenObject == CANCEL_MARKER) { // So, cancel all void methods if (method.getReturnType().equals(Void.TYPE)) return null; @@ -108,7 +114,7 @@ class ReadPacketModifier implements MethodInterceptor { Packet result = event.getPacket().getHandle(); if (event.isCancelled()) { - override.put(thisObj, null); + override.put(thisObj, CANCEL_MARKER); } else if (!objectEquals(thisObj, result)) { override.put(thisObj, result); } From 0aac8ebf0a6d26ea5cefdefae6144cb7a2598c6c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 21 Oct 2012 22:40:26 +0200 Subject: [PATCH 11/54] Increment to 1.4.3 snapshot. --- ProtocolLib/dependency-reduced-pom.xml | 2 +- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/java/plugin.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index a9c613e4..9ab24145 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.4.2-SNAPSHOT + 1.4.2 Provides read/write access to the Minecraft protocol. http://dev.bukkit.org/server-mods/protocollib/ diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 8203765f..b60d461e 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.4.2 + 1.4.3-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/java/plugin.yml b/ProtocolLib/src/main/java/plugin.yml index 242f994a..cb4caf7f 100644 --- a/ProtocolLib/src/main/java/plugin.yml +++ b/ProtocolLib/src/main/java/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.4.2 +version: 1.4.3 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 96ad954cf725be77c3a565c147399079b18c8088 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 23 Oct 2012 00:37:35 +0200 Subject: [PATCH 12/54] Prevent the PlayerQuitEvent from being fired twice. FIXES Ticket-2. --- ProtocolLib/dependency-reduced-pom.xml | 2 +- .../injector/PacketFilterManager.java | 13 +++++-- .../player/InjectedServerConnection.java | 2 +- .../injector/player/NetworkFieldInjector.java | 5 +++ .../player/NetworkObjectInjector.java | 5 +++ .../player/NetworkServerInjector.java | 37 +++++++++++++++++++ .../player/PlayerInjectionHandler.java | 12 ++++++ .../injector/player/PlayerInjector.java | 5 +++ .../injector/player/ReplacedArrayList.java | 32 +++++++++++++++- 9 files changed, 105 insertions(+), 8 deletions(-) diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index 9ab24145..657d089f 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.4.2 + 1.4.3-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/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index b4af084f..afbb5fcd 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -606,6 +606,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPlayerQuit(PlayerQuitEvent event) { + playerInjection.handleDisconnect(event.getPlayer()); playerInjection.uninjectPlayer(event.getPlayer()); } @@ -689,10 +690,14 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok Object event = args[0]; // Check for the correct event - if (event instanceof PlayerJoinEvent) - playerInjection.injectPlayer(((PlayerJoinEvent) event).getPlayer()); - else if (event instanceof PlayerQuitEvent) - playerInjection.uninjectPlayer(((PlayerQuitEvent) event).getPlayer()); + if (event instanceof PlayerJoinEvent) { + Player player = ((PlayerJoinEvent) event).getPlayer(); + playerInjection.injectPlayer(player); + } else if (event instanceof PlayerQuitEvent) { + Player player = ((PlayerQuitEvent) event).getPlayer(); + playerInjection.handleDisconnect(player); + playerInjection.uninjectPlayer(player); + } } return null; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java index d65fd405..83d61646 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java @@ -241,7 +241,7 @@ class InjectedServerConnection { // Clean up? if (removing instanceof NetLoginHandler) { netLoginInjector.cleanup(removing); - } + } } }; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java index 10bf4f1d..19104e74 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java @@ -191,6 +191,11 @@ class NetworkFieldInjector extends PlayerInjector { overridenLists.clear(); } + @Override + public void handleDisconnect() { + // No need to do anything + } + @Override public boolean canInject(GamePhase phase) { // All phases should work diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java index 9cf12a21..e0b77d46 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java @@ -159,6 +159,11 @@ class NetworkObjectInjector extends PlayerInjector { } } + @Override + public void handleDisconnect() { + // No need to do anything + } + @Override public boolean canInject(GamePhase phase) { // Works for all phases diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index 9872947a..1fcb28ed 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -17,8 +17,10 @@ package com.comphenix.protocol.injector.player; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.logging.Level; import java.util.logging.Logger; import net.minecraft.server.Packet; @@ -50,6 +52,7 @@ import com.comphenix.protocol.reflect.instances.ExistingGenerator; */ public class NetworkServerInjector extends PlayerInjector { + private static Field disconnectField; private static Method sendPacketMethod; private InjectedServerConnection serverInjection; @@ -59,6 +62,9 @@ public class NetworkServerInjector extends PlayerInjector { // Used to create proxy objects private ClassLoader classLoader; + // Whether or not the player has disconnected + private boolean hasDisconnected; + public NetworkServerInjector( ClassLoader classLoader, Logger logger, Player player, ListenerInvoker invoker, IntegerSet sendingFilters, @@ -250,11 +256,42 @@ public class NetworkServerInjector extends PlayerInjector { } catch (IllegalAccessException e) { e.printStackTrace(); } + + // Prevent the PlayerQuitEvent from being sent twice + if (hasDisconnected) { + setDisconnect(serverHandlerRef.getValue(), true); + } } serverInjection.revertServerHandler(serverHandler); } + @Override + public void handleDisconnect() { + hasDisconnected = true; + } + + /** + * Set the disconnected field in a NetServerHandler. + * @param handler - the NetServerHandler. + * @param value - the new value. + */ + private void setDisconnect(Object handler, boolean value) { + // Set it + try { + // Load the field + if (disconnectField == null) { + disconnectField = FuzzyReflection.fromObject(handler).getFieldByName("disconnected.*"); + } + FieldUtils.writeField(disconnectField, handler, value); + + } catch (IllegalArgumentException e) { + logger.log(Level.WARNING, "Unable to find disconnect field. Is ProtocolLib up to date?"); + } catch (IllegalAccessException e) { + logger.log(Level.WARNING, "Unable to update disconnected field. Player quit event may be sent twice."); + } + } + @Override public void checkListener(PacketListener listener) { // We support everything diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index 14b3279a..e601dc72 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -358,6 +358,18 @@ public class PlayerInjectionHandler { } } + /** + * Invoke special routines for handling disconnect before a player is uninjected. + * @param player - player to process. + */ + public void handleDisconnect(Player player) { + PlayerInjector injector = getInjector(player); + + if (injector != null) { + injector.handleDisconnect(); + } + } + /** * Unregisters the given player. * @param player - player to unregister. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index 739df11c..a4c2d605 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -451,6 +451,11 @@ abstract class PlayerInjector { cleanHook(); clean = true; } + + /** + * Clean up after the player has disconnected. + */ + public abstract void handleDisconnect(); /** * Override to add custom cleanup behavior. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ReplacedArrayList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ReplacedArrayList.java index 6ebb9cb7..8fe8b3e3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ReplacedArrayList.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ReplacedArrayList.java @@ -68,7 +68,7 @@ class ReplacedArrayList extends ArrayList { } /** - * Invoksed when an element is being removed. + * Invoked when an element is being removed. * @param removing - the element being removed. */ protected void onRemoved(TKey removing) { @@ -264,6 +264,15 @@ class ReplacedArrayList extends ArrayList { addMapping(target, replacement, false); } + /** + * Retrieve the old value, if it exists. + * @param target - the key. + * @return The value that was replaced, or NULL. + */ + public TKey getMapping(TKey target) { + return replaceMap.get(target); + } + /** * Add a replace rule. *

@@ -284,8 +293,9 @@ class ReplacedArrayList extends ArrayList { /** * Revert the given mapping. * @param target - the instance we replaced. + * @return The old mapped value, or NULL if nothing was replaced. */ - public synchronized void removeMapping(TKey target) { + public synchronized TKey removeMapping(TKey target) { // Make sure the mapping exist if (replaceMap.containsKey(target)) { TKey replacement = replaceMap.get(target); @@ -293,7 +303,25 @@ class ReplacedArrayList extends ArrayList { // Revert existing elements replaceAll(replacement, target); + return replacement; } + return null; + } + + /** + * Swap the new replaced value with its old value. + * @param target - the instance we replaced. + * @param The old mapped value, or NULL if nothing was swapped. + */ + public synchronized TKey swapMapping(TKey target) { + // Make sure the mapping exist + TKey replacement = removeMapping(target); + + // Add the reverse + if (replacement != null) { + replaceMap.put(replacement, target); + } + return replacement; } /** From 7536815e58df818bf7365a8a3e329046164cfade Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 23 Oct 2012 00:41:53 +0200 Subject: [PATCH 13/54] Set as SNAPSHOT in the plugin-file as well. --- ProtocolLib/src/main/java/plugin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/plugin.yml b/ProtocolLib/src/main/java/plugin.yml index cb4caf7f..ab891d1e 100644 --- a/ProtocolLib/src/main/java/plugin.yml +++ b/ProtocolLib/src/main/java/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.4.3 +version: 1.4.3-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 4e9b5009c8e1a07dc57cd02051f325788ac8e3d1 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 23 Oct 2012 00:54:41 +0200 Subject: [PATCH 14/54] Don't print messages to the console without a plugin prefix. It's bad practice. --- .../src/main/java/com/comphenix/protocol/ProtocolLibrary.java | 4 ++-- .../java/com/comphenix/protocol/async/PacketSendingQueue.java | 2 +- .../protocol/injector/player/PlayerInjectionHandler.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index d36cebe1..9878ab66 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -117,7 +117,7 @@ public class ProtocolLibrary extends JavaPlugin { public void onPacketReceiving(PacketEvent event) { Object handle = event.getPacket().getHandle(); - System.out.println(String.format( + logger.info(String.format( "RECEIVING %s@%s from %s.", handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName() )); @@ -126,7 +126,7 @@ public class ProtocolLibrary extends JavaPlugin { public void onPacketSending(PacketEvent event) { Object handle = event.getPacket().getHandle(); - System.out.println(String.format( + logger.info(String.format( "SENDING %s@%s from %s.", handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName() )); 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 01fd8449..e9816259 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java @@ -205,7 +205,7 @@ class PacketSendingQueue { } catch (PlayerLoggedOutException e) { System.out.println(String.format( - "Warning: Dropped packet index %s of ID %s", + "[ProtocolLib] Warning: Dropped packet index %s of ID %s", marker.getOriginalSendingIndex(), event.getPacketID() )); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index e601dc72..afc432b9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -198,7 +198,7 @@ public class PlayerInjectionHandler { if (injector != null) { return injector.getPlayer(); } else { - System.out.println("Unable to find stream: " + inputStream); + logger.warning("Unable to find stream: " + inputStream); return null; } From cb2c7b4e0ad6576191fb3742e3bc5aa88969b651 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 28 Oct 2012 06:09:29 +0100 Subject: [PATCH 15/54] Use a class cache when constructing the packet interceptor. --- .../java/com/comphenix/protocol/injector/PacketInjector.java | 1 - 1 file changed, 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java index 1361d64c..82bcb303 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java @@ -129,7 +129,6 @@ class PacketInjector { // Subclass the specific packet class ex.setSuperclass(old); ex.setCallbackType(ReadPacketModifier.class); - ex.setUseCache(false); ex.setClassLoader(classLoader); Class proxy = ex.createClass(); From eaf0b73c002f43f4d6909eecd337e67a56ec3232 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 28 Oct 2012 06:10:31 +0100 Subject: [PATCH 16/54] Increment version to 1.4.4-SNAPSHOT (skipping release of 1.4.3) --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/java/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index b60d461e..df4c475d 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.4.3-SNAPSHOT + 1.4.4-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/java/plugin.yml b/ProtocolLib/src/main/java/plugin.yml index ab891d1e..23f7aed6 100644 --- a/ProtocolLib/src/main/java/plugin.yml +++ b/ProtocolLib/src/main/java/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.4.3-SNAPSHOT +version: 1.4.4-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From bdc41221dbb9feddeb2c44a7f37dd05f63d673db Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 01:42:22 +0100 Subject: [PATCH 17/54] Fixed a serious misuse causing the creation of too many new classes. The different injectors using CGLib was using a custom CallbackFilter to optimize the resulting generated class. Unfortunately, the custom filter didn't properly implement equals() and hashCode(), and so every time a player logged, a new injector class had to be generated. This was fixed by making the injectors share a single CallbackFilter. In addition, I've begun adding cleanup code that will reset all static fields once the plugin has unloaded. --- ProtocolLib/dependency-reduced-pom.xml | 2 +- .../comphenix/protocol/ProtocolLibrary.java | 4 +++ .../protocol/async/AsyncListenerHandler.java | 2 +- .../protocol/injector/PacketConstructor.java | 2 +- .../player/NetworkObjectInjector.java | 26 ++++++++++------ .../player/NetworkServerInjector.java | 27 +++++++++++------ .../player/TemporaryPlayerFactory.java | 30 ++++++++++++------- 7 files changed, 61 insertions(+), 32 deletions(-) diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index 657d089f..7019dfad 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.4.3-SNAPSHOT + 1.4.4-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/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 9878ab66..04acbc26 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -190,6 +190,10 @@ public class ProtocolLibrary extends JavaPlugin { protocolManager.close(); protocolManager = null; statistisc = null; + + // Leaky ClassLoader begone! + CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), logger); + cleanup.resetAll(); } /** diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java index db01761b..9f77400f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -54,7 +54,7 @@ public class AsyncListenerHandler { private static final AtomicInteger nextID = new AtomicInteger(); // Default queue capacity - private static int DEFAULT_CAPACITY = 1024; + private static final int DEFAULT_CAPACITY = 1024; // Cancel the async handler private volatile boolean cancelled; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java index 07b509f4..f8e0e654 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java @@ -41,7 +41,7 @@ public class PacketConstructor { *

* Remember to call withPacket(). */ - public static final PacketConstructor DEFAULT = new PacketConstructor(null); + public static PacketConstructor DEFAULT = new PacketConstructor(null); // The constructor method that's actually responsible for creating the packet private Constructor constructorMethod; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java index e0b77d46..4f98d3cd 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java @@ -50,6 +50,9 @@ class NetworkObjectInjector extends PlayerInjector { // Used to construct proxy objects private ClassLoader classLoader; + + // Shared callback filter - avoid creating a new class every time + private static CallbackFilter callbackFilter; public NetworkObjectInjector(ClassLoader classLoader, Logger logger, Player player, ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException { @@ -131,20 +134,25 @@ class NetworkObjectInjector extends PlayerInjector { } }; + // Share callback filter - that way, we avoid generating a new class every time. + if (callbackFilter == null) { + callbackFilter = new CallbackFilter() { + @Override + public int accept(Method method) { + if (method.equals(queueMethod)) + return 0; + else + return 1; + } + }; + } + // Create our proxy object Enhancer ex = new Enhancer(); ex.setClassLoader(classLoader); ex.setSuperclass(networkInterface); ex.setCallbacks(new Callback[] { queueFilter, dispatch }); - ex.setCallbackFilter(new CallbackFilter() { - @Override - public int accept(Method method) { - if (method.equals(queueMethod)) - return 0; - else - return 1; - } - }); + ex.setCallbackFilter(callbackFilter); // Inject it, if we can. networkManagerRef.setValue(ex.create()); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index 1fcb28ed..9ad93166 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -52,6 +52,8 @@ import com.comphenix.protocol.reflect.instances.ExistingGenerator; */ public class NetworkServerInjector extends PlayerInjector { + private volatile static CallbackFilter callbackFilter; + private static Field disconnectField; private static Method sendPacketMethod; private InjectedServerConnection serverInjection; @@ -170,18 +172,24 @@ public class NetworkServerInjector extends PlayerInjector { }; Callback noOpCallback = NoOp.INSTANCE; + // Share callback filter - that way, we avoid generating a new class for + // every logged in player. + if (callbackFilter == null) { + callbackFilter = new CallbackFilter() { + @Override + public int accept(Method method) { + if (method.equals(sendPacketMethod)) + return 0; + else + return 1; + } + }; + } + ex.setClassLoader(classLoader); ex.setSuperclass(serverClass); ex.setCallbacks(new Callback[] { sendPacketCallback, noOpCallback }); - ex.setCallbackFilter(new CallbackFilter() { - @Override - public int accept(Method method) { - if (method.equals(sendPacketMethod)) - return 0; - else - return 1; - } - }); + ex.setCallbackFilter(callbackFilter); // Find the Minecraft NetServerHandler superclass Class minecraftSuperClass = getFirstMinecraftSuperClass(serverHandler.getClass()); @@ -208,6 +216,7 @@ public class NetworkServerInjector extends PlayerInjector { if (proxyObject != null) { // This will be done by InjectedServerConnection instead //copyTo(serverHandler, proxyObject); + serverInjection.replaceServerHandler(serverHandler, proxyObject); serverHandlerRef.setValue(proxyObject); return true; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java index b6afb6a8..891edc7c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/TemporaryPlayerFactory.java @@ -42,6 +42,9 @@ class TemporaryPlayerFactory { // Helpful constructors private final PacketConstructor chatPacket; + // Prevent too many class creations + private static CallbackFilter callbackFilter; + public TemporaryPlayerFactory() { chatPacket = PacketConstructor.DEFAULT.withPacket(3, new Object[] { "DEMO" }); } @@ -120,22 +123,27 @@ class TemporaryPlayerFactory { } }; + // Shared callback filter + if (callbackFilter == null) { + callbackFilter = new CallbackFilter() { + @Override + public int accept(Method method) { + // Do not override the object method or the superclass methods + if (method.getDeclaringClass().equals(Object.class) || + method.getDeclaringClass().equals(InjectContainer.class)) + return 0; + else + return 1; + } + }; + } + // CGLib is amazing Enhancer ex = new Enhancer(); ex.setSuperclass(InjectContainer.class); ex.setInterfaces(new Class[] { Player.class }); ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation }); - ex.setCallbackFilter(new CallbackFilter() { - @Override - public int accept(Method method) { - // Do not override the object method or the superclass methods - if (method.getDeclaringClass().equals(Object.class) || - method.getDeclaringClass().equals(InjectContainer.class)) - return 0; - else - return 1; - } - }); + ex.setCallbackFilter(callbackFilter); return (Player) ex.create(); } From dfba92456105ba24c7b5dda4a81f982bda56177a Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 02:25:29 +0100 Subject: [PATCH 18/54] Clean up every static field, preempting potential ClassLoader leaks. Note that this method is using some fairly ugly hacks and must be maintained/updated manually whenever a new class is added or removed. --- .../protocol/CleanupStaticMembers.java | 135 ++++++++++++++++++ .../comphenix/protocol/ProtocolLibrary.java | 2 + 2 files changed, 137 insertions(+) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java new file mode 100644 index 00000000..b3b5e118 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -0,0 +1,135 @@ +package com.comphenix.protocol; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.comphenix.protocol.async.AsyncListenerHandler; +import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.injector.BukkitUnwrapper; +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.MethodUtils; +import com.comphenix.protocol.reflect.ObjectCloner; +import com.comphenix.protocol.reflect.PrimitiveUtils; +import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; +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; + +/** + * Used to fix ClassLoader leaks that may lead to filling up the permanent generation. + * + * @author Kristian + */ +class CleanupStaticMembers { + + private ClassLoader loader; + private Logger logger; + + public CleanupStaticMembers(ClassLoader loader, Logger logger) { + this.loader = loader; + this.logger = logger; + } + + /** + * Ensure that the previous ClassLoader is not leaking. + */ + public void resetAll() { + // This list must always be updated + Class[] publicClasses = { + AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class, + BukkitUnwrapper.class, CollectionGenerator.class, DefaultInstances.class, + PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, + BackgroundCompiler.class, StructureCompiler.class, + ObjectCloner.class, PrimitiveUtils.class, Packets.Server.class, + Packets.Client.class + }; + + String[] internalClasses = { + "com.comphenix.protocol.events.SerializedOfflinePlayer", + "com.comphenix.protocol.injector.player.InjectedServerConnection", + "com.comphenix.protocol.injector.player.NetworkFieldInjector", + "com.comphenix.protocol.injector.player.NetworkObjectInjector", + "com.comphenix.protocol.injector.player.NetworkServerInjector", + "com.comphenix.protocol.injector.player.PlayerInjector", + "com.comphenix.protocol.injector.player.TemporaryPlayerFactory", + "com.comphenix.protocol.injector.EntityUtilities", + "com.comphenix.protocol.injector.MinecraftRegistry", + "com.comphenix.protocol.injector.PacketInjector", + "com.comphenix.protocol.injector.ReadPacketModifier", + "com.comphenix.protocol.injector.StructureCache", + "com.comphenix.protocol.reflect.compiler.BoxingHelper", + "com.comphenix.protocol.reflect.compiler.MethodDescriptor" + }; + + resetClasses(publicClasses); + resetClasses(getClasses(loader, internalClasses)); + } + + private void resetClasses(Class[] classes) { + // Reset each class one by one + for (Class clazz : classes) { + resetClass(clazz); + } + } + + private void resetClass(Class clazz) { + for (Field field : clazz.getFields()) { + Class type = field.getType(); + + // Only check static non-primitive fields. We also skip strings. + if (Modifier.isStatic(field.getModifiers()) && + !type.isPrimitive() && !type.equals(String.class)) { + + try { + setFinalStatic(field, null); + } catch (IllegalAccessException e) { + // Just inform us + logger.warning("Unable to reset field " + field.getName() + ": " + e.getMessage()); + } + } + } + } + + // HACK! HAACK! + private static void setFinalStatic(Field field, Object newValue) throws IllegalAccessException { + int modifier = field.getModifiers(); + boolean isFinal = Modifier.isFinal(modifier); + + Field modifiersField = isFinal ? FieldUtils.getField(Field.class, "modifiers", true) : null; + + // We have to remove the final field first + if (isFinal) { + FieldUtils.writeField(modifiersField, field, modifier & ~Modifier.FINAL, true); + } + + // Now we can safely modify the field + FieldUtils.writeStaticField(field, newValue, true); + + // Revert modifier + if (isFinal) { + FieldUtils.writeField(modifiersField, field, modifier, true); + } + } + + private Class[] getClasses(ClassLoader loader, String[] names) { + List> output = new ArrayList>(); + + for (String name : names) { + try { + output.add(loader.loadClass(name)); + } catch (ClassNotFoundException e) { + // Warn the user + logger.log(Level.WARNING, "Unable to unload class " + name, e); + } + } + + return output.toArray(new Class[0]); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 04acbc26..91a32c46 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -77,6 +77,8 @@ public class ProtocolLibrary extends JavaPlugin { Server server = getServer(); PluginManager manager = server.getPluginManager(); + System.out.println("Created using ClassLoader " + getClassLoader().hashCode()); + // Initialize background compiler if (backgroundCompiler == null) { backgroundCompiler = new BackgroundCompiler(getClassLoader()); From 03da0e19749e54531834be8234ac7914d955bc1b Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 02:37:10 +0100 Subject: [PATCH 19/54] Increment to 1.5.0 Release --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/java/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index df4c475d..a841b2a9 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.4.4-SNAPSHOT + 1.5.0 jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/java/plugin.yml b/ProtocolLib/src/main/java/plugin.yml index 23f7aed6..c742800b 100644 --- a/ProtocolLib/src/main/java/plugin.yml +++ b/ProtocolLib/src/main/java/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.4.4-SNAPSHOT +version: 1.5.0 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 5e036185b810bc08f151b614be5c87b02e653684 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 02:39:05 +0100 Subject: [PATCH 20/54] Removed DEBUG message. --- .../src/main/java/com/comphenix/protocol/ProtocolLibrary.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 91a32c46..85e00881 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -76,9 +76,7 @@ public class ProtocolLibrary extends JavaPlugin { public void onEnable() { Server server = getServer(); PluginManager manager = server.getPluginManager(); - - System.out.println("Created using ClassLoader " + getClassLoader().hashCode()); - + // Initialize background compiler if (backgroundCompiler == null) { backgroundCompiler = new BackgroundCompiler(getClassLoader()); From 2239c1ea3204a4e37f588c4ba72ed8dd429546e6 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 04:17:53 +0100 Subject: [PATCH 21/54] Add a better error reporter. --- ProtocolLib/dependency-reduced-pom.xml | 2 +- .../protocol/AsynchronousManager.java | 8 +- .../protocol/CleanupStaticMembers.java | 15 +- .../comphenix/protocol/ProtocolLibrary.java | 81 ++++-- .../protocol/async/AsyncFilterManager.java | 14 +- .../protocol/async/AsyncListenerHandler.java | 3 +- .../protocol/error/DetailedErrorReporter.java | 268 ++++++++++++++++++ .../protocol/error/ErrorReporter.java | 39 +++ .../protocol/events/PacketAdapter.java | 14 +- .../injector/PacketFilterManager.java | 36 ++- .../injector/SortedPacketListenerList.java | 20 +- .../player/InjectedServerConnection.java | 29 +- .../injector/player/NetLoginInjector.java | 13 +- .../injector/player/NetworkFieldInjector.java | 6 +- .../player/NetworkObjectInjector.java | 6 +- .../player/NetworkServerInjector.java | 11 +- .../player/PlayerInjectionHandler.java | 48 ++-- .../injector/player/PlayerInjector.java | 34 ++- 18 files changed, 484 insertions(+), 163 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index 7019dfad..5fb86ef9 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.4.4-SNAPSHOT + 1.5.0 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/AsynchronousManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/AsynchronousManager.java index a16f5db5..a449b1ca 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/AsynchronousManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/AsynchronousManager.java @@ -18,11 +18,11 @@ package com.comphenix.protocol; import java.util.Set; -import java.util.logging.Logger; import org.bukkit.plugin.Plugin; import com.comphenix.protocol.async.AsyncListenerHandler; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; @@ -81,10 +81,10 @@ public interface AsynchronousManager { public abstract PacketStream getPacketStream(); /** - * Retrieve the default error logger. - * @return Default logger. + * Retrieve the default error reporter. + * @return Default reporter. */ - public abstract Logger getLogger(); + public abstract ErrorReporter getErrorReporter(); /** * Remove listeners, close threads and transmit every delayed packet. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java index b3b5e118..269345f9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -4,10 +4,9 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; import com.comphenix.protocol.async.AsyncListenerHandler; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.injector.BukkitUnwrapper; @@ -30,11 +29,11 @@ import com.comphenix.protocol.reflect.instances.PrimitiveGenerator; class CleanupStaticMembers { private ClassLoader loader; - private Logger logger; + private ErrorReporter reporter; - public CleanupStaticMembers(ClassLoader loader, Logger logger) { + public CleanupStaticMembers(ClassLoader loader, ErrorReporter reporter) { this.loader = loader; - this.logger = logger; + this.reporter = reporter; } /** @@ -90,8 +89,8 @@ class CleanupStaticMembers { try { setFinalStatic(field, null); } catch (IllegalAccessException e) { - // Just inform us - logger.warning("Unable to reset field " + field.getName() + ": " + e.getMessage()); + // Just inform the player + reporter.reportWarning(this, "Unable to reset field " + field.getName() + ": " + e.getMessage(), e); } } } @@ -126,7 +125,7 @@ class CleanupStaticMembers { output.add(loader.loadClass(name)); } catch (ClassNotFoundException e) { // Warn the user - logger.log(Level.WARNING, "Unable to unload class " + name, e); + reporter.reportWarning(this, "Unable to unload class " + name, e); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 85e00881..c17b84d9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -18,7 +18,6 @@ package com.comphenix.protocol; import java.io.IOException; -import java.util.logging.Level; import java.util.logging.Logger; import org.bukkit.Server; @@ -26,6 +25,8 @@ import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import com.comphenix.protocol.async.AsyncFilterManager; +import com.comphenix.protocol.error.DetailedErrorReporter; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.events.MonitorAdapter; import com.comphenix.protocol.events.PacketEvent; @@ -44,9 +45,12 @@ public class ProtocolLibrary extends JavaPlugin { // There should only be one protocol manager, so we'll make it static private static PacketFilterManager protocolManager; - // Error logger + // Information logger private Logger logger; + // Error reporter + private ErrorReporter reporter; + // Metrics and statistisc private Statistics statistisc; @@ -67,39 +71,55 @@ public class ProtocolLibrary extends JavaPlugin { @Override public void onLoad() { - logger = getLoggerSafely(); - unhookTask = new DelayedSingleTask(this); - protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, logger); + // Add global parameters + DetailedErrorReporter reporter = new DetailedErrorReporter(); + + try { + logger = getLoggerSafely(); + unhookTask = new DelayedSingleTask(this); + protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, reporter); + reporter.addGlobalParameter("manager", protocolManager); + + } catch (Throwable e) { + reporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager); + } } @Override public void onEnable() { - Server server = getServer(); - PluginManager manager = server.getPluginManager(); - - // Initialize background compiler - if (backgroundCompiler == null) { - backgroundCompiler = new BackgroundCompiler(getClassLoader()); - BackgroundCompiler.setInstance(backgroundCompiler); - } - - // Notify server managers of incompatible plugins - checkForIncompatibility(manager); - - // Player login and logout events - protocolManager.registerEvents(manager, this); + try { + Server server = getServer(); + PluginManager manager = server.getPluginManager(); + + // Initialize background compiler + if (backgroundCompiler == null) { + backgroundCompiler = new BackgroundCompiler(getClassLoader()); + BackgroundCompiler.setInstance(backgroundCompiler); + } + + // Notify server managers of incompatible plugins + checkForIncompatibility(manager); - // Worker that ensures that async packets are eventually sent - createAsyncTask(server); - //toggleDebugListener(); + // Player login and logout events + protocolManager.registerEvents(manager, this); + + // Worker that ensures that async packets are eventually sent + createAsyncTask(server); + //toggleDebugListener(); + + } catch (Throwable e) { + reporter.reportDetailed(this, "Cannot enable ProtocolLib.", e); + disablePlugin(); + return; + } // Try to enable statistics try { statistisc = new Statistics(this); } catch (IOException e) { - logger.log(Level.SEVERE, "Unable to enable metrics.", e); + reporter.reportDetailed(this, "Unable to enable metrics.", e, statistisc); } catch (Throwable e) { - logger.log(Level.SEVERE, "Metrics cannot be enabled. Incompatible Bukkit version.", e); + reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc); } } @@ -136,6 +156,13 @@ public class ProtocolLibrary extends JavaPlugin { debugListener = !debugListener; } + /** + * Disable the current plugin. + */ + private void disablePlugin() { + getServer().getPluginManager().disablePlugin(this); + } + private void createAsyncTask(Server server) { try { if (asyncPacketTask >= 0) @@ -154,7 +181,7 @@ public class ProtocolLibrary extends JavaPlugin { } catch (Throwable e) { if (asyncPacketTask == -1) { - logger.log(Level.SEVERE, "Unable to create packet timeout task.", e); + reporter.reportDetailed(this, "Unable to create packet timeout task.", e); } } } @@ -166,7 +193,7 @@ public class ProtocolLibrary extends JavaPlugin { for (String plugin : incompatiblePlugins) { if (manager.getPlugin(plugin) != null) { // Check for versions, ect. - logger.severe("Detected incompatible plugin: " + plugin); + reporter.reportWarning(this, "Detected incompatible plugin: " + plugin); } } } @@ -192,7 +219,7 @@ public class ProtocolLibrary extends JavaPlugin { statistisc = null; // Leaky ClassLoader begone! - CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), logger); + CleanupStaticMembers cleanup = new CleanupStaticMembers(getClassLoader(), reporter); cleanup.resetAll(); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java index e0707229..7b5744e7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java @@ -21,7 +21,6 @@ import java.util.Collection; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Logger; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitScheduler; @@ -29,6 +28,7 @@ import org.bukkit.scheduler.BukkitScheduler; import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.PacketStream; import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; @@ -49,7 +49,7 @@ public class AsyncFilterManager implements AsynchronousManager { private PacketProcessingQueue clientProcessingQueue; private PacketSendingQueue clientQueue; - private Logger logger; + private ErrorReporter reporter; // The likely main thread private Thread mainThread; @@ -66,7 +66,7 @@ public class AsyncFilterManager implements AsynchronousManager { // Whether or not we're currently cleaning up private volatile boolean cleaningUp; - public AsyncFilterManager(Logger logger, BukkitScheduler scheduler, ProtocolManager manager) { + public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler, ProtocolManager manager) { // Server packets are synchronized already this.serverQueue = new PacketSendingQueue(false); @@ -80,7 +80,7 @@ public class AsyncFilterManager implements AsynchronousManager { this.scheduler = scheduler; this.manager = manager; - this.logger = logger; + this.reporter = reporter; this.mainThread = Thread.currentThread(); } @@ -267,10 +267,10 @@ public class AsyncFilterManager implements AsynchronousManager { } @Override - public Logger getLogger() { - return logger; + public ErrorReporter getErrorReporter() { + return reporter; } - + @Override public void cleanupAll() { cleaningUp = true; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java index 9f77400f..96d04db3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -442,8 +442,7 @@ public class AsyncListenerHandler { } catch (Throwable e) { // Minecraft doesn't want your Exception. - filterManager.getLogger().log(Level.SEVERE, - "Unhandled exception occured in onAsyncPacket() for " + getPluginName(), e); + filterManager.getErrorReporter().reportMinimal(listener.getPlugin(), "onAsyncPacket()", e); } // Now, get the next non-cancelled listener diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java new file mode 100644 index 00000000..13f0a5eb --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java @@ -0,0 +1,268 @@ +package com.comphenix.protocol.error; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.events.PacketAdapter; +import com.google.common.primitives.Primitives; + +/** + * Internal class used to handle exceptions. + * + * @author Kristian + */ +public class DetailedErrorReporter implements ErrorReporter { + + public static final String SECOND_LEVEL_PREFIX = " "; + public static final String DEFAULT_PREFIX = " "; + public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/"; + public static final String PLUGIN_NAME = "ProtocolLib"; + + // We don't want to spam the server + public static final int DEFAULT_MAX_ERROR_COUNT = 20; + + protected String prefix; + protected String supportURL; + + protected int errorCount; + protected int maxErrorCount; + protected Logger logger; + + // Whether or not Apache Commons is not present + protected boolean apacheCommonsMissing; + + // Map of global objects + protected Map globalParameters = new HashMap(); + + /** + * Create a default error reporting system. + */ + public DetailedErrorReporter() { + this(DEFAULT_PREFIX, DEFAULT_SUPPORT_URL); + } + + /** + * Create a central error reporting system. + * @param prefix - default line prefix. + * @param supportURL - URL to report the error. + */ + public DetailedErrorReporter(String prefix, String supportURL) { + this(prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger()); + } + + // Attempt to get the logger. + private static Logger getBukkitLogger() { + try { + return Bukkit.getLogger(); + } catch (Throwable e) { + return Logger.getLogger("Minecraft"); + } + } + + /** + * Create a central error reporting system. + * @param prefix - default line prefix. + * @param supportURL - URL to report the error. + * @param maxErrorCount - number of errors to print before giving up. + * @param logger - current logger. + */ + public DetailedErrorReporter(String prefix, String supportURL, int maxErrorCount, Logger logger) { + this.prefix = prefix; + this.supportURL = supportURL; + this.maxErrorCount = maxErrorCount; + this.logger = logger; + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error) { + logger.log(Level.SEVERE, "[" + PLUGIN_NAME + "] Unhandled exception occured in " + methodName + " for " + + PacketAdapter.getPluginName(sender), error); + } + + @Override + public void reportWarning(Object sender, String message) { + logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message); + } + + @Override + public void reportWarning(Object sender, String message, Throwable error) { + logger.log(Level.WARNING, "[" + PLUGIN_NAME + "] [" + getSenderName(sender) + "] " + message, error); + } + + private String getSenderName(Object sender) { + if (sender != null) + return sender.getClass().getSimpleName(); + else + return "NULL"; + } + + @Override + public void reportDetailed(Object sender, String message, Throwable error, Object... parameters) { + + // Do not overtly spam the server! + if (++errorCount > maxErrorCount) { + String maxReached = String.format("Reached maxmimum error count. Cannot pass error %s from %s.", error, sender); + logger.severe(maxReached); + return; + } + + StringWriter text = new StringWriter(); + PrintWriter writer = new PrintWriter(text); + + // Helpful message + writer.println("[ProtocolLib] INTERNAL ERROR: " + message); + writer.println("If this problem hasn't already been reported, please open a ticket"); + writer.println("at " + supportURL + " with the following data:"); + + // Now, let us print important exception information + writer.println(" ===== STACK TRACE ====="); + + if (error != null) + error.printStackTrace(writer); + + // Data dump! + writer.println(" ===== DUMP ====="); + + // Relevant parameters + if (parameters != null && parameters.length > 0) { + writer.println("Parameters:"); + + // We *really* want to get as much information as possible + for (Object param : parameters) { + writer.println(addPrefix(getStringDescription(param), SECOND_LEVEL_PREFIX)); + } + } + + // Global parameters + for (String param : globalParameters()) { + writer.println(SECOND_LEVEL_PREFIX + param + ":"); + writer.println(addPrefix(getStringDescription(getGlobalParameter(param)), + SECOND_LEVEL_PREFIX + SECOND_LEVEL_PREFIX)); + } + + // Now, for the sender itself + writer.println("Sender:"); + writer.println(addPrefix(getStringDescription(sender), SECOND_LEVEL_PREFIX)); + + // Add the server version too + if (Bukkit.getServer() != null) { + writer.println("Server:"); + writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX)); + } + + // Make sure it is reported + logger.severe(addPrefix(text.toString(), prefix)); + } + + /** + * Adds the given prefix to every line in the text. + * @param text - text to modify. + * @param prefix - prefix added to every line in the text. + * @return The modified text. + */ + protected String addPrefix(String text, String prefix) { + return text.replaceAll("(?m)^", prefix); + } + + protected String getStringDescription(Object value) { + + // We can't only rely on toString. + if (value == null) { + return "[NULL]"; + } if (isSimpleType(value)) { + return value.toString(); + } else { + try { + if (!apacheCommonsMissing) + return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null)); + } catch (Throwable ex) { + // Apache is probably missing + logger.warning("Cannot find Apache Commons. Object introspection disabled."); + apacheCommonsMissing = true; + } + + // Just use toString() + return String.format("%s", value); + } + } + + /** + * Determine if the given object is a wrapper for a primitive/simple type or not. + * @param test - the object to test. + * @return TRUE if this object is simple enough to simply be printed, FALSE othewise. + */ + protected boolean isSimpleType(Object test) { + return test instanceof String || Primitives.isWrapperType(test.getClass()); + } + + public int getErrorCount() { + return errorCount; + } + + public void setErrorCount(int errorCount) { + this.errorCount = errorCount; + } + + public int getMaxErrorCount() { + return maxErrorCount; + } + + public void setMaxErrorCount(int maxErrorCount) { + this.maxErrorCount = maxErrorCount; + } + + /** + * Adds the given global parameter. It will be included in every error report. + * @param key - name of parameter. + * @param value - the global parameter itself. + */ + public void addGlobalParameter(String key, Object value) { + globalParameters.put(key, value); + } + + public Object getGlobalParameter(String key) { + return globalParameters.get(key); + } + + public void clearGlobalParameters() { + globalParameters.clear(); + } + + public Set globalParameters() { + return globalParameters.keySet(); + } + + public String getSupportURL() { + return supportURL; + } + + public void setSupportURL(String supportURL) { + this.supportURL = supportURL; + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public Logger getLogger() { + return logger; + } + + public void setLogger(Logger logger) { + this.logger = logger; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java new file mode 100644 index 00000000..de6e1e36 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ErrorReporter.java @@ -0,0 +1,39 @@ +package com.comphenix.protocol.error; + +import org.bukkit.plugin.Plugin; + +public interface ErrorReporter { + + /** + * Prints a small minimal error report about an exception from another plugin. + * @param sender - the other plugin. + * @param methodName - name of the caller method. + * @param error - the exception itself. + */ + public abstract void reportMinimal(Plugin sender, String methodName, Throwable error); + + /** + * Prints a warning message from the current plugin. + * @param sender - the object containing the caller method. + * @param message - error message. + */ + public abstract void reportWarning(Object sender, String message); + + /** + * Prints a warning message from the current plugin. + * @param sender - the object containing the caller method. + * @param message - error message. + * @param error - the exception that was thrown. + */ + public abstract void reportWarning(Object sender, String message, Throwable error); + + /** + * Prints a detailed error report about an unhandled exception. + * @param sender - the object containing the caller method. + * @param message - an error message to include. + * @param error - the exception that was thrown in the caller method. + * @param parameters - parameters from the caller method. + */ + public abstract void reportDetailed(Object sender, String message, Throwable error, Object... parameters); + +} \ No newline at end of file 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 fbe14775..056fe089 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketAdapter.java @@ -168,12 +168,20 @@ public abstract class PacketAdapter implements PacketListener { /** * Retrieves the name of the plugin that has been associated with the listener. + * @param listener - the listener. * @return Name of the associated plugin. */ public static String getPluginName(PacketListener listener) { - - Plugin plugin = listener.getPlugin(); - + return getPluginName(listener.getPlugin()); + } + + /** + * Retrieves the name of the given plugin. + * @param plugin - the plugin. + * @return Name of the given plugin. + */ + public static String getPluginName(Plugin plugin) { + // Try to get the plugin name try { if (plugin == null) 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 afbb5fcd..63473dd2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -25,8 +25,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.Nullable; @@ -51,6 +49,8 @@ import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.async.AsyncMarker; +import com.comphenix.protocol.error.DetailedErrorReporter; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.*; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.reflect.FieldAccessException; @@ -118,8 +118,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // The default class loader private ClassLoader classLoader; - // Error logger - private Logger logger; + // Error repoter + private ErrorReporter reporter; // The current server private Server server; @@ -142,9 +142,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * Only create instances of this class if protocol lib is disabled. * @param unhookTask */ - public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, Logger logger) { - if (logger == null) - throw new IllegalArgumentException("logger cannot be NULL."); + public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, DetailedErrorReporter reporter) { + if (reporter == null) + throw new IllegalArgumentException("reporter cannot be NULL."); if (classLoader == null) throw new IllegalArgumentException("classLoader cannot be NULL."); @@ -155,7 +155,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok this.unhookTask = unhookTask; this.server = server; this.classLoader = classLoader; - this.logger = logger; + this.reporter = reporter; // Used to determine if injection is needed Predicate isInjectionNecessary = new Predicate() { @@ -174,20 +174,20 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok try { // Initialize injection mangers - this.playerInjection = new PlayerInjectionHandler(classLoader, logger, isInjectionNecessary, this, server); + this.playerInjection = new PlayerInjectionHandler(classLoader, reporter, isInjectionNecessary, this, server); this.packetInjector = new PacketInjector(classLoader, this, playerInjection); - this.asyncFilterManager = new AsyncFilterManager(logger, server.getScheduler(), this); + this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this); // Attempt to load the list of server and client packets try { this.serverPackets = MinecraftRegistry.getServerPackets(); this.clientPackets = MinecraftRegistry.getClientPackets(); } catch (FieldAccessException e) { - logger.log(Level.WARNING, "Cannot load server and client packet list.", e); + reporter.reportWarning(this, "Cannot load server and client packet list.", e); } } catch (IllegalAccessException e) { - logger.log(Level.SEVERE, "Unable to initialize packet injector.", e); + reporter.reportWarning(this, "Unable to initialize packet injector.", e); } } @@ -215,10 +215,6 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok playerInjection.checkListener(packetListeners); } - public Logger getLogger() { - return logger; - } - @Override public ImmutableSet getPacketListeners() { return ImmutableSet.copyOf(packetListeners); @@ -398,9 +394,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Process synchronous events if (sending) - packetListeners.invokePacketSending(logger, event); + packetListeners.invokePacketSending(reporter, event); else - packetListeners.invokePacketRecieving(logger, event); + packetListeners.invokePacketRecieving(reporter, event); // To cancel asynchronous processing, use the async marker if (!event.isCancelled() && !hasAsyncCancelled(event.getAsyncMarker())) { @@ -438,7 +434,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (serverPackets != null && serverPackets.contains(packetID)) playerInjection.addPacketHandler(packetID); else - logger.warning(String.format( + reporter.reportWarning(this, String.format( "[%s] Unsupported server packet ID in current Minecraft version: %s", PacketAdapter.getPluginName(listener), packetID )); @@ -449,7 +445,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok if (clientPackets != null && clientPackets.contains(packetID)) packetInjector.addPacketHandler(packetID); else - logger.warning(String.format( + reporter.reportWarning(this, String.format( "[%s] Unsupported client packet ID in current Minecraft version: %s", PacketAdapter.getPluginName(listener), packetID )); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java index 94662c9b..5a1ef288 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java @@ -18,11 +18,9 @@ package com.comphenix.protocol.injector; import java.util.Collection; -import java.util.logging.Level; -import java.util.logging.Logger; import com.comphenix.protocol.concurrency.AbstractConcurrentListenerMultimap; -import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; @@ -35,10 +33,10 @@ class SortedPacketListenerList extends AbstractConcurrentListenerMultimap> list = getListener(event.getPacketID()); if (list == null) @@ -50,19 +48,17 @@ class SortedPacketListenerList extends AbstractConcurrentListenerMultimap> list = getListener(event.getPacketID()); if (list == null) @@ -73,9 +69,7 @@ class SortedPacketListenerList extends AbstractConcurrentListenerMultimap(); this.replacedLists = new ArrayList>(); - this.logger = logger; + this.reporter = reporter; this.server = server; this.netLoginInjector = netLoginInjector; } @@ -83,7 +82,7 @@ class InjectedServerConnection { try { minecraftServer = FieldUtils.readField(minecraftServerField, server, true); } catch (IllegalAccessException e1) { - logger.log(Level.WARNING, "Cannot extract minecraft server from Bukkit."); + reporter.reportWarning(this, "Cannot extract minecraft server from Bukkit."); return; } @@ -95,15 +94,13 @@ class InjectedServerConnection { injectServerConnection(); } catch (IllegalArgumentException e) { - // DEBUG - logger.log(Level.WARNING, "Reverting to old 1.2.5 server connection injection.", e); - + // Minecraft 1.2.5 or lower injectListenerThread(); } catch (Exception e) { // Oh damn - inform the player - logger.log(Level.SEVERE, "Cannot inject into server connection. Bad things will happen.", e); + reporter.reportDetailed(this, "Cannot inject into server connection. Bad things will happen.", e); } } @@ -115,7 +112,7 @@ class InjectedServerConnection { listenerThreadField = FuzzyReflection.fromObject(minecraftServer). getFieldByType(".*NetworkListenThread"); } catch (RuntimeException e) { - logger.log(Level.SEVERE, "Cannot find listener thread in MinecraftServer.", e); + reporter.reportDetailed(this, "Cannot find listener thread in MinecraftServer.", e, minecraftServer); return; } @@ -125,7 +122,7 @@ class InjectedServerConnection { try { listenerThread = listenerThreadField.get(minecraftServer); } catch (Exception e) { - logger.log(Level.WARNING, "Unable to read the listener thread.", e); + reporter.reportWarning(this, "Unable to read the listener thread.", e); return; } @@ -142,7 +139,7 @@ class InjectedServerConnection { try { serverConnection = serverConnectionMethod.invoke(minecraftServer); } catch (Exception ex) { - logger.log(Level.WARNING, "Unable to retrieve server connection", ex); + reporter.reportDetailed(this, "Unable to retrieve server connection", ex, minecraftServer); return; } @@ -154,7 +151,7 @@ class InjectedServerConnection { // Verify the field count if (matches.size() != 1) - logger.log(Level.WARNING, "Unexpected number of threads in " + serverConnection.getClass().getName()); + reporter.reportWarning(this, "Unexpected number of threads in " + serverConnection.getClass().getName()); else dedicatedThreadField = matches.get(0); } @@ -164,7 +161,7 @@ class InjectedServerConnection { if (dedicatedThreadField != null) injectEveryListField(FieldUtils.readField(dedicatedThreadField, serverConnection, true), 1); } catch (IllegalAccessException e) { - logger.log(Level.WARNING, "Unable to retrieve net handler thread.", e); + reporter.reportWarning(this, "Unable to retrieve net handler thread.", e); } injectIntoList(serverConnection, listField); @@ -186,7 +183,7 @@ class InjectedServerConnection { // Warn about unexpected errors if (lists.size() < minimum) { - logger.log(Level.WARNING, "Unable to inject " + minimum + " lists in " + container.getClass().getName()); + reporter.reportWarning(this, "Unable to inject " + minimum + " lists in " + container.getClass().getName()); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index 0bc8981a..b5fc924b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -4,12 +4,11 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.logging.Level; -import java.util.logging.Logger; import org.bukkit.Server; import org.bukkit.entity.Player; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.player.TemporaryPlayerFactory.InjectContainer; import com.google.common.collect.Maps; @@ -27,16 +26,16 @@ class NetLoginInjector { private PlayerInjectionHandler injectionHandler; private Server server; - // The current logger - private Logger logger; + // The current error rerporter + private ErrorReporter reporter; private ReadWriteLock injectionLock = new ReentrantReadWriteLock(); // Used to create fake players private TemporaryPlayerFactory tempPlayerFactory = new TemporaryPlayerFactory(); - public NetLoginInjector(Logger logger, PlayerInjectionHandler injectionHandler, Server server) { - this.logger = logger; + public NetLoginInjector(ErrorReporter reporter, PlayerInjectionHandler injectionHandler, Server server) { + this.reporter = reporter; this.injectionHandler = injectionHandler; this.server = server; } @@ -71,7 +70,7 @@ class NetLoginInjector { } catch (Throwable e) { // Minecraft can't handle this, so we'll deal with it here - logger.log(Level.WARNING, "Unable to hook NetLoginHandler.", e); + reporter.reportDetailed(this, "Unable to hook NetLoginHandler.", e, inserting); return inserting; } finally { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java index 19104e74..2f414f2c 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java @@ -24,11 +24,11 @@ import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; import org.bukkit.entity.Player; import com.comphenix.protocol.Packets; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; @@ -73,10 +73,10 @@ class NetworkFieldInjector extends PlayerInjector { // Used to construct proxy objects private ClassLoader classLoader; - public NetworkFieldInjector(ClassLoader classLoader, Logger logger, Player player, + public NetworkFieldInjector(ClassLoader classLoader, ErrorReporter reporter, Player player, ListenerInvoker manager, IntegerSet sendingFilters) throws IllegalAccessException { - super(logger, player, manager); + super(reporter, player, manager); this.classLoader = classLoader; this.sendingFilters = sendingFilters; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java index 4f98d3cd..6635744d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java @@ -28,11 +28,11 @@ import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; -import java.util.logging.Logger; import org.bukkit.entity.Player; import com.comphenix.protocol.Packets; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ListeningWhitelist; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; @@ -54,9 +54,9 @@ class NetworkObjectInjector extends PlayerInjector { // Shared callback filter - avoid creating a new class every time private static CallbackFilter callbackFilter; - public NetworkObjectInjector(ClassLoader classLoader, Logger logger, Player player, + public NetworkObjectInjector(ClassLoader classLoader, ErrorReporter reporter, Player player, ListenerInvoker invoker, IntegerSet sendingFilters) throws IllegalAccessException { - super(logger, player, invoker); + super(reporter, player, invoker); this.sendingFilters = sendingFilters; this.classLoader = classLoader; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index 9ad93166..70ed7a3f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -20,8 +20,6 @@ package com.comphenix.protocol.injector.player; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.logging.Level; -import java.util.logging.Logger; import net.minecraft.server.Packet; import net.sf.cglib.proxy.Callback; @@ -34,6 +32,7 @@ import net.sf.cglib.proxy.NoOp; import org.bukkit.entity.Player; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.injector.ListenerInvoker; @@ -68,11 +67,11 @@ public class NetworkServerInjector extends PlayerInjector { private boolean hasDisconnected; public NetworkServerInjector( - ClassLoader classLoader, Logger logger, Player player, + ClassLoader classLoader, ErrorReporter reporter, Player player, ListenerInvoker invoker, IntegerSet sendingFilters, InjectedServerConnection serverInjection) throws IllegalAccessException { - super(logger, player, invoker); + super(reporter, player, invoker); this.classLoader = classLoader; this.sendingFilters = sendingFilters; this.serverInjection = serverInjection; @@ -295,9 +294,9 @@ public class NetworkServerInjector extends PlayerInjector { FieldUtils.writeField(disconnectField, handler, value); } catch (IllegalArgumentException e) { - logger.log(Level.WARNING, "Unable to find disconnect field. Is ProtocolLib up to date?"); + reporter.reportDetailed(this, "Unable to find disconnect field. Is ProtocolLib up to date?", e, handler); } catch (IllegalAccessException e) { - logger.log(Level.WARNING, "Unable to update disconnected field. Player quit event may be sent twice."); + reporter.reportWarning(this, "Unable to update disconnected field. Player quit event may be sent twice."); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index afc432b9..215fc3c7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -24,14 +24,13 @@ import java.net.Socket; import java.net.SocketAddress; import java.util.Map; import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; import net.minecraft.server.Packet; import org.bukkit.Server; import org.bukkit.entity.Player; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketListener; @@ -72,8 +71,8 @@ public class PlayerInjectionHandler { private volatile PlayerInjectHooks loginPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; private volatile PlayerInjectHooks playingPlayerHook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; - // Error logger - private Logger logger; + // Error reporter + private ErrorReporter reporter; // Whether or not we're closing private boolean hasClosed; @@ -90,15 +89,15 @@ public class PlayerInjectionHandler { // Used to filter injection attempts private Predicate injectionFilter; - public PlayerInjectionHandler(ClassLoader classLoader, Logger logger, Predicate injectionFilter, + public PlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate injectionFilter, ListenerInvoker invoker, Server server) { this.classLoader = classLoader; - this.logger = logger; + this.reporter = reporter; this.invoker = invoker; this.injectionFilter = injectionFilter; - this.netLoginInjector = new NetLoginInjector(logger, this, server); - this.serverInjection = new InjectedServerConnection(logger, server, netLoginInjector); + this.netLoginInjector = new NetLoginInjector(reporter, this, server); + this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector); serverInjection.injectList(); } @@ -173,11 +172,11 @@ public class PlayerInjectionHandler { // Construct the correct player hook switch (hook) { case NETWORK_HANDLER_FIELDS: - return new NetworkFieldInjector(classLoader, logger, player, invoker, sendingFilters); + return new NetworkFieldInjector(classLoader, reporter, player, invoker, sendingFilters); case NETWORK_MANAGER_OBJECT: - return new NetworkObjectInjector(classLoader, logger, player, invoker, sendingFilters); + return new NetworkObjectInjector(classLoader, reporter, player, invoker, sendingFilters); case NETWORK_SERVER_OBJECT: - return new NetworkServerInjector(classLoader, logger, player, invoker, sendingFilters, serverInjection); + return new NetworkServerInjector(classLoader, reporter, player, invoker, sendingFilters, serverInjection); default: throw new IllegalArgumentException("Cannot construct a player injector."); } @@ -198,7 +197,7 @@ public class PlayerInjectionHandler { if (injector != null) { return injector.getPlayer(); } else { - logger.warning("Unable to find stream: " + inputStream); + reporter.reportWarning(this, "Unable to find stream: " + inputStream); return null; } @@ -310,7 +309,8 @@ public class PlayerInjectionHandler { } catch (Exception e) { // Mark this injection attempt as a failure - logger.log(Level.SEVERE, "Player hook " + tempHook.toString() + " failed.", e); + reporter.reportDetailed(this, "Player hook " + tempHook.toString() + " failed.", + e, player, injectionPoint, phase); hookFailed = true; } @@ -318,7 +318,7 @@ public class PlayerInjectionHandler { tempHook = PlayerInjectHooks.values()[tempHook.ordinal() - 1]; if (hookFailed) - logger.log(Level.INFO, "Switching to " + tempHook.toString() + " instead."); + reporter.reportWarning(this, "Switching to " + tempHook.toString() + " instead."); // Check for UTTER FAILURE if (tempHook == PlayerInjectHooks.NONE) { @@ -353,8 +353,8 @@ public class PlayerInjectionHandler { try { if (injector != null) injector.cleanupAll(); - } catch (Exception e2) { - logger.log(Level.WARNING, "Cleaing up after player hook failed.", e2); + } catch (Exception ex) { + reporter.reportDetailed(this, "Cleaing up after player hook failed.", ex, injector); } } @@ -415,7 +415,7 @@ public class PlayerInjectionHandler { } catch (IllegalAccessException e) { // Let the user know - logger.log(Level.WARNING, "Unable to fully revert old injector. May cause conflicts.", e); + reporter.reportWarning(this, "Unable to fully revert old injector. May cause conflicts.", e); } } @@ -470,7 +470,7 @@ public class PlayerInjectionHandler { if (injector != null) injector.sendServerPacket(packet.getHandle(), filters); else - logger.log(Level.WARNING, String.format( + reporter.reportWarning(this, String.format( "Unable to send packet %s (%s): Player %s has logged out.", packet.getID(), packet, reciever.getName() )); @@ -491,7 +491,7 @@ public class PlayerInjectionHandler { if (injector != null) injector.processPacket(mcPacket); else - logger.log(Level.WARNING, String.format( + reporter.reportWarning(this, String.format( "Unable to receieve packet %s. Player %s has logged out.", mcPacket, player.getName() )); @@ -537,7 +537,7 @@ public class PlayerInjectionHandler { try { checkListener(listener); } catch (IllegalStateException e) { - logger.log(Level.WARNING, "Unsupported listener.", e); + reporter.reportWarning(this, "Unsupported listener.", e); } } } @@ -565,14 +565,6 @@ public class PlayerInjectionHandler { return sendingFilters.toSet(); } - /** - * Retrieve the current logger. - * @return Error logger. - */ - public Logger getLogger() { - return logger; - } - public void close() { // Guard diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index a4c2d605..31b2eeda 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -24,8 +24,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Socket; import java.net.SocketAddress; -import java.util.logging.Level; -import java.util.logging.Logger; import net.minecraft.server.EntityPlayer; import net.minecraft.server.NetLoginHandler; @@ -36,6 +34,7 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; import com.comphenix.protocol.Packets; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; @@ -100,7 +99,7 @@ abstract class PlayerInjector { protected DataInputStream cachedInput; // Handle errors - protected Logger logger; + protected ErrorReporter reporter; // Scheduled action on the next packet event protected Runnable scheduledAction; @@ -112,8 +111,8 @@ abstract class PlayerInjector { boolean updateOnLogin; Player updatedPlayer; - public PlayerInjector(Logger logger, Player player, ListenerInvoker invoker) throws IllegalAccessException { - this.logger = logger; + public PlayerInjector(ErrorReporter reporter, Player player, ListenerInvoker invoker) throws IllegalAccessException { + this.reporter = reporter; this.player = player; this.invoker = invoker; } @@ -303,19 +302,24 @@ abstract class PlayerInjector { disconnect.invoke(handler, message); return; } catch (IllegalArgumentException e) { - logger.log(Level.WARNING, "Invalid argument passed to disconnect method: " + message, e); + reporter.reportDetailed(this, "Invalid argument passed to disconnect method: " + message, e, handler); } catch (IllegalAccessException e) { - logger.log(Level.SEVERE, "Unable to access disconnect method.", e); + reporter.reportWarning(this, "Unable to access disconnect method.", e); } } - + // Fuck it try { - getSocket().close(); - } catch (IOException e) { - logger.log(Level.SEVERE, "Unable to close socket.", e); + Socket socket = getSocket(); + + try { + socket.close(); + } catch (IOException e) { + reporter.reportDetailed(this, "Unable to close socket.", e, socket); + } + } catch (IllegalAccessException e) { - logger.log(Level.SEVERE, "Insufficient permissions. Cannot close socket.", e); + reporter.reportWarning(this, "Insufficient permissions. Cannot close socket.", e); } } @@ -332,7 +336,7 @@ abstract class PlayerInjector { return null; hasProxyType = true; - logger.log(Level.WARNING, "Detected server handler proxy type by another plugin. Conflict may occur!"); + reporter.reportWarning(this, "Detected server handler proxy type by another plugin. Conflict may occur!"); // No? Is it a Proxy type? try { @@ -347,7 +351,7 @@ abstract class PlayerInjector { } } catch (IllegalAccessException e) { - logger.warning("Unable to load server handler from proxy type."); + reporter.reportWarning(this, "Unable to load server handler from proxy type."); } // Nope, just go with it @@ -511,7 +515,7 @@ abstract class PlayerInjector { try { updatedPlayer = getEntityPlayer(getNetHandler()).getBukkitEntity(); } catch (IllegalAccessException e) { - logger.log(Level.WARNING, "Cannot update player in PlayerEvent.", e); + reporter.reportDetailed(this, "Cannot update player in PlayerEvent.", e, packet); } } From 8636215b9874d78b2f536654134467647e96735d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 04:21:12 +0100 Subject: [PATCH 22/54] Add a couple of extra error handlers. --- .../injector/PacketFilterManager.java | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) 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 63473dd2..f4d86ae9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -590,27 +590,43 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok manager.registerEvents(new Listener() { @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) public void onPrePlayerJoin(PlayerJoinEvent event) { - // Let's clean up the other injection first. - playerInjection.uninjectPlayer(event.getPlayer().getAddress()); + try { + // Let's clean up the other injection first. + playerInjection.uninjectPlayer(event.getPlayer().getAddress()); + } catch (Exception e) { + reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject net handler for player.", e, event); + } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPlayerJoin(PlayerJoinEvent event) { - // This call will be ignored if no listeners are registered - playerInjection.injectPlayer(event.getPlayer()); + try { + // This call will be ignored if no listeners are registered + playerInjection.injectPlayer(event.getPlayer()); + } catch (Exception e) { + reporter.reportDetailed(PacketFilterManager.this, "Unable to inject player.", e, event); + } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPlayerQuit(PlayerQuitEvent event) { - playerInjection.handleDisconnect(event.getPlayer()); - playerInjection.uninjectPlayer(event.getPlayer()); + try { + playerInjection.handleDisconnect(event.getPlayer()); + playerInjection.uninjectPlayer(event.getPlayer()); + } catch (Exception e) { + reporter.reportDetailed(PacketFilterManager.this, "Unable to uninject logged off player.", e, event); + } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void onPluginDisabled(PluginDisableEvent event) { - // Clean up in case the plugin forgets - if (event.getPlugin() != plugin) { - removePacketListeners(event.getPlugin()); + try { + // Clean up in case the plugin forgets + if (event.getPlugin() != plugin) { + removePacketListeners(event.getPlugin()); + } + } catch (Exception e) { + reporter.reportDetailed(PacketFilterManager.this, "Unable handle disabled plugin.", e, event); } } From cbf4def71bc63c591976b9716cc970f293588e09 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 16:54:53 +0100 Subject: [PATCH 23/54] Prevented map chunk listeners from crashing the server. If the NetServerHandler injector fails and we revert to the network field injector instead, any map chunk listener will crash the server due to complications with Bukkit's ChunkCompressionThread. We avoid this by disabling map chunk listening entirely. --- .../protocol/CleanupStaticMembers.java | 2 +- .../protocol/injector/ListenerInvoker.java | 21 ++++++++ .../protocol/injector/MinecraftRegistry.java | 6 ++- .../injector/PacketFilterManager.java | 20 +++++-- .../protocol/injector/PacketInjector.java | 2 +- .../injector/player/InjectedArrayList.java | 52 +++++++++++++------ .../injector/player/NetworkFieldInjector.java | 11 ++-- .../player/NetworkObjectInjector.java | 11 ++-- .../player/NetworkServerInjector.java | 3 +- .../player/PlayerInjectionHandler.java | 35 ++++++++----- .../injector/player/PlayerInjector.java | 14 ++++- .../injector/player/UnsupportedListener.java | 47 +++++++++++++++++ 12 files changed, 177 insertions(+), 47 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java index 269345f9..60bbbcfa 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -43,7 +43,7 @@ class CleanupStaticMembers { // This list must always be updated Class[] publicClasses = { AsyncListenerHandler.class, ListeningWhitelist.class, PacketContainer.class, - BukkitUnwrapper.class, CollectionGenerator.class, DefaultInstances.class, + BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class, PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, BackgroundCompiler.class, StructureCompiler.class, ObjectCloner.class, PrimitiveUtils.class, Packets.Server.class, diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java index fd0a8391..ae48ea6d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java @@ -46,4 +46,25 @@ public interface ListenerInvoker { * @return The packet ID. */ public abstract int getPacketID(Packet packet); + + /** + * Associate a given class with the given packet ID. Internal method. + * @param clazz - class to associate. + * @param packetID - the packet ID. + */ + public abstract void unregisterPacketClass(Class clazz); + + /** + * Remove a given class from the packet registry. Internal method. + * @param clazz - class to remove. + */ + public abstract void registerPacketClass(Class clazz, int packetID); + + /** + * Retrieves the correct packet class from a given packet ID. + * @param packetID - the packet ID. + * @param forceVanilla - whether or not to look for vanilla classes, not injected classes. + * @return The associated class. + */ + public abstract Class getPacketClassFromID(int packetID, boolean forceVanilla); } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java index 7b171e0d..34f7a341 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/MinecraftRegistry.java @@ -157,7 +157,7 @@ class MinecraftRegistry { /** * Retrieves the correct packet class from a given packet ID. * @param packetID - the packet ID. - * @param vanilla - whether or not to look for vanilla classes, not injected classes. + * @param forceVanilla - whether or not to look for vanilla classes, not injected classes. * @return The associated class. */ public static Class getPacketClassFromID(int packetID, boolean forceVanilla) { @@ -172,7 +172,9 @@ class MinecraftRegistry { // Will most likely not be used for (Map.Entry entry : getPacketToID().entrySet()) { if (Objects.equal(entry.getValue(), packetID)) { - return entry.getKey(); + // Attempt to get the vanilla class here too + if (!forceVanilla || entry.getKey().getName().startsWith("net.minecraft.server")) + return entry.getKey(); } } 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 f4d86ae9..4284b566 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -174,7 +174,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok try { // Initialize injection mangers - this.playerInjection = new PlayerInjectionHandler(classLoader, reporter, isInjectionNecessary, this, server); + this.playerInjection = new PlayerInjectionHandler(classLoader, reporter, isInjectionNecessary, this, packetListeners, server); this.packetInjector = new PacketInjector(classLoader, this, playerInjection); this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this); @@ -210,9 +210,6 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok */ public void setPlayerHook(PlayerInjectHooks playerHook) { playerInjection.setPlayerHook(playerHook); - - // Make sure the current listeners are compatible - playerInjection.checkListener(packetListeners); } @Override @@ -662,6 +659,21 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok return MinecraftRegistry.getPacketToID().get(packet.getClass()); } + @Override + public void registerPacketClass(Class clazz, int packetID) { + MinecraftRegistry.getPacketToID().put(clazz, packetID); + } + + @Override + public void unregisterPacketClass(Class clazz) { + MinecraftRegistry.getPacketToID().remove(clazz); + } + + @Override + public Class getPacketClassFromID(int packetID, boolean forceVanilla) { + return MinecraftRegistry.getPacketClassFromID(packetID, forceVanilla); + } + // Yes, this is crazy. @SuppressWarnings({ "unchecked", "rawtypes" }) private void registerOld(PluginManager manager, Plugin plugin) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java index 82bcb303..625093da 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java @@ -141,10 +141,10 @@ class PacketInjector { try { // Override values - putMethod.invoke(intHashMap, packetID, proxy); previous.put(packetID, old); registry.put(proxy, packetID); overwritten.put(packetID, proxy); + putMethod.invoke(intHashMap, packetID, proxy); return true; } catch (IllegalArgumentException e) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java index 8b2f1471..f75b0718 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java @@ -22,6 +22,8 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Set; +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket; import net.minecraft.server.Packet; @@ -73,7 +75,7 @@ class InjectedArrayList extends ArrayList { // We'll use the FakePacket marker instead of preventing the filters injector.sendServerPacket(createNegativePacket(packet), true); } - + // Collection.add contract return true; @@ -90,8 +92,10 @@ class InjectedArrayList extends ArrayList { * @return The inverted packet. */ Packet createNegativePacket(Packet source) { - Enhancer ex = new Enhancer(); - Class type = source.getClass(); + ListenerInvoker invoker = injector.getInvoker(); + + int packetID = invoker.getPacketID(source); + Class type = invoker.getPacketClassFromID(packetID, true); // We want to subtract the byte amount that were added to the running // total of outstanding packets. Otherwise, cancelling too many packets @@ -111,22 +115,38 @@ class InjectedArrayList extends ArrayList { // } // ect. // } + Enhancer ex = new Enhancer(); + ex.setSuperclass(type); ex.setInterfaces(new Class[] { FakePacket.class } ); ex.setUseCache(true); ex.setClassLoader(classLoader); - ex.setSuperclass(type); - ex.setCallback(new MethodInterceptor() { - @Override - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - if (method.getReturnType().equals(int.class) && args.length == 0) { - Integer result = (Integer) proxy.invokeSuper(obj, args); - return -result; - } else { - return proxy.invokeSuper(obj, args); - } - } - }); + ex.setCallbackType(InvertedIntegerCallback.class); + + Class proxyClass = ex.createClass(); + + // Temporarily associate the fake packet class + invoker.registerPacketClass(proxyClass, packetID); + + Packet fake = (Packet) Enhancer.create(proxyClass, new InvertedIntegerCallback()); - return (Packet) ex.create(); + // Remove this association + invoker.unregisterPacketClass(proxyClass); + return fake; + } + + /** + * Inverts the integer result of every integer method. + * @author Kristian + */ + private class InvertedIntegerCallback implements MethodInterceptor { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + if (method.getReturnType().equals(int.class) && args.length == 0) { + Integer result = (Integer) proxy.invokeSuper(obj, args); + return -result; + } else { + return proxy.invokeSuper(obj, args); + } + } } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java index 2f414f2c..dd65fa17 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkFieldInjector.java @@ -123,11 +123,14 @@ class NetworkFieldInjector extends PlayerInjector { } @Override - public void checkListener(PacketListener listener) { + public UnsupportedListener checkListener(PacketListener listener) { + int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK }; + // Unfortunately, we don't support chunk packets - if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), - Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) { - throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners."); + if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) { + return new UnsupportedListener("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners.", unsupported); + } else { + return null; } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java index 6635744d..e391d832 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkObjectInjector.java @@ -88,11 +88,14 @@ class NetworkObjectInjector extends PlayerInjector { } @Override - public void checkListener(PacketListener listener) { + public UnsupportedListener checkListener(PacketListener listener) { + int[] unsupported = { Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK }; + // Unfortunately, we don't support chunk packets - if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), - Packets.Server.MAP_CHUNK, Packets.Server.MAP_CHUNK_BULK)) { - throw new IllegalStateException("The NETWORK_FIELD_INJECTOR hook doesn't support map chunk listeners."); + if (ListeningWhitelist.containsAny(listener.getSendingWhitelist(), unsupported)) { + return new UnsupportedListener("The NETWORK_OBJECT_INJECTOR hook doesn't support map chunk listeners.", unsupported); + } else { + return null; } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java index 70ed7a3f..c97d9f50 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetworkServerInjector.java @@ -301,8 +301,9 @@ public class NetworkServerInjector extends PlayerInjector { } @Override - public void checkListener(PacketListener listener) { + public UnsupportedListener checkListener(PacketListener listener) { // We support everything + return null; } @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index 215fc3c7..2b2b86a6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -83,6 +83,9 @@ public class PlayerInjectionHandler { // Enabled packet filters private IntegerSet sendingFilters = new IntegerSet(MAXIMUM_PACKET_ID + 1); + // List of packet listeners + private Set packetListeners; + // The class loader we're using private ClassLoader classLoader; @@ -90,12 +93,13 @@ public class PlayerInjectionHandler { private Predicate injectionFilter; public PlayerInjectionHandler(ClassLoader classLoader, ErrorReporter reporter, Predicate injectionFilter, - ListenerInvoker invoker, Server server) { + ListenerInvoker invoker, Set packetListeners, Server server) { this.classLoader = classLoader; this.reporter = reporter; this.invoker = invoker; this.injectionFilter = injectionFilter; + this.packetListeners = packetListeners; this.netLoginInjector = new NetLoginInjector(reporter, this, server); this.serverInjection = new InjectedServerConnection(reporter, server, netLoginInjector); serverInjection.injectList(); @@ -143,6 +147,9 @@ public class PlayerInjectionHandler { loginPlayerHook = playerHook; if (phase.hasPlaying()) playingPlayerHook = playerHook; + + // Make sure the current listeners are compatible + checkListener(packetListeners); } /** @@ -534,26 +541,30 @@ public class PlayerInjectionHandler { // Make sure the current listeners are compatible if (lastSuccessfulHook != null) { for (PacketListener listener : listeners) { - try { - checkListener(listener); - } catch (IllegalStateException e) { - reporter.reportWarning(this, "Unsupported listener.", e); - } + checkListener(listener); } } } /** * Determine if a listener is valid or not. + *

+ * If not, a warning will be printed to the console. * @param listener - listener to check. - * @throws IllegalStateException If the given listener's whitelist cannot be fulfilled. */ public void checkListener(PacketListener listener) { - try { - if (lastSuccessfulHook != null) - lastSuccessfulHook.checkListener(listener); - } catch (Exception e) { - throw new IllegalStateException("Registering listener " + PacketAdapter.getPluginName(listener) + " failed", e); + if (lastSuccessfulHook != null) { + UnsupportedListener result = lastSuccessfulHook.checkListener(listener); + + // We won't prevent the listener, as it may still have valid packets + if (result != null) { + reporter.reportWarning(this, "Cannot fully register listener for " + + PacketAdapter.getPluginName(listener) + ": " + result.toString()); + + // These are illegal + for (int packetID : result.getPackets()) + removePacketHandler(packetID); + } } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index 31b2eeda..5508d235 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -489,10 +489,12 @@ abstract class PlayerInjector { /** * Invoked before a new listener is registered. *

- * The player injector should throw an exception if this listener cannot be properly supplied with packet events. + * The player injector should only return a non-null value if some or all of the packet IDs are unsupported. + * * @param listener - the listener that is about to be registered. + * @return A error message with the unsupported packet IDs, or NULL if this listener is valid. */ - public abstract void checkListener(PacketListener listener); + public abstract UnsupportedListener checkListener(PacketListener listener); /** * Allows a packet to be sent by the listeners. @@ -589,6 +591,14 @@ abstract class PlayerInjector { return player; } + /** + * Object that can invoke the packet events. + * @return Packet event invoker. + */ + public ListenerInvoker getInvoker() { + return invoker; + } + /** * Retrieve the hooked player object OR the more up-to-date player instance. * @return The hooked player, or a more up-to-date instance. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java new file mode 100644 index 00000000..c00f1088 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java @@ -0,0 +1,47 @@ +package com.comphenix.protocol.injector.player; + +import java.util.Arrays; + +import com.google.common.base.Joiner; + +/** + * Represents an error message from a player injector. + * + * @author Kristian + */ +public class UnsupportedListener { + private String message; + private int[] packets; + + /** + * Create a new error message. + * @param message - the message. + * @param packets - unsupported packets. + */ + public UnsupportedListener(String message, int[] packets) { + super(); + this.message = message; + this.packets = packets; + } + + /** + * Retrieve the error message. + * @return Error message. + */ + public String getMessage() { + return message; + } + + /** + * Retrieve all unsupported packets. + * @return Unsupported packets. + */ + public int[] getPackets() { + return packets; + } + + @Override + public String toString() { + return String.format("%s (%s)", message, Joiner.on(", ").join(Arrays.asList(packets))); + } +} From f9c0f212c10a4cbdf274af42f412ac0bc5eb0e73 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 16:57:07 +0100 Subject: [PATCH 24/54] Put UnsupportedListener at the package level. --- .../com/comphenix/protocol/async/AsyncListenerHandler.java | 5 ----- .../protocol/injector/player/InjectedArrayList.java | 1 - .../protocol/injector/player/UnsupportedListener.java | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java index 96d04db3..cbd99394 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -22,7 +22,6 @@ import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; import org.bukkit.plugin.Plugin; @@ -113,10 +112,6 @@ public class AsyncListenerHandler { PacketListener getNullPacketListener() { return nullPacketListener; } - - private String getPluginName() { - return PacketAdapter.getPluginName(listener); - } /** * Retrieve the plugin associated with this async listener. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java index f75b0718..aa50212f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedArrayList.java @@ -22,7 +22,6 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Set; -import com.comphenix.protocol.Packets; import com.comphenix.protocol.injector.ListenerInvoker; import com.comphenix.protocol.injector.player.NetworkFieldInjector.FakePacket; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java index c00f1088..2e4cbe5a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/UnsupportedListener.java @@ -9,7 +9,7 @@ import com.google.common.base.Joiner; * * @author Kristian */ -public class UnsupportedListener { +class UnsupportedListener { private String message; private int[] packets; From 0dc2bfef0ca81eaad87fde03d0ecd955f3636fea Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 17:20:04 +0100 Subject: [PATCH 25/54] Handle exceptions in injected code. This is very important, otherwise the server may crash unnecessarily. --- .../injector/PacketFilterManager.java | 5 +- .../protocol/injector/PacketInjector.java | 9 ++- .../protocol/injector/ReadPacketModifier.java | 50 +++++++------ .../player/InjectedServerConnection.java | 7 +- .../injector/player/NetLoginInjector.java | 48 +++++++------ .../injector/player/PlayerInjector.java | 71 ++++++++++--------- 6 files changed, 109 insertions(+), 81 deletions(-) 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 4284b566..e84998eb 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -49,7 +49,6 @@ import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.async.AsyncMarker; -import com.comphenix.protocol.error.DetailedErrorReporter; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.*; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; @@ -142,7 +141,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * Only create instances of this class if protocol lib is disabled. * @param unhookTask */ - public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, DetailedErrorReporter reporter) { + public PacketFilterManager(ClassLoader classLoader, Server server, DelayedSingleTask unhookTask, ErrorReporter reporter) { if (reporter == null) throw new IllegalArgumentException("reporter cannot be NULL."); if (classLoader == null) @@ -175,7 +174,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok try { // Initialize injection mangers this.playerInjection = new PlayerInjectionHandler(classLoader, reporter, isInjectionNecessary, this, packetListeners, server); - this.packetInjector = new PacketInjector(classLoader, this, playerInjection); + this.packetInjector = new PacketInjector(classLoader, this, playerInjection, reporter); this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this); // Attempt to load the list of server and client packets diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java index 625093da..951e4d79 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketInjector.java @@ -31,6 +31,7 @@ import net.minecraft.server.Packet; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Enhancer; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; @@ -51,6 +52,9 @@ class PacketInjector { // The packet filter manager private ListenerInvoker manager; + // Error reporter + private ErrorReporter reporter; + // Allows us to determine the sender private PlayerInjectionHandler playerInjection; @@ -61,11 +65,12 @@ class PacketInjector { private ClassLoader classLoader; public PacketInjector(ClassLoader classLoader, ListenerInvoker manager, - PlayerInjectionHandler playerInjection) throws IllegalAccessException { + PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws IllegalAccessException { this.classLoader = classLoader; this.manager = manager; this.playerInjection = playerInjection; + this.reporter = reporter; this.readModifier = new ConcurrentHashMap(); initialize(); } @@ -133,7 +138,7 @@ class PacketInjector { Class proxy = ex.createClass(); // Create the proxy handler - ReadPacketModifier modifier = new ReadPacketModifier(packetID, this); + ReadPacketModifier modifier = new ReadPacketModifier(packetID, this, reporter); readModifier.put(packetID, modifier); // Add a static reference diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java index 67b3fae5..6e1967e1 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ReadPacketModifier.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.WeakHashMap; import com.comphenix.protocol.Packets; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; @@ -44,12 +45,16 @@ class ReadPacketModifier implements MethodInterceptor { private PacketInjector packetInjector; private int packetID; + // Report errors + private ErrorReporter reporter; + // Whether or not a packet has been cancelled private static Map override = Collections.synchronizedMap(new WeakHashMap()); - public ReadPacketModifier(int packetID, PacketInjector packetInjector) { + public ReadPacketModifier(int packetID, PacketInjector packetInjector, ErrorReporter reporter) { this.packetID = packetID; this.packetInjector = packetInjector; + this.reporter = reporter; } /** @@ -102,27 +107,32 @@ class ReadPacketModifier implements MethodInterceptor { if (returnValue == null && Arrays.equals(method.getParameterTypes(), parameters)) { - // We need this in order to get the correct player - DataInputStream input = (DataInputStream) args[0]; - - // Let the people know - PacketContainer container = new PacketContainer(packetID, (Packet) thisObj); - PacketEvent event = packetInjector.packetRecieved(container, input); - - // Handle override - if (event != null) { - Packet result = event.getPacket().getHandle(); + try { + // We need this in order to get the correct player + DataInputStream input = (DataInputStream) args[0]; + + // Let the people know + PacketContainer container = new PacketContainer(packetID, (Packet) thisObj); + PacketEvent event = packetInjector.packetRecieved(container, input); - if (event.isCancelled()) { - override.put(thisObj, CANCEL_MARKER); - } else if (!objectEquals(thisObj, result)) { - override.put(thisObj, result); - } - - // Update DataInputStream next time - if (!event.isCancelled() && packetID == Packets.Server.KEY_RESPONSE) { - packetInjector.scheduleDataInputRefresh(event.getPlayer()); + // Handle override + if (event != null) { + Packet result = event.getPacket().getHandle(); + + if (event.isCancelled()) { + override.put(thisObj, CANCEL_MARKER); + } else if (!objectEquals(thisObj, result)) { + override.put(thisObj, result); + } + + // Update DataInputStream next time + if (!event.isCancelled() && packetID == Packets.Server.KEY_RESPONSE) { + packetInjector.scheduleDataInputRefresh(event.getPlayer()); + } } + } catch (Throwable e) { + // Minecraft cannot handle this error + reporter.reportDetailed(this, "Cannot handle clienet packet.", e, args[0]); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java index e7b20d58..c9ae47ff 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/InjectedServerConnection.java @@ -217,7 +217,12 @@ class InjectedServerConnection { // Is this a normal Minecraft object? if (!(inserting instanceof Factory)) { // If so, copy the content of the old element to the new - ObjectCloner.copyTo(inserting, replacement, inserting.getClass()); + try { + ObjectCloner.copyTo(inserting, replacement, inserting.getClass()); + } catch (Throwable e) { + reporter.reportDetailed(InjectedServerConnection.this, "Cannot copy old " + inserting + + " to new.", e, inserting, replacement); + } } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java index b5fc924b..ac1c16d6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/NetLoginInjector.java @@ -95,30 +95,34 @@ class NetLoginInjector { PlayerInjector injected = injectedLogins.get(removing); if (injected != null) { - PlayerInjector newInjector = null; - Player player = injected.getPlayer(); - - // Clean up list - injectedLogins.remove(removing); - - // No need to clean up twice - if (injected.isClean()) - return; - - // Hack to clean up other references - newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager()); - - // Update NetworkManager - if (newInjector == null) { - injectionHandler.uninjectPlayer(player); - } else { - injectionHandler.uninjectPlayer(player, false); + try { + PlayerInjector newInjector = null; + Player player = injected.getPlayer(); - if (injected instanceof NetworkObjectInjector) - newInjector.setNetworkManager(injected.getNetworkManager(), true); + // Clean up list + injectedLogins.remove(removing); + + // No need to clean up twice + if (injected.isClean()) + return; + + // Hack to clean up other references + newInjector = injectionHandler.getInjectorByNetworkHandler(injected.getNetworkManager()); + + // Update NetworkManager + if (newInjector == null) { + injectionHandler.uninjectPlayer(player); + } else { + injectionHandler.uninjectPlayer(player, false); + + if (injected instanceof NetworkObjectInjector) + newInjector.setNetworkManager(injected.getNetworkManager(), true); + } + + } catch (Throwable e) { + // Don't leak this to Minecraft + reporter.reportDetailed(this, "Cannot cleanup NetLoginHandler.", e, removing); } - - //logger.warning("Using alternative cleanup method."); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java index 5508d235..6eda9848 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjector.java @@ -502,43 +502,48 @@ abstract class PlayerInjector { * @return The given packet, or the packet replaced by the listeners. */ public Packet handlePacketSending(Packet packet) { - // Get the packet ID too - Integer id = invoker.getPacketID(packet); - Player currentPlayer = player; - - // Hack #1: Handle a single scheduled action - if (scheduledAction != null) { - scheduledAction.run(); - scheduledAction = null; - } - // Hack #2 - if (updateOnLogin) { - if (id == Packets.Server.LOGIN) { - try { - updatedPlayer = getEntityPlayer(getNetHandler()).getBukkitEntity(); - } catch (IllegalAccessException e) { - reporter.reportDetailed(this, "Cannot update player in PlayerEvent.", e, packet); + try { + // Get the packet ID too + Integer id = invoker.getPacketID(packet); + Player currentPlayer = player; + + // Hack #1: Handle a single scheduled action + if (scheduledAction != null) { + scheduledAction.run(); + scheduledAction = null; + } + // Hack #2 + if (updateOnLogin) { + if (id == Packets.Server.LOGIN) { + try { + updatedPlayer = getEntityPlayer(getNetHandler()).getBukkitEntity(); + } catch (IllegalAccessException e) { + reporter.reportDetailed(this, "Cannot update player in PlayerEvent.", e, packet); + } } + + // This will only occur in the NetLoginHandler injection + if (updatedPlayer != null) + currentPlayer = updatedPlayer; } - // This will only occur in the NetLoginHandler injection - if (updatedPlayer != null) - currentPlayer = updatedPlayer; - } - - // Make sure we're listening - if (id != null && hasListener(id)) { - // A packet has been sent guys! - PacketContainer container = new PacketContainer(id, packet); - PacketEvent event = PacketEvent.fromServer(invoker, container, currentPlayer); - invoker.invokePacketSending(event); + // Make sure we're listening + if (id != null && hasListener(id)) { + // A packet has been sent guys! + PacketContainer container = new PacketContainer(id, packet); + PacketEvent event = PacketEvent.fromServer(invoker, container, currentPlayer); + invoker.invokePacketSending(event); + + // Cancelling is pretty simple. Just ignore the packet. + if (event.isCancelled()) + return null; + + // Right, remember to replace the packet again + return event.getPacket().getHandle(); + } - // Cancelling is pretty simple. Just ignore the packet. - if (event.isCancelled()) - return null; - - // Right, remember to replace the packet again - return event.getPacket().getHandle(); + } catch (Throwable e) { + reporter.reportDetailed(this, "Cannot handle server packet.", e, packet); } return packet; From d54cc35445cde195860a445d6f8ab5f4c6be166e Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 22:05:55 +0100 Subject: [PATCH 26/54] Updated to 1.5.1-SNAPSHOT for development of the next version. --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/java/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index a841b2a9..6f85cf36 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.5.0 + 1.5.1-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/java/plugin.yml b/ProtocolLib/src/main/java/plugin.yml index c742800b..85d3318c 100644 --- a/ProtocolLib/src/main/java/plugin.yml +++ b/ProtocolLib/src/main/java/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 1.5.0 +version: 1.5.1-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From ae08abe821f6c6ce0a5c5d8f741925ce35ec4ca3 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Mon, 29 Oct 2012 22:15:25 +0100 Subject: [PATCH 27/54] Remove PrimitiveUtils - it's already present in Bukkit's Guava library. --- ProtocolLib/dependency-reduced-pom.xml | 2 +- .../protocol/CleanupStaticMembers.java | 4 +- .../protocol/injector/PacketConstructor.java | 6 +- .../protocol/reflect/PrimitiveUtils.java | 124 ------------------ .../protocol/reflect/StructureModifier.java | 2 +- .../reflect/compiler/StructureCompiler.java | 6 +- .../reflect/instances/PrimitiveGenerator.java | 8 +- 7 files changed, 13 insertions(+), 139 deletions(-) delete mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrimitiveUtils.java diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index 5fb86ef9..6b5086a3 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ com.comphenix.protocol ProtocolLib ProtocolLib - 1.5.0 + 1.5.1-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/CleanupStaticMembers.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java index 60bbbcfa..dbc2e819 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CleanupStaticMembers.java @@ -14,7 +14,6 @@ import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.MethodUtils; import com.comphenix.protocol.reflect.ObjectCloner; -import com.comphenix.protocol.reflect.PrimitiveUtils; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; import com.comphenix.protocol.reflect.compiler.StructureCompiler; import com.comphenix.protocol.reflect.instances.CollectionGenerator; @@ -46,8 +45,7 @@ class CleanupStaticMembers { BukkitUnwrapper.class, DefaultInstances.class, CollectionGenerator.class, PrimitiveGenerator.class, FuzzyReflection.class, MethodUtils.class, BackgroundCompiler.class, StructureCompiler.class, - ObjectCloner.class, PrimitiveUtils.class, Packets.Server.class, - Packets.Client.class + ObjectCloner.class, Packets.Server.class, Packets.Client.class }; String[] internalClasses = { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java index f8e0e654..7c2917f7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java @@ -25,9 +25,9 @@ import net.minecraft.server.Packet; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.reflect.PrimitiveUtils; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.google.common.primitives.Primitives; /** * A packet constructor that uses an internal Minecraft. @@ -181,9 +181,9 @@ public class PacketConstructor { Class paramType = params[i]; // The input type is always wrapped - if (PrimitiveUtils.isPrimitive(paramType)) { + if (paramType.isPrimitive()) { // Wrap it - paramType = PrimitiveUtils.wrap(paramType); + paramType = Primitives.wrap(paramType); } // Compare assignability diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrimitiveUtils.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrimitiveUtils.java deleted file mode 100644 index 3597920f..00000000 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrimitiveUtils.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.comphenix.protocol.reflect; - -/* - * Copyright (C) 2008 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.lang.reflect.Type; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * Contains static utility methods pertaining to primitive types and their - * corresponding wrapper types. - * - * @author Kevin Bourrillion - */ -public final class PrimitiveUtils { - private PrimitiveUtils() { - } - - /** A map from primitive types to their corresponding wrapper types. */ - private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPE; - - /** A map from wrapper types to their corresponding primitive types. */ - private static final Map, Class> WRAPPER_TO_PRIMITIVE_TYPE; - - // Sad that we can't use a BiMap. :( - static { - Map, Class> primToWrap = new HashMap, Class>(16); - Map, Class> wrapToPrim = new HashMap, Class>(16); - - add(primToWrap, wrapToPrim, boolean.class, Boolean.class); - add(primToWrap, wrapToPrim, byte.class, Byte.class); - add(primToWrap, wrapToPrim, char.class, Character.class); - add(primToWrap, wrapToPrim, double.class, Double.class); - add(primToWrap, wrapToPrim, float.class, Float.class); - add(primToWrap, wrapToPrim, int.class, Integer.class); - add(primToWrap, wrapToPrim, long.class, Long.class); - add(primToWrap, wrapToPrim, short.class, Short.class); - add(primToWrap, wrapToPrim, void.class, Void.class); - - PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); - WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim); - } - - private static void add(Map, Class> forward, - Map, Class> backward, Class key, Class value) { - forward.put(key, value); - backward.put(value, key); - } - - /** - * Returns true if this type is a primitive. - */ - public static boolean isPrimitive(Type type) { - return PRIMITIVE_TO_WRAPPER_TYPE.containsKey(type); - } - - /** - * Returns {@code true} if {@code type} is one of the nine primitive-wrapper - * types, such as {@link Integer}. - * - * @see Class#isPrimitive - */ - public static boolean isWrapperType(Type type) { - return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(checkNotNull(type)); - } - - /** - * Returns the corresponding wrapper type of {@code type} if it is a - * primitive type; otherwise returns {@code type} itself. Idempotent. - * - *

-	 *     wrap(int.class) == Integer.class
-	 *     wrap(Integer.class) == Integer.class
-	 *     wrap(String.class) == String.class
-	 * 
- */ - public static Class wrap(Class type) { - // cast is safe: long.class and Long.class are both of type Class - @SuppressWarnings("unchecked") - Class wrapped = (Class) PRIMITIVE_TO_WRAPPER_TYPE - .get(checkNotNull(type)); - return (wrapped == null) ? type : wrapped; - } - - /** - * Returns the corresponding primitive type of {@code type} if it is a - * wrapper type; otherwise returns {@code type} itself. Idempotent. - * - *
-	 *     unwrap(Integer.class) == int.class
-	 *     unwrap(int.class) == int.class
-	 *     unwrap(String.class) == String.class
-	 * 
- */ - public static Class unwrap(Class type) { - // cast is safe: long.class and Long.class are both of type Class - @SuppressWarnings("unchecked") - Class unwrapped = (Class) WRAPPER_TO_PRIMITIVE_TYPE - .get(checkNotNull(type)); - return (unwrapped == null) ? type : unwrapped; - } - - public static T checkNotNull(T obj) { - if (obj == null) { - throw new NullPointerException(); - } - return obj; - } -} 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 f4bb07f3..2665dc21 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/StructureModifier.java @@ -426,7 +426,7 @@ public class StructureModifier { Class type = field.getType(); // First, ignore primitive fields - if (!PrimitiveUtils.isPrimitive(type)) { + if (!type.isPrimitive()) { // Next, see if we actually can generate a default value if (generator.getDefault(type) != null) { // If so, require it 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 fde9de08..10ceaa72 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 @@ -25,9 +25,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import com.comphenix.protocol.reflect.PrimitiveUtils; import com.comphenix.protocol.reflect.StructureModifier; import com.google.common.base.Objects; +import com.google.common.primitives.Primitives; import net.sf.cglib.asm.*; @@ -306,7 +306,7 @@ public final class StructureCompiler { for (int i = 0; i < fields.size(); i++) { Class outputType = fields.get(i).getType(); - Class inputType = PrimitiveUtils.wrap(outputType); + Class inputType = Primitives.wrap(outputType); String typeDescriptor = Type.getDescriptor(outputType); String inputPath = inputType.getName().replace('.', '/'); @@ -323,7 +323,7 @@ public final class StructureCompiler { mv.visitVarInsn(Opcodes.ALOAD, 3); mv.visitVarInsn(Opcodes.ALOAD, 2); - if (!PrimitiveUtils.isPrimitive(outputType)) + if (!outputType.isPrimitive()) mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath); else boxingHelper.unbox(Type.getType(outputType)); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/PrimitiveGenerator.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/PrimitiveGenerator.java index 526846a0..5563acc6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/PrimitiveGenerator.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/instances/PrimitiveGenerator.java @@ -21,8 +21,8 @@ import java.lang.reflect.Array; import javax.annotation.Nullable; -import com.comphenix.protocol.reflect.PrimitiveUtils; import com.google.common.base.Defaults; +import com.google.common.primitives.Primitives; /** * Provides constructors for primtive types, wrappers, arrays and strings. @@ -58,10 +58,10 @@ public class PrimitiveGenerator implements InstanceProvider { @Override public Object create(@Nullable Class type) { - if (PrimitiveUtils.isPrimitive(type)) { + if (type.isPrimitive()) { return Defaults.defaultValue(type); - } else if (PrimitiveUtils.isWrapperType(type)) { - return Defaults.defaultValue(PrimitiveUtils.unwrap(type)); + } else if (Primitives.isWrapperType(type)) { + return Defaults.defaultValue(Primitives.unwrap(type)); } else if (type.isArray()) { Class arrayType = type.getComponentType(); return Array.newInstance(arrayType, 0); From aa9616d6b0015dbc60287aa470cad20c52c4d28c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 31 Oct 2012 00:52:02 +0100 Subject: [PATCH 28/54] Don't check for closed sockets. If it talks like a duck, ect. --- .../protocol/injector/player/PlayerInjectionHandler.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java index 2b2b86a6..b627810f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/PlayerInjectionHandler.java @@ -289,11 +289,6 @@ public class PlayerInjectionHandler { Socket socket = injector.getSocket(); SocketAddress address = socket != null ? socket.getRemoteSocketAddress() : null; - // Make sure the current player is not logged out - if (socket != null && socket.isClosed()) { - throw new PlayerLoggedOutException(); - } - // Guard against NPE here too PlayerInjector previous = address != null ? addressLookup.get(address) : null; From 51184dd209907952dbee0ecefaa7969a20d313f9 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 2 Nov 2012 00:35:35 +0100 Subject: [PATCH 29/54] Ensure getEntityModifier is thread safe. FIXES Ticket-6. Don't use the world's getPlayers-method, as it's enumerating a collection that's not thread safe. This is important as getEntityModifier may be called by a client packet listener (which is async). --- .../protocol/events/PacketContainer.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 920611b5..5ab389c2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -26,6 +26,8 @@ import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import org.bukkit.Bukkit; +import org.bukkit.Server; import org.bukkit.World; import org.bukkit.WorldType; import org.bukkit.craftbukkit.CraftWorld; @@ -268,8 +270,7 @@ public class PacketContainer implements Serializable { final Object worldServer = ((CraftWorld) world).getHandle(); final Class nmsEntityClass = net.minecraft.server.Entity.class; - final World worldCopy = world; - + if (getEntity == null) getEntity = FuzzyReflection.fromObject(worldServer).getMethodByParameters( "getEntity", nmsEntityClass, new Class[] { int.class }); @@ -296,10 +297,14 @@ public class PacketContainer implements Serializable { if (nmsEntity != null) { return nmsEntity.getBukkitEntity(); } else { - // Maybe it's a player that's just logged in? Try a search - for (Player player : worldCopy.getPlayers()) { - if (player.getEntityId() == id) { - return player; + 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; + } } } From cda235af7eb3685e9ceb429f81408372090f2bad Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 2 Nov 2012 01:03:12 +0100 Subject: [PATCH 30/54] Increment the default async timeout to 2 minutes. --- .../src/main/java/com/comphenix/protocol/async/AsyncMarker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java index 36843939..994cfef0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java @@ -51,7 +51,7 @@ public class AsyncMarker implements Serializable, Comparable { /** * Default number of milliseconds until a packet will rejected. */ - public static final int DEFAULT_TIMEOUT_DELTA = 60000; + public static final int DEFAULT_TIMEOUT_DELTA = 120000; /** * Default number of packets to skip. From 5ac15f11dcb3d8b82c2dfd5c19fbb641ca320747 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 2 Nov 2012 01:18:38 +0100 Subject: [PATCH 31/54] Drop expired packets in the processing chain. --- .../protocol/async/AsyncListenerHandler.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java index cbd99394..0b1ef3ba 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncListenerHandler.java @@ -441,12 +441,14 @@ public class AsyncListenerHandler { } // Now, get the next non-cancelled listener - for (; marker.getListenerTraversal().hasNext(); ) { - AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener(); - - if (!handler.isCancelled()) { - handler.enqueuePacket(packet); - continue mainLoop; + if (!marker.hasExpired()) { + for (; marker.getListenerTraversal().hasNext(); ) { + AsyncListenerHandler handler = marker.getListenerTraversal().next().getListener(); + + if (!handler.isCancelled()) { + handler.enqueuePacket(packet); + continue mainLoop; + } } } From 2c00b570aa91190c5dbc552ec7fb4b0848418258 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 2 Nov 2012 16:58:56 +0100 Subject: [PATCH 32/54] Increase default packet timeout to 30 minutes. --- .../src/main/java/com/comphenix/protocol/async/AsyncMarker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java index 994cfef0..989545fc 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java @@ -51,7 +51,7 @@ public class AsyncMarker implements Serializable, Comparable { /** * Default number of milliseconds until a packet will rejected. */ - public static final int DEFAULT_TIMEOUT_DELTA = 120000; + public static final int DEFAULT_TIMEOUT_DELTA = 1800 * 1000; /** * Default number of packets to skip. From 18ec9bc973a7f4900299a997ae260918a866edfd Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 2 Nov 2012 17:22:42 +0100 Subject: [PATCH 33/54] Adding h31ix's auto update system. --- .../comphenix/protocol/metrics/Updater.java | 626 ++++++++++++++++++ 1 file changed, 626 insertions(+) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java new file mode 100644 index 00000000..bd5c717d --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java @@ -0,0 +1,626 @@ +package com.comphenix.protocol.metrics; + +/* + * Updater for Bukkit. + * + * This class provides the means to safetly and easily update a plugin, or check to see if it is updated using dev.bukkit.org + */ +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.XMLEvent; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; + +/** + * Check dev.bukkit.org to find updates for a given plugin, and download the updates if needed. + *

+ * VERY, VERY IMPORTANT: Because there are no standards for adding auto-update toggles in your plugin's config, this system provides NO CHECK WITH YOUR CONFIG to make sure the user has allowed auto-updating. + *
+ * It is a BUKKIT POLICY that you include a boolean value in your config that prevents the auto-updater from running AT ALL. + *
+ * If you fail to include this option in your config, your plugin will be REJECTED when you attempt to submit it to dev.bukkit.org. + *

+ * An example of a good configuration option would be something similar to 'auto-update: true' - if this value is set to false you may NOT run the auto-updater. + *
+ * If you are unsure about these rules, please read the plugin submission guidelines: http://goo.gl/8iU5l + * + * @author H31IX + */ +public class Updater +{ + private Plugin plugin; + private UpdateType type; + private String versionTitle; + private String versionLink; + private long totalSize; // Holds the total size of the file + //private double downloadedSize; TODO: Holds the number of bytes downloaded + private int sizeLine; // Used for detecting file size + private int multiplier; // Used for determining when to broadcast download updates + private boolean announce; // Whether to announce file downloads + private URL url; // Connecting to RSS + private static final String DBOUrl = "http://dev.bukkit.org/server-mods/"; // Slugs will be appended to this to get to the project's RSS feed + private String [] noUpdateTag = {"-DEV","-PRE"}; // If the version number contains one of these, don't update. + private static final int BYTE_SIZE = 1024; // Used for downloading files + private String updateFolder = YamlConfiguration.loadConfiguration(new File("bukkit.yml")).getString("settings.update-folder"); // The folder that downloads will be placed in + private Updater.UpdateResult result = Updater.UpdateResult.SUCCESS; // Used for determining the outcome of the update process + + // Strings for reading RSS + private static final String TITLE = "title"; + private static final String LINK = "link"; + private static final String ITEM = "item"; + + /** + * Gives the dev the result of the update process. Can be obtained by called getResult(). + */ + public enum UpdateResult + { + /** + * The updater found an update, and has readied it to be loaded the next time the server restarts/reloads. + */ + SUCCESS(1), + /** + * The updater did not find an update, and nothing was downloaded. + */ + NO_UPDATE(2), + /** + * The updater found an update, but was unable to download it. + */ + FAIL_DOWNLOAD(3), + /** + * For some reason, the updater was unable to contact dev.bukkit.org to download the file. + */ + FAIL_DBO(4), + /** + * When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as 'v1.0'. + */ + FAIL_NOVERSION(5), + /** + * The slug provided by the plugin running the updater was invalid and doesn't exist on DBO. + */ + FAIL_BADSLUG(6), + /** + * The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded. + */ + UPDATE_AVAILABLE(7); + + private static final Map valueList = new HashMap(); + private final int value; + + private UpdateResult(int value) + { + this.value = value; + } + + public int getValue() + { + return this.value; + } + + public static Updater.UpdateResult getResult(int value) + { + return valueList.get(value); + } + + static + { + for(Updater.UpdateResult result : Updater.UpdateResult.values()) + { + valueList.put(result.value, result); + } + } + } + + /** + * Allows the dev to specify the type of update that will be run. + */ + public enum UpdateType + { + /** + * Run a version check, and then if the file is out of date, download the newest version. + */ + DEFAULT(1), + /** + * Don't run a version check, just find the latest update and download it. + */ + NO_VERSION_CHECK(2), + /** + * Get information about the version and the download size, but don't actually download anything. + */ + NO_DOWNLOAD(3); + + private static final Map valueList = new HashMap(); + private final int value; + + private UpdateType(int value) + { + this.value = value; + } + + public int getValue() + { + return this.value; + } + + public static Updater.UpdateType getResult(int value) + { + return valueList.get(value); + } + + static + { + for(Updater.UpdateType result : Updater.UpdateType.values()) + { + valueList.put(result.value, result); + } + } + } + + /** + * Initialize the updater + * + * @param plugin + * The plugin that is checking for an update. + * @param slug + * The dev.bukkit.org slug of the project (http://dev.bukkit.org/server-mods/SLUG_IS_HERE) + * @param file + * The file that the plugin is running from, get this by doing this.getFile() from within your main class. + * @param type + * Specify the type of update this will be. See {@link UpdateType} + * @param announce + * True if the program should announce the progress of new updates in console + */ + public Updater(Plugin plugin, String slug, File file, UpdateType type, boolean announce) + { + this.plugin = plugin; + this.type = type; + this.announce = announce; + try + { + // Obtain the results of the project's file feed + url = new URL(DBOUrl + slug + "/files.rss"); + } + catch (MalformedURLException ex) + { + // The slug doesn't exist + plugin.getLogger().warning("The author of this plugin has misconfigured their Auto Update system"); + plugin.getLogger().warning("The project slug added ('" + slug + "') is invalid, and does not exist on dev.bukkit.org"); + result = Updater.UpdateResult.FAIL_BADSLUG; // Bad slug! Bad! + } + if(url != null) + { + // Obtain the results of the project's file feed + readFeed(); + if(versionCheck(versionTitle)) + { + String fileLink = getFile(versionLink); + if(fileLink != null && type != UpdateType.NO_DOWNLOAD) + { + String name = file.getName(); + // If it's a zip file, it shouldn't be downloaded as the plugin's name + if(fileLink.endsWith(".zip")) + { + String [] split = fileLink.split("/"); + name = split[split.length-1]; + } + saveFile(new File("plugins/" + updateFolder), name, fileLink); + } + else + { + result = UpdateResult.UPDATE_AVAILABLE; + } + } + } + } + + /** + * Get the result of the update process. + */ + public Updater.UpdateResult getResult() + { + return result; + } + + /** + * Get the total bytes of the file (can only be used after running a version check or a normal run). + */ + public long getFileSize() + { + return totalSize; + } + + /** + * Get the version string latest file avaliable online. + */ + public String getLatestVersionString() + { + return versionTitle; + } + + /** + * Save an update from dev.bukkit.org into the server's update folder. + */ + private void saveFile(File folder, String file, String u) + { + if(!folder.exists()) + { + folder.mkdir(); + } + BufferedInputStream in = null; + FileOutputStream fout = null; + try + { + // Download the file + URL url = new URL(u); + int fileLength = url.openConnection().getContentLength(); + in = new BufferedInputStream(url.openStream()); + fout = new FileOutputStream(folder.getAbsolutePath() + "/" + file); + + byte[] data = new byte[BYTE_SIZE]; + int count; + if(announce) plugin.getLogger().info("About to download a new update: " + versionTitle); + long downloaded = 0; + while ((count = in.read(data, 0, BYTE_SIZE)) != -1) + { + downloaded += count; + fout.write(data, 0, count); + int percent = (int) (downloaded * 100 / fileLength); + if(announce & (percent % 10 == 0)) + { + plugin.getLogger().info("Downloading update: " + percent + "% of " + fileLength + " bytes."); + } + } + //Just a quick check to make sure we didn't leave any files from last time... + for(File xFile : new File("plugins/" + updateFolder).listFiles()) + { + if(xFile.getName().endsWith(".zip")) + { + xFile.delete(); + } + } + // Check to see if it's a zip file, if it is, unzip it. + File dFile = new File(folder.getAbsolutePath() + "/" + file); + if(dFile.getName().endsWith(".zip")) + { + // Unzip + unzip(dFile.getCanonicalPath()); + } + if(announce) plugin.getLogger().info("Finished updating."); + } + catch (Exception ex) + { + plugin.getLogger().warning("The auto-updater tried to download a new update, but was unsuccessful."); + result = Updater.UpdateResult.FAIL_DOWNLOAD; + } + finally + { + try + { + if (in != null) + { + in.close(); + } + if (fout != null) + { + fout.close(); + } + } + catch (Exception ex) + { + } + } + } + + /** + * Part of Zip-File-Extractor, modified by H31IX for use with Bukkit + */ + private void unzip(String file) + { + try + { + File fSourceZip = new File(file); + String zipPath = file.substring(0, file.length()-4); + ZipFile zipFile = new ZipFile(fSourceZip); + Enumeration e = zipFile.entries(); + while(e.hasMoreElements()) + { + ZipEntry entry = (ZipEntry)e.nextElement(); + File destinationFilePath = new File(zipPath,entry.getName()); + destinationFilePath.getParentFile().mkdirs(); + if(entry.isDirectory()) + { + continue; + } + else + { + BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(entry)); + int b; + byte buffer[] = new byte[BYTE_SIZE]; + FileOutputStream fos = new FileOutputStream(destinationFilePath); + BufferedOutputStream bos = new BufferedOutputStream(fos, BYTE_SIZE); + while((b = bis.read(buffer, 0, BYTE_SIZE)) != -1) + { + bos.write(buffer, 0, b); + } + bos.flush(); + bos.close(); + bis.close(); + String name = destinationFilePath.getName(); + if(name.endsWith(".jar") && pluginFile(name)) + { + destinationFilePath.renameTo(new File("plugins/" + updateFolder + "/" + name)); + } + } + entry = null; + destinationFilePath = null; + } + e = null; + zipFile.close(); + zipFile = null; + // Move any plugin data folders that were included to the right place, Bukkit won't do this for us. + for(File dFile : new File(zipPath).listFiles()) + { + if(dFile.isDirectory()) + { + if(pluginFile(dFile.getName())) + { + File oFile = new File("plugins/" + dFile.getName()); // Get current dir + File [] contents = oFile.listFiles(); // List of existing files in the current dir + for(File cFile : dFile.listFiles()) // Loop through all the files in the new dir + { + boolean found = false; + for(File xFile : contents) // Loop through contents to see if it exists + { + if(xFile.getName().equals(cFile.getName())) + { + found = true; + break; + } + } + if(!found) + { + // Move the new file into the current dir + cFile.renameTo(new File(oFile.getCanonicalFile() + "/" + cFile.getName())); + } + else + { + // This file already exists, so we don't need it anymore. + cFile.delete(); + } + } + } + } + dFile.delete(); + } + new File(zipPath).delete(); + fSourceZip.delete(); + } + catch(IOException ex) + { + ex.printStackTrace(); + plugin.getLogger().warning("The auto-updater tried to unzip a new update file, but was unsuccessful."); + result = Updater.UpdateResult.FAIL_DOWNLOAD; + } + new File(file).delete(); + } + + /** + * Check if the name of a jar is one of the plugins currently installed, used for extracting the correct files out of a zip. + */ + public boolean pluginFile(String name) + { + for(File file : new File("plugins").listFiles()) + { + if(file.getName().equals(name)) + { + return true; + } + } + return false; + } + + /** + * Obtain the direct download file url from the file's page. + */ + private String getFile(String link) + { + String download = null; + try + { + // Open a connection to the page + URL url = new URL(link); + URLConnection urlConn = url.openConnection(); + InputStreamReader inStream = new InputStreamReader(urlConn.getInputStream()); + BufferedReader buff = new BufferedReader(inStream); + + int counter = 0; + String line; + while((line = buff.readLine()) != null) + { + counter++; + // Search for the download link + if(line.contains("

  • ")) + { + // Get the raw link + download = line.split("Download")[0]; + } + // Search for size + else if (line.contains("
    Size
    ")) + { + sizeLine = counter+1; + } + else if(counter == sizeLine) + { + String size = line.replaceAll("
    ", "").replaceAll("
    ", ""); + multiplier = size.contains("MiB") ? 1048576 : 1024; + size = size.replace(" KiB", "").replace(" MiB", ""); + totalSize = (long)(Double.parseDouble(size)*multiplier); + } + } + urlConn = null; + inStream = null; + buff.close(); + buff = null; + } + catch (Exception ex) + { + ex.printStackTrace(); + plugin.getLogger().warning("The auto-updater tried to contact dev.bukkit.org, but was unsuccessful."); + result = Updater.UpdateResult.FAIL_DBO; + return null; + } + return download; + } + + /** + * Check to see if the program should continue by evaluation whether the plugin is already updated, or shouldn't be updated + */ + private boolean versionCheck(String title) + { + if(type != UpdateType.NO_VERSION_CHECK) + { + String version = plugin.getDescription().getVersion(); + if(title.split("v").length == 2) + { + String remoteVersion = title.split("v")[1].split(" ")[0]; // Get the newest file's version number + int remVer = -1,curVer=0; + try + { + remVer = calVer(remoteVersion); + curVer = calVer(version); + } + catch(NumberFormatException nfe) + { + remVer=-1; + } + if(hasTag(version)||version.equalsIgnoreCase(remoteVersion)||curVer>=remVer) + { + // We already have the latest version, or this build is tagged for no-update + result = Updater.UpdateResult.NO_UPDATE; + return false; + } + } + else + { + // The file's name did not contain the string 'vVersion' + plugin.getLogger().warning("The author of this plugin has misconfigured their Auto Update system"); + plugin.getLogger().warning("Files uploaded to BukkitDev should contain the version number, seperated from the name by a 'v', such as PluginName v1.0"); + plugin.getLogger().warning("Please notify the author (" + plugin.getDescription().getAuthors().get(0) + ") of this error."); + result = Updater.UpdateResult.FAIL_NOVERSION; + return false; + } + } + return true; + } + /** + * Used to calculate the version string as an Integer + */ + private Integer calVer(String s) throws NumberFormatException + { + if(s.contains(".")) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i Date: Fri, 2 Nov 2012 17:35:41 +0100 Subject: [PATCH 34/54] Add a couple of permissions and commands. --- ProtocolLib/src/main/java/plugin.yml | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/plugin.yml b/ProtocolLib/src/main/java/plugin.yml index 85d3318c..2bc5f45d 100644 --- a/ProtocolLib/src/main/java/plugin.yml +++ b/ProtocolLib/src/main/java/plugin.yml @@ -5,4 +5,29 @@ author: Comphenix website: http://www.comphenix.net/ProtocolLib main: com.comphenix.protocol.ProtocolLibrary -database: false \ No newline at end of file +database: false + +commands: + protocol: + description: Performs administrative tasks regarding ProtocolLib. + usage: / [reload|update] + permission: experiencemod.admin + permission-message: You don't have + packet: + description: Adds or removes a simple packet listener. + usage: / [add|remove|clear] [ID start] [ID stop] + permission: experiencemod.admin + permission-message: You don't have + +permissions: + protocol.*: + description: Gives access to everything. + children: + protocol.admin: true + protocol.info: true + protocol.admin: + description: Able to initiate the update process, and can configure debug mode. + default: op + protocol.info: + description: Can read update notifications, debug messages and error reports. + default: op \ No newline at end of file From 4328072f4944cf83a16e3f2111942963b2e9fe83 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 2 Nov 2012 17:47:33 +0100 Subject: [PATCH 35/54] Created a default maven resources folder. --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/{java => resources}/plugin.yml | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename ProtocolLib/src/main/{java => resources}/plugin.yml (100%) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 6f85cf36..ea188dd7 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -36,7 +36,7 @@ src/test/java - src/main/java + src/main/resources **/*.java diff --git a/ProtocolLib/src/main/java/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml similarity index 100% rename from ProtocolLib/src/main/java/plugin.yml rename to ProtocolLib/src/main/resources/plugin.yml From 3d0dce7e8d7d30f3a28cf7e77f45a1eae8453041 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 2 Nov 2012 18:55:23 +0100 Subject: [PATCH 36/54] Make the update class easier to work with. --- .../comphenix/protocol/metrics/Updater.java | 123 ++++++++++++++---- 1 file changed, 98 insertions(+), 25 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java index bd5c717d..2108c2cd 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java @@ -12,12 +12,17 @@ import java.net.URLConnection; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.XMLEvent; + +import org.bukkit.Server; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.Plugin; @@ -38,21 +43,33 @@ import org.bukkit.plugin.Plugin; */ public class Updater { + // If the version number contains one of these, don't update. + private static final String[] noUpdateTag = {"-DEV", "-PRE", "-SNAPSHOT"}; + + // Slugs will be appended to this to get to the project's RSS feed + private static final String DBOUrl = "http://dev.bukkit.org/server-mods/"; + private static final int BYTE_SIZE = 1024; // Used for downloading files + private Plugin plugin; private UpdateType type; + private String downloadedVersion; private String versionTitle; private String versionLink; private long totalSize; // Holds the total size of the file - //private double downloadedSize; TODO: Holds the number of bytes downloaded private int sizeLine; // Used for detecting file size private int multiplier; // Used for determining when to broadcast download updates private boolean announce; // Whether to announce file downloads private URL url; // Connecting to RSS - private static final String DBOUrl = "http://dev.bukkit.org/server-mods/"; // Slugs will be appended to this to get to the project's RSS feed - private String [] noUpdateTag = {"-DEV","-PRE"}; // If the version number contains one of these, don't update. - private static final int BYTE_SIZE = 1024; // Used for downloading files + private String updateFolder = YamlConfiguration.loadConfiguration(new File("bukkit.yml")).getString("settings.update-folder"); // The folder that downloads will be placed in - private Updater.UpdateResult result = Updater.UpdateResult.SUCCESS; // Used for determining the outcome of the update process + + // Used for determining the outcome of the update process + private Updater.UpdateResult result = Updater.UpdateResult.SUCCESS; + private String slug; + private File file; + + // Used to announce progress + private Logger logger; // Strings for reading RSS private static final String TITLE = "title"; @@ -174,34 +191,79 @@ public class Updater * The dev.bukkit.org slug of the project (http://dev.bukkit.org/server-mods/SLUG_IS_HERE) * @param file * The file that the plugin is running from, get this by doing this.getFile() from within your main class. + * @param permission + * Permission needed to read the output of the update process. + */ + public Updater(Plugin plugin, String slug, File file, String permission) + { + this.plugin = plugin; + this.file = file; + this.slug = slug; + + // Prevent issues with older versions of Bukkit + try { + logger = plugin.getLogger(); + logger.getLevel(); + } catch (Throwable e) { + logger = Logger.getLogger("Minecraft"); + } + + broadcastUsers(plugin.getServer(), permission); + } + + private void broadcastUsers(final Server server, final String permission) { + // Broadcast information to every user too + logger.addHandler(new Handler() { + @Override + public void publish(LogRecord record) { + server.broadcast(record.getMessage(), permission); + } + + @Override + public void flush() { + // Not needed. + } + + @Override + public void close() throws SecurityException { + // Do nothing. + } + }); + } + + /** + * Update the plugin. + * * @param type * Specify the type of update this will be. See {@link UpdateType} * @param announce * True if the program should announce the progress of new updates in console + * @return The result of the update process. */ - public Updater(Plugin plugin, String slug, File file, UpdateType type, boolean announce) + public UpdateResult update(UpdateType type, boolean announce) { - this.plugin = plugin; - this.type = type; - this.announce = announce; + this.type = type; + this.announce = announce; + try { // Obtain the results of the project's file feed + url = null; url = new URL(DBOUrl + slug + "/files.rss"); } catch (MalformedURLException ex) { // The slug doesn't exist - plugin.getLogger().warning("The author of this plugin has misconfigured their Auto Update system"); - plugin.getLogger().warning("The project slug added ('" + slug + "') is invalid, and does not exist on dev.bukkit.org"); + logger.warning("The author of this plugin has misconfigured their Auto Update system"); + logger.warning("The project slug added ('" + slug + "') is invalid, and does not exist on dev.bukkit.org"); result = Updater.UpdateResult.FAIL_BADSLUG; // Bad slug! Bad! } - if(url != null) + if (url != null) { // Obtain the results of the project's file feed readFeed(); if(versionCheck(versionTitle)) - { + { String fileLink = getFile(versionLink); if(fileLink != null && type != UpdateType.NO_DOWNLOAD) { @@ -212,7 +274,16 @@ public class Updater String [] split = fileLink.split("/"); name = split[split.length-1]; } - saveFile(new File("plugins/" + updateFolder), name, fileLink); + + // Never download the same file twice + if (!downloadedVersion.equalsIgnoreCase(versionLink)) { + saveFile(new File("plugins/" + updateFolder), name, fileLink); + downloadedVersion = versionLink; + result = UpdateResult.SUCCESS; + + } else { + result = UpdateResult.UPDATE_AVAILABLE; + } } else { @@ -220,8 +291,10 @@ public class Updater } } } + + return result; } - + /** * Get the result of the update process. */ @@ -267,7 +340,7 @@ public class Updater byte[] data = new byte[BYTE_SIZE]; int count; - if(announce) plugin.getLogger().info("About to download a new update: " + versionTitle); + if(announce) logger.info("About to download a new update: " + versionTitle); long downloaded = 0; while ((count = in.read(data, 0, BYTE_SIZE)) != -1) { @@ -276,7 +349,7 @@ public class Updater int percent = (int) (downloaded * 100 / fileLength); if(announce & (percent % 10 == 0)) { - plugin.getLogger().info("Downloading update: " + percent + "% of " + fileLength + " bytes."); + logger.info("Downloading update: " + percent + "% of " + fileLength + " bytes."); } } //Just a quick check to make sure we didn't leave any files from last time... @@ -294,11 +367,11 @@ public class Updater // Unzip unzip(dFile.getCanonicalPath()); } - if(announce) plugin.getLogger().info("Finished updating."); + if(announce) logger.info("Finished updating."); } catch (Exception ex) { - plugin.getLogger().warning("The auto-updater tried to download a new update, but was unsuccessful."); + logger.warning("The auto-updater tried to download a new update, but was unsuccessful."); result = Updater.UpdateResult.FAIL_DOWNLOAD; } finally @@ -407,7 +480,7 @@ public class Updater catch(IOException ex) { ex.printStackTrace(); - plugin.getLogger().warning("The auto-updater tried to unzip a new update file, but was unsuccessful."); + logger.warning("The auto-updater tried to unzip a new update file, but was unsuccessful."); result = Updater.UpdateResult.FAIL_DOWNLOAD; } new File(file).delete(); @@ -474,7 +547,7 @@ public class Updater catch (Exception ex) { ex.printStackTrace(); - plugin.getLogger().warning("The auto-updater tried to contact dev.bukkit.org, but was unsuccessful."); + logger.warning("The auto-updater tried to contact dev.bukkit.org, but was unsuccessful."); result = Updater.UpdateResult.FAIL_DBO; return null; } @@ -486,7 +559,7 @@ public class Updater */ private boolean versionCheck(String title) { - if(type != UpdateType.NO_VERSION_CHECK) + if (type != UpdateType.NO_VERSION_CHECK) { String version = plugin.getDescription().getVersion(); if(title.split("v").length == 2) @@ -512,9 +585,9 @@ public class Updater else { // The file's name did not contain the string 'vVersion' - plugin.getLogger().warning("The author of this plugin has misconfigured their Auto Update system"); - plugin.getLogger().warning("Files uploaded to BukkitDev should contain the version number, seperated from the name by a 'v', such as PluginName v1.0"); - plugin.getLogger().warning("Please notify the author (" + plugin.getDescription().getAuthors().get(0) + ") of this error."); + logger.warning("The author of this plugin has misconfigured their Auto Update system"); + logger.warning("Files uploaded to BukkitDev should contain the version number, seperated from the name by a 'v', such as PluginName v1.0"); + logger.warning("Please notify the author (" + plugin.getDescription().getAuthors().get(0) + ") of this error."); result = Updater.UpdateResult.FAIL_NOVERSION; return false; } From 7952da99c9b3f2ece48d1d0508ed1ae698a49c0c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 00:38:57 +0100 Subject: [PATCH 37/54] Added configuration and commands. The two commands added are as follows: protocol: Reload the configuration, check for updates and download the most recent version. packet: Add or remove a simple packet inspector. Can even display the content of each packet. --- ProtocolLib/.classpath | 7 +- .../org.eclipse.core.resources.prefs | 1 + ProtocolLib/dependency-reduced-pom.xml | 2 +- .../com/comphenix/protocol/CommandPacket.java | 325 ++++++++++++++++ .../comphenix/protocol/CommandProtocol.java | 84 ++++ .../comphenix/protocol/ProtocolConfig.java | 135 +++++++ .../comphenix/protocol/ProtocolLibrary.java | 133 ++++--- .../concurrency/AbstractIntervalTree.java | 362 ++++++++++++++++++ .../protocol/error/DetailedErrorReporter.java | 13 +- .../comphenix/protocol/metrics/Updater.java | 66 ++-- ProtocolLib/src/main/resources/config.yml | 10 + ProtocolLib/src/main/resources/plugin.yml | 6 +- 12 files changed, 1054 insertions(+), 90 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java create mode 100644 ProtocolLib/src/main/resources/config.yml diff --git a/ProtocolLib/.classpath b/ProtocolLib/.classpath index 3e0a6999..e842aee4 100644 --- a/ProtocolLib/.classpath +++ b/ProtocolLib/.classpath @@ -1,6 +1,6 @@ - + @@ -13,6 +13,11 @@ + + + + + diff --git a/ProtocolLib/.settings/org.eclipse.core.resources.prefs b/ProtocolLib/.settings/org.eclipse.core.resources.prefs index 6e796e59..78478a74 100644 --- a/ProtocolLib/.settings/org.eclipse.core.resources.prefs +++ b/ProtocolLib/.settings/org.eclipse.core.resources.prefs @@ -1,4 +1,5 @@ eclipse.preferences.version=1 encoding//src/main/java=cp1252 +encoding//src/main/resources=cp1252 encoding//src/test/java=cp1252 encoding/=cp1252 diff --git a/ProtocolLib/dependency-reduced-pom.xml b/ProtocolLib/dependency-reduced-pom.xml index 6b5086a3..59e0de0c 100644 --- a/ProtocolLib/dependency-reduced-pom.xml +++ b/ProtocolLib/dependency-reduced-pom.xml @@ -38,7 +38,7 @@ clean install - src/main/java + src/main/resources **/*.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java new file mode 100644 index 00000000..510842a2 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -0,0 +1,325 @@ +package com.comphenix.protocol; + +import java.util.Set; +import java.util.logging.Logger; + +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.concurrency.AbstractIntervalTree; +import com.comphenix.protocol.events.ConnectionSide; +import com.comphenix.protocol.events.ListenerPriority; +import com.comphenix.protocol.events.ListeningWhitelist; +import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.GamePhase; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.google.common.collect.DiscreteDomains; +import com.google.common.collect.Range; +import com.google.common.collect.Ranges; + +/** + * Handles the "packet" debug command. + * + * @author Kristian + */ +class CommandPacket implements CommandExecutor { + private interface DetailedPacketListener extends PacketListener { + /** + * Determine whether or not the given packet listener is detailed or not. + * @return TRUE if it is detailed, FALSE otherwise. + */ + public boolean isDetailed(); + } + + private enum SubCommand { + ADD, REMOVE; + } + + /** + * Name of this command. + */ + public static final String NAME = "packet"; + + private Plugin plugin; + private Logger logger; + private ProtocolManager manager; + + // Registered packet listeners + private AbstractIntervalTree clientListeners = createTree(ConnectionSide.CLIENT_SIDE); + private AbstractIntervalTree serverListeners = createTree(ConnectionSide.SERVER_SIDE); + + public CommandPacket(Plugin plugin, Logger logger, ProtocolManager manager) { + this.plugin = plugin; + this.logger = logger; + this.manager = manager; + } + + /** + * Construct a packet listener interval tree. + * @return Construct the tree. + */ + private AbstractIntervalTree createTree(final ConnectionSide side) { + return new AbstractIntervalTree() { + @Override + protected Integer decrementKey(Integer key) { + return key != null ? key - 1 : null; + } + + @Override + protected Integer incrementKey(Integer key) { + return key != null ? key + 1 : null; + } + + @Override + protected void onEntryAdded(Entry added) { + // Ensure that the starting ID and the ending ID is correct + // This is necessary because the interval tree may change the range. + if (added != null) { + Range key = added.getKey(); + DetailedPacketListener listener = added.getValue(); + DetailedPacketListener corrected = createPacketListener( + side, key.lowerEndpoint(), key.upperEndpoint(), listener.isDetailed()); + + added.setValue(corrected); + + if (corrected != null) { + manager.addPacketListener(corrected); + } else { + // Never mind + remove(key.lowerEndpoint(), key.upperEndpoint()); + } + } + } + + @Override + protected void onEntryRemoved(Entry removed) { + // Remove the listener + if (removed != null) { + DetailedPacketListener listener = removed.getValue(); + + if (listener != null) { + manager.removePacketListener(listener); + } + } + } + }; + } + + /* + * Description: Adds or removes a simple packet listener. + Usage: / add|remove client|server|both [ID start] [ID stop] [detailed] + */ + @Override + public 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; + + // We need at least one argument + if (args != null && args.length > 0) { + try { + SubCommand subCommand = parseCommand(args, 0); + ConnectionSide side = parseSide(args, 1, ConnectionSide.BOTH); + + int idStart = parseInteger(args, 2, 0); + int idStop = parseInteger(args, 3, 255); + + // Make sure the packet IDs are valid + if (idStart < 0 || idStart > 255) + throw new IllegalAccessError("The starting packet ID must be within 0 - 255."); + if (idStop < 0 || idStop > 255) + throw new IllegalAccessError("The stop packet ID must be within 0 - 255."); + + // Special case. If stop is not set, but start is set, use a interval size of 1. + if (args.length == 3) + idStop = idStart + 1; + + boolean detailed = parseBoolean(args, 4, false); + + // Perform command + if (subCommand == SubCommand.ADD) + addPacketListeners(side, idStart, idStop, detailed); + else + removePacketListeners(side, idStart, idStop, detailed); + + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.DARK_RED + "Cannot parse number: " + e.getMessage()); + } catch (IllegalArgumentException e) { + sender.sendMessage(ChatColor.DARK_RED + e.getMessage()); + } + } + + return false; + } + + private Set getValidPackets(ConnectionSide side) throws FieldAccessException { + if (side.isForClient()) + return Packets.Client.getSupported(); + else if (side.isForServer()) + return Packets.Server.getSupported(); + else + throw new IllegalArgumentException("Illegal side: " + side); + } + + public DetailedPacketListener createPacketListener(final ConnectionSide side, int idStart, int idStop, final boolean detailed) { + + Set range = Ranges.closed(idStart, idStop).asSet(DiscreteDomains.integers()); + Set packets; + + try { + // Only use supported packet IDs + packets = getValidPackets(side); + packets.retainAll(range); + + } catch (FieldAccessException e) { + // Don't filter anything then + packets = range; + } + + // Ignore empty sets + if (packets.isEmpty()) + return null; + + // Create the listener we will be using + final ListeningWhitelist whitelist = new ListeningWhitelist(ListenerPriority.MONITOR, packets, GamePhase.BOTH); + + return new DetailedPacketListener() { + @Override + public void onPacketSending(PacketEvent event) { + if (side.isForServer()) { + printInformation(event); + } + } + + @Override + public void onPacketReceiving(PacketEvent event) { + if (side.isForClient()) { + printInformation(event); + } + } + + private void printInformation(PacketEvent event) { + String verb = side.isForClient() ? "Received" : "Sent"; + String shortDescription = String.format( + "%s packet %s (%s)", + verb, + event.getPacketID(), + Packets.getDeclaredName(event.getPacketID()) + ); + + // Detailed will print the packet's content too + if (detailed) { + logger.info(shortDescription + ":\n" + + ToStringBuilder.reflectionToString(event.getPacket().getHandle(), ToStringStyle.MULTI_LINE_STYLE) + ); + } else { + logger.info(shortDescription + "."); + } + } + + @Override + public ListeningWhitelist getSendingWhitelist() { + return side.isForServer() ? whitelist : ListeningWhitelist.EMPTY_WHITELIST; + } + + @Override + public ListeningWhitelist getReceivingWhitelist() { + return side.isForClient() ? whitelist : ListeningWhitelist.EMPTY_WHITELIST; + } + + @Override + public Plugin getPlugin() { + return plugin; + } + + @Override + public boolean isDetailed() { + return detailed; + } + }; + } + + public void addPacketListeners(ConnectionSide side, int idStart, int idStop, boolean detailed) { + DetailedPacketListener listener = createPacketListener(side, idStart, idStop, detailed); + + // The trees will manage the listeners for us + if (listener != null) + getListenerTree(side).put(idStart, idStop, listener); + else + throw new IllegalArgumentException("No packets found in the range " + idStart + " - " + idStop + "."); + } + + public void removePacketListeners(ConnectionSide side, int idStart, int idStop, boolean detailed) { + // The interval tree will automatically remove the listeners for us + getListenerTree(side).remove(idStart, idStop); + } + + private AbstractIntervalTree getListenerTree(ConnectionSide side) { + if (side.isForClient()) + return clientListeners; + else if (side.isForServer()) + return serverListeners; + else + throw new IllegalArgumentException("Not a legal connection side."); + } + + private SubCommand parseCommand(String[] args, int index) { + String text = args[index].toLowerCase(); + + // Parse this too + if ("add".startsWith(text)) + return SubCommand.ADD; + else if ("remove".startsWith(text)) + return SubCommand.REMOVE; + else + throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove."); + } + + private ConnectionSide parseSide(String[] args, int index, ConnectionSide defaultValue) { + if (index < args.length) { + String text = args[index].toLowerCase(); + + // Parse the side gracefully + if ("both".startsWith(text)) + return ConnectionSide.BOTH; + else if ("client".startsWith(text)) + return ConnectionSide.CLIENT_SIDE; + else if ("server".startsWith(text)) + return ConnectionSide.SERVER_SIDE; + else + throw new IllegalArgumentException(text + " is not a connection side."); + + } else { + return defaultValue; + } + } + + // Parse a boolean + private boolean parseBoolean(String[] args, int index, boolean defaultValue) { + if (index < args.length) { + return Boolean.parseBoolean(args[index]); + } else { + return defaultValue; + } + } + + // And an integer + private int parseInteger(String[] args, int index, int defaultValue) { + if (index < args.length) { + return Integer.parseInt(args[index]); + } else { + return defaultValue; + } + } + + public void cleanupAll() { + clientListeners.clear(); + serverListeners.clear(); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java new file mode 100644 index 00000000..e4d504d7 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java @@ -0,0 +1,84 @@ +package com.comphenix.protocol; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.metrics.Updater; +import com.comphenix.protocol.metrics.Updater.UpdateResult; +import com.comphenix.protocol.metrics.Updater.UpdateType; + +/** + * Handles the "protocol" administration command. + * + * @author Kristian + */ +class CommandProtocol implements CommandExecutor { + /** + * Name of this command. + */ + public static final String NAME = "protocol"; + + private Plugin plugin; + private Updater updater; + + public CommandProtocol(Plugin plugin, Updater updater) { + this.plugin = plugin; + this.updater = updater; + } + + @Override + public 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; + + // We need one argument (the sub-command) + if (args != null && args.length == 1) { + String subCommand = args[0]; + + // Only return TRUE if we executed the correct command + if (subCommand.equalsIgnoreCase("config")) + reloadConfiguration(sender); + else if (subCommand.equalsIgnoreCase("check")) + checkVersion(sender); + else if (subCommand.equalsIgnoreCase("update")) + updateVersion(sender); + else + return false; + return true; + } + + return false; + } + + public void checkVersion(final CommandSender sender) { + // Perform on an async thread + plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() { + @Override + public void run() { + UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true); + sender.sendMessage(ChatColor.DARK_BLUE + "Version check: " + result.toString()); + } + }); + } + + public void updateVersion(final CommandSender sender) { + // Perform on an async thread + plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() { + @Override + public void run() { + UpdateResult result = updater.update(UpdateType.DEFAULT, true); + sender.sendMessage(ChatColor.DARK_BLUE + "Update: " + result.toString()); + } + }); + } + + public void reloadConfiguration(CommandSender sender) { + plugin.saveConfig(); + plugin.reloadConfig(); + sender.sendMessage(ChatColor.DARK_BLUE + "Reloaded configuration!"); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java new file mode 100644 index 00000000..516db0fc --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -0,0 +1,135 @@ +package com.comphenix.protocol; + +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.plugin.Plugin; + +/** + * Represents the configuration of ProtocolLib. + * + * @author Kristian + */ +class ProtocolConfig { + + private static final String SECTION_GLOBAL = "global"; + private static final String SECTION_AUTOUPDATER = "auto updater"; + + private static final String UPDATER_NOTIFY = "notify"; + private static final String UPDATER_DOWNLAD = "download"; + private static final String UPDATER_DELAY = "delay"; + private static final String UPDATER_LAST_TIME = "last"; + + // Defaults + private static final long DEFAULT_UPDATER_DELAY = 60; + + private Plugin plugin; + private Configuration config; + + private ConfigurationSection global; + private ConfigurationSection updater; + + public ProtocolConfig(Plugin plugin) { + this(plugin, plugin.getConfig()); + } + + public ProtocolConfig(Plugin plugin, Configuration config) { + this.config = config; + loadSections(true); + } + + /** + * Load data sections. + * @param copyDefaults - whether or not to copy configuration defaults. + */ + private void loadSections(boolean copyDefaults) { + if (config != null) { + global = config.getConfigurationSection(SECTION_GLOBAL); + } + if (global != null) { + updater = global.getConfigurationSection(SECTION_AUTOUPDATER); + } + + // Automatically copy defaults + if (copyDefaults && (global == null || updater == null)) { + config.options().copyDefaults(true); + loadSections(false); + } + } + + /** + * Retrieve whether or not ProtocolLib should determine if a new version has been released. + * @return TRUE if it should do this automatically, FALSE otherwise. + */ + public boolean isAutoNotify() { + return updater.getBoolean(UPDATER_NOTIFY, true); + } + + /** + * Set whether or not ProtocolLib should determine if a new version has been released. + * @param value - TRUE to do this automatically, FALSE otherwise. + */ + public void setAutoNotify(boolean value) { + updater.set(UPDATER_NOTIFY, value); + } + + /** + * Retrieve whether or not ProtocolLib should automatically download the new version. + * @return TRUE if it should, FALSE otherwise. + */ + public boolean isAutoDownload() { + return updater != null && updater.getBoolean(UPDATER_DOWNLAD, true); + } + + /** + * Set whether or not ProtocolLib should automatically download the new version. + * @param value - TRUE if it should. FALSE otherwise. + */ + public void setAutoDownload(boolean value) { + updater.set(UPDATER_DOWNLAD, value); + } + + /** + * Retrieve the amount of time to wait until checking for a new update. + * @return The amount of time to wait. + */ + public long getAutoDelay() { + // Note that the delay must be greater than 59 seconds + return Math.max(updater.getInt(UPDATER_DELAY, 0), DEFAULT_UPDATER_DELAY); + } + + /** + * Set the amount of time to wait until checking for a new update. + *

    + * This time must be greater than 59 seconds. + * @param delaySeconds - the amount of time to wait. + */ + public void setAutoDelay(long delaySeconds) { + // Silently fix the delay + if (delaySeconds < DEFAULT_UPDATER_DELAY) + delaySeconds = DEFAULT_UPDATER_DELAY; + updater.set(UPDATER_DELAY, delaySeconds); + } + + /** + * Retrieve the last time we updated, in seconds since 1970.01.01 00:00. + * @return Last update time. + */ + public long getAutoLastTime() { + return updater.getLong(UPDATER_LAST_TIME, 0); + } + + /** + * Set the last time we updated, in seconds since 1970.01.01 00:00. + * @param lastTimeSeconds - new last update time. + */ + public void setAutoLastTime(long lastTimeSeconds) { + updater.set(UPDATER_LAST_TIME, lastTimeSeconds); + } + + /** + * Save the current configuration file. + */ + public void saveAll() { + plugin.saveConfig(); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index c17b84d9..e0acc802 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -27,12 +27,10 @@ import org.bukkit.plugin.java.JavaPlugin; import com.comphenix.protocol.async.AsyncFilterManager; import com.comphenix.protocol.error.DetailedErrorReporter; import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.events.ConnectionSide; -import com.comphenix.protocol.events.MonitorAdapter; -import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.DelayedSingleTask; import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.metrics.Statistics; +import com.comphenix.protocol.metrics.Updater; import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; /** @@ -42,12 +40,11 @@ import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; */ public class ProtocolLibrary extends JavaPlugin { + private static final long MILLI_PER_SECOND = 1000; + // There should only be one protocol manager, so we'll make it static private static PacketFilterManager protocolManager; - // Information logger - private Logger logger; - // Error reporter private ErrorReporter reporter; @@ -66,36 +63,66 @@ public class ProtocolLibrary extends JavaPlugin { // Used to unhook players after a delay private DelayedSingleTask unhookTask; - // Used for debugging - private boolean debugListener; + // Settings/options + private ProtocolConfig config; + + // Updater + private Updater updater; + + // Commands + private CommandProtocol commandProtocol; + private CommandPacket commandPacket; @Override public void onLoad() { // Add global parameters DetailedErrorReporter reporter = new DetailedErrorReporter(); + // Load configuration + updater = new Updater(this, "protocollib", getFile(), "protocol.info"); + config = new ProtocolConfig(this); + try { - logger = getLoggerSafely(); unhookTask = new DelayedSingleTask(this); protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, reporter); reporter.addGlobalParameter("manager", protocolManager); + // Initialize command handlers + commandProtocol = new CommandProtocol(this, updater); + commandPacket = new CommandPacket(this, getLoggerSafely(), protocolManager); + } catch (Throwable e) { reporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager); + disablePlugin(); } } + @Override + public void reloadConfig() { + super.reloadConfig(); + // Reload configuration + config = new ProtocolConfig(this); + } + @Override public void onEnable() { try { Server server = getServer(); PluginManager manager = server.getPluginManager(); - + + // Don't do anything else! + if (manager == null) + return; + // Initialize background compiler if (backgroundCompiler == null) { backgroundCompiler = new BackgroundCompiler(getClassLoader()); BackgroundCompiler.setInstance(backgroundCompiler); } + + // Set up command handlers + getCommand(CommandProtocol.NAME).setExecutor(commandProtocol); + getCommand(CommandPacket.NAME).setExecutor(commandPacket); // Notify server managers of incompatible plugins checkForIncompatibility(manager); @@ -104,8 +131,8 @@ public class ProtocolLibrary extends JavaPlugin { protocolManager.registerEvents(manager, this); // Worker that ensures that async packets are eventually sent + // It also performs the update check. createAsyncTask(server); - //toggleDebugListener(); } catch (Throwable e) { reporter.reportDetailed(this, "Cannot enable ProtocolLib.", e); @@ -122,39 +149,6 @@ public class ProtocolLibrary extends JavaPlugin { reporter.reportDetailed(this, "Metrics cannot be enabled. Incompatible Bukkit version.", e, statistisc); } } - - /** - * Toggle a listener that prints every sent and received packet. - */ - void toggleDebugListener() { - - if (debugListener) { - protocolManager.removePacketListeners(this); - } else { - // DEBUG DEBUG - protocolManager.addPacketListener(new MonitorAdapter(this, ConnectionSide.BOTH, logger) { - @Override - public void onPacketReceiving(PacketEvent event) { - Object handle = event.getPacket().getHandle(); - - logger.info(String.format( - "RECEIVING %s@%s from %s.", - handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName() - )); - }; - @Override - public void onPacketSending(PacketEvent event) { - Object handle = event.getPacket().getHandle(); - - logger.info(String.format( - "SENDING %s@%s from %s.", - handle.getClass().getSimpleName(), handle.hashCode(), event.getPlayer().getName() - )); - } - }); - } - debugListener = !debugListener; - } /** * Disable the current plugin. @@ -176,6 +170,9 @@ public class ProtocolLibrary extends JavaPlugin { // We KNOW we're on the main thread at the moment manager.sendProcessedPackets(tickCounter++, true); + + // Check for updates too + checkUpdates(); } }, ASYNC_PACKET_DELAY, ASYNC_PACKET_DELAY); @@ -186,6 +183,24 @@ public class ProtocolLibrary extends JavaPlugin { } } + private void checkUpdates() { + // Ignore milliseconds - it's pointless + long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND; + + // Should we update? + if (currentTime < config.getAutoLastTime() + config.getAutoDelay()) { + // Great. Save this check. + config.setAutoLastTime(currentTime); + config.saveAll(); + + // Initiate the update from the console + if (config.isAutoDownload()) + commandProtocol.updateVersion(getServer().getConsoleSender()); + else if (config.isAutoNotify()) + commandProtocol.checkVersion(getServer().getConsoleSender()); + } + } + private void checkForIncompatibility(PluginManager manager) { // Plugin authors: Notify me to remove these String[] incompatiblePlugins = {}; @@ -223,6 +238,20 @@ public class ProtocolLibrary extends JavaPlugin { cleanup.resetAll(); } + // Get the Bukkit logger first, before we try to create our own + private Logger getLoggerSafely() { + Logger log = null; + + try { + log = getLogger(); + } catch (Throwable e) { } + + // Use the default logger instead + if (log == null) + log = Logger.getLogger("Minecraft"); + return log; + } + /** * Retrieves the packet protocol manager. * @return Packet protocol manager, or NULL if it has been disabled. @@ -240,20 +269,4 @@ public class ProtocolLibrary extends JavaPlugin { public Statistics getStatistics() { return statistisc; } - - // Get the Bukkit logger first, before we try to create our own - private Logger getLoggerSafely() { - - Logger log = null; - - try { - log = getLogger(); - } catch (Throwable e) { - // We'll handle it - } - - if (log == null) - log = Logger.getLogger("Minecraft"); - return log; - } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java new file mode 100644 index 00000000..de627887 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java @@ -0,0 +1,362 @@ +package com.comphenix.protocol.concurrency; + +import java.util.HashSet; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Set; +import java.util.TreeMap; + +import org.apache.commons.lang.NotImplementedException; + +import com.google.common.collect.Range; +import com.google.common.collect.Ranges; + +/** + * Represents a generic store of intervals and associated values. No two intervals + * can overlap in this representation. + *

    + * Note that this implementation is not thread safe. + * + * @author Kristian + * + * @param - type of the key. Must implement Comparable. + * @param - type of the value to associate. + */ +public abstract class AbstractIntervalTree, TValue> { + + protected enum State { + OPEN, + CLOSE, + BOTH + } + + /** + * Represents a range and a value in this interval tree. + */ + public class Entry implements Map.Entry, TValue> { + private final Range key; + private final TValue value; + + public Entry(Range key, TValue value) { + this.key = key; + this.value = value; + } + + @Override + public Range getKey() { + return key; + } + + @Override + public TValue getValue() { + return value; + } + + @Override + public TValue setValue(TValue value) { + throw new NotImplementedException(); + } + } + + /** + * Represents a single end point (open, close or both) of a range. + */ + protected class EndPoint { + + // Whether or not the end-point is opening a range, closing a range or both. + public State state; + + // The value this range contains + public TValue value; + + public EndPoint(State state, TValue value) { + this.state = state; + this.value = value; + } + } + + // To quickly look up ranges we'll index them by endpoints + protected NavigableMap bounds = new TreeMap(); + + /** + * Removes every interval that intersects with the given range. + * @param lowerBound - lowest value to remove. + * @param upperBound - highest value to remove. + */ + public void remove(TKey lowerBound, TKey upperBound) { + remove(lowerBound, upperBound, false); + } + + /** + * Removes every interval that intersects with the given range. + * @param lowerBound - lowest value to remove. + * @param upperBound - highest value to remove. + * @param preserveOutside - whether or not to preserve the intervals that are partially outside. + */ + public void remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) { + checkBounds(lowerBound, upperBound); + NavigableMap range = bounds.subMap(lowerBound, true, upperBound, true); + + boolean emptyRange = range.isEmpty(); + TKey first = emptyRange ? range.firstKey() : null; + TKey last = emptyRange ? range.lastKey() : null; + + Set resized = new HashSet(); + Set removed = new HashSet(); + + // Remove the previous element too. A close end-point must be preceded by an OPEN end-point. + if (first != null && range.get(first).state == State.CLOSE) { + TKey key = bounds.floorKey(first); + EndPoint removedPoint = removeIfNonNull(key); + + // Add the interval back + if (removedPoint != null && preserveDifference) { + resized.add(putUnsafe(key, decrementKey(lowerBound), removedPoint.value)); + } + } + + // Get the closing element too. + if (last != null && range.get(last).state == State.OPEN) { + TKey key = bounds.ceilingKey(last); + EndPoint removedPoint = removeIfNonNull(key); + + if (removedPoint != null && preserveDifference) { + resized.add(putUnsafe(incrementKey(upperBound), key, removedPoint.value)); + } + } + + // Get the removed entries too + getEntries(removed, range); + invokeEntryRemoved(removed); + + if (preserveDifference) { + invokeEntryRemoved(resized); + invokeEntryAdded(resized); + } + + // Remove the range as well + range.clear(); + } + + // Helper + private EndPoint removeIfNonNull(TKey key) { + if (key != null) { + return bounds.remove(key); + } else { + return null; + } + } + + // Adds a given end point + protected EndPoint addEndPoint(TKey key, TValue value, State state) { + EndPoint endPoint = bounds.get(key); + + if (endPoint != null) { + endPoint.state = State.BOTH; + } else { + endPoint = bounds.put(key, new EndPoint(state, value)); + } + return endPoint; + } + + /** + * Associates a given interval of keys with a certain value. Any previous + * association will be overwritten in the given interval. + *

    + * Overlapping intervals are not permitted. A key can only be associated with a single value. + * + * @param lowerBound - the minimum key (inclusive). + * @param upperBound - the maximum key (inclusive). + * @param value - the value, or NULL to reset this range. + */ + public void put(TKey lowerBound, TKey upperBound, TValue value) { + // While we don't permit overlapping intervals, we'll still allow overwriting existing intervals. + remove(lowerBound, upperBound, true); + invokeEntryAdded(putUnsafe(lowerBound, upperBound, value)); + } + + /** + * Associates a given interval without performing any interval checks. + * @param lowerBound - the minimum key (inclusive). + * @param upperBound - the maximum key (inclusive). + * @param value - the value, or NULL to reset the range. + */ + private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) { + // OK. Add the end points now + if (value != null) { + addEndPoint(lowerBound, value, State.OPEN); + addEndPoint(upperBound, value, State.CLOSE); + + Range range = Ranges.closed(lowerBound, upperBound); + return new Entry(range, value); + } else { + return null; + } + } + + /** + * Used to verify the validity of the given interval. + * @param lowerBound - lower bound (inclusive). + * @param upperBound - upper bound (inclusive). + */ + private void checkBounds(TKey lowerBound, TKey upperBound) { + if (lowerBound == null) + throw new IllegalAccessError("lowerbound cannot be NULL."); + if (upperBound == null) + throw new IllegalAccessError("upperBound cannot be NULL."); + if (upperBound.compareTo(lowerBound) < 0) + throw new IllegalArgumentException("upperBound cannot be less than lowerBound."); + } + + /** + * Determines if the given key is within an interval. + * @param key - key to check. + * @return TRUE if the given key is within an interval in this tree, FALSE otherwise. + */ + public boolean containsKey(TKey key) { + return getEndPoint(key) != null; + } + + /** + * Enumerates over every range in this interval tree. + * @return Number of ranges. + */ + public Set entrySet() { + // Don't mind the Java noise + Set result = new HashSet(); + getEntries(result, bounds); + return result; + } + + /** + * Remove every interval. + */ + public void clear() { + if (!bounds.isEmpty()) { + remove(bounds.firstKey(), bounds.lastKey()); + } + } + + /** + * Converts a map of end points into a set of entries. + * @param destination - set of entries. + * @param map - a map of end points. + */ + private void getEntries(Set destination, NavigableMap map) { + Map.Entry last = null; + + for (Map.Entry entry : bounds.entrySet()) { + switch (entry.getValue().state) { + case BOTH: + destination.add(new Entry(Ranges.singleton(entry.getKey()), entry.getValue().value)); + break; + case CLOSE: + Range range = Ranges.closed(last.getKey(), entry.getKey()); + destination.add(new Entry(range, entry.getValue().value)); + break; + case OPEN: + // We don't know the full range yet + last = entry; + break; + default: + throw new IllegalStateException("Illegal open/close state detected."); + } + } + } + + /** + * Inserts every range from the given tree into the current tree. + * @param other - the other tree to read from. + */ + public void putAll(AbstractIntervalTree other) { + // Naively copy every range. + for (Entry entry : other.entrySet()) { + put(entry.key.lowerEndpoint(), entry.key.upperEndpoint(), entry.value); + } + } + + /** + * Retrieves the value of the range that matches the given key, or NULL if nothing was found. + * @param key - the level to read for. + * @return The correct amount of experience, or NULL if nothing was recorded. + */ + public TValue get(TKey key) { + EndPoint point = getEndPoint(key); + + if (point != null) + return point.value; + else + return null; + } + + /** + * Get the end-point composite associated with this key. + * @param key - key to search for. + * @return The end point found, or NULL. + */ + protected EndPoint getEndPoint(TKey key) { + EndPoint ends = bounds.get(key); + + if (ends != null) { + // This is a piece of cake + return ends; + } else { + + // We need to determine if the point intersects with a range + TKey left = bounds.floorKey(key); + + // We only need to check to the left + if (left != null && bounds.get(left).state == State.OPEN) { + return bounds.get(left); + } else { + return null; + } + } + } + + private void invokeEntryAdded(Entry added) { + if (added != null) { + onEntryAdded(added); + } + } + + private void invokeEntryAdded(Set added) { + for (Entry entry : added) { + onEntryAdded(entry); + } + } + + private void invokeEntryRemoved(Set removed) { + for (Entry entry : removed) { + onEntryRemoved(entry); + } + } + + // Listeners for added or removed entries + /** + * Invoked when an entry is added. + * @param added - the entry that was added. + */ + protected void onEntryAdded(Entry added) { } + + /** + * Invoked when an entry is removed. + * @param removed - the removed entry. + */ + protected void onEntryRemoved(Entry removed) { } + + // Helpers for decrementing or incrementing key values + /** + * Decrement the given key by one unit. + * @param key - the key that should be decremented. + * @return The new decremented key. + */ + protected abstract TKey decrementKey(TKey key); + + /** + * Increment the given key by one unit. + * @param key - the key that should be incremented. + * @return The new incremented key. + */ + protected abstract TKey incrementKey(TKey key); +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java index 13f0a5eb..bceb7dca 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java @@ -28,6 +28,9 @@ public class DetailedErrorReporter implements ErrorReporter { public static final String DEFAULT_SUPPORT_URL = "http://dev.bukkit.org/server-mods/protocollib/"; public static final String PLUGIN_NAME = "ProtocolLib"; + // Users that are informed about errors in the chat + public static final String ERROR_PERMISSION = "protocol.info"; + // We don't want to spam the server public static final int DEFAULT_MAX_ERROR_COUNT = 20; @@ -158,8 +161,16 @@ public class DetailedErrorReporter implements ErrorReporter { if (Bukkit.getServer() != null) { writer.println("Server:"); writer.println(addPrefix(Bukkit.getServer().getVersion(), SECOND_LEVEL_PREFIX)); + + // Inform of this occurrence + if (ERROR_PERMISSION != null) { + Bukkit.getServer().broadcast( + String.format("Error %s (%s) occured in %s.", message, error, sender), + ERROR_PERMISSION + ); + } } - + // Make sure it is reported logger.severe(addPrefix(text.toString(), prefix)); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java index 2108c2cd..14eee6d5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java @@ -50,26 +50,31 @@ public class Updater private static final String DBOUrl = "http://dev.bukkit.org/server-mods/"; private static final int BYTE_SIZE = 1024; // Used for downloading files - private Plugin plugin; - private UpdateType type; - private String downloadedVersion; - private String versionTitle; - private String versionLink; - private long totalSize; // Holds the total size of the file - private int sizeLine; // Used for detecting file size - private int multiplier; // Used for determining when to broadcast download updates - private boolean announce; // Whether to announce file downloads - private URL url; // Connecting to RSS + private final Plugin plugin; + private final String slug; - private String updateFolder = YamlConfiguration.loadConfiguration(new File("bukkit.yml")).getString("settings.update-folder"); // The folder that downloads will be placed in + private volatile long totalSize; // Holds the total size of the file + private volatile int sizeLine; // Used for detecting file size + private volatile int multiplier; // Used for determining when to broadcast download updates + + private volatile URL url; // Connecting to RSS + + private volatile String updateFolder = YamlConfiguration.loadConfiguration(new File("bukkit.yml")).getString("settings.update-folder"); // The folder that downloads will be placed in // Used for determining the outcome of the update process - private Updater.UpdateResult result = Updater.UpdateResult.SUCCESS; - private String slug; - private File file; + private volatile Updater.UpdateResult result = Updater.UpdateResult.SUCCESS; + + // Whether to announce file downloads + private volatile boolean announce; + + private volatile UpdateType type; + private volatile String downloadedVersion; + private volatile String versionTitle; + private volatile String versionLink; + private volatile File file; // Used to announce progress - private Logger logger; + private volatile Logger logger; // Strings for reading RSS private static final String TITLE = "title"; @@ -84,38 +89,46 @@ public class Updater /** * The updater found an update, and has readied it to be loaded the next time the server restarts/reloads. */ - SUCCESS(1), + SUCCESS(1, "The updater found an update, and has readied it to be loaded the next time the server restarts/reloads."), + /** * The updater did not find an update, and nothing was downloaded. */ - NO_UPDATE(2), + NO_UPDATE(2, "The updater did not find an update, and nothing was downloaded."), + /** * The updater found an update, but was unable to download it. */ - FAIL_DOWNLOAD(3), + FAIL_DOWNLOAD(3, "The updater found an update, but was unable to download it."), + /** * For some reason, the updater was unable to contact dev.bukkit.org to download the file. */ - FAIL_DBO(4), + FAIL_DBO(4, "For some reason, the updater was unable to contact dev.bukkit.org to download the file."), + /** * When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as 'v1.0'. */ - FAIL_NOVERSION(5), + FAIL_NOVERSION(5, "When running the version check, the file on DBO did not contain the a version in the format 'vVersion' such as 'v1.0'."), + /** * The slug provided by the plugin running the updater was invalid and doesn't exist on DBO. */ - FAIL_BADSLUG(6), + FAIL_BADSLUG(6, "The slug provided by the plugin running the updater was invalid and doesn't exist on DBO."), + /** * The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded. */ - UPDATE_AVAILABLE(7); + UPDATE_AVAILABLE(7, "The updater found an update, but because of the UpdateType being set to NO_DOWNLOAD, it wasn't downloaded."); private static final Map valueList = new HashMap(); private final int value; + private final String description; - private UpdateResult(int value) + private UpdateResult(int value, String description) { this.value = value; + this.description = description; } public int getValue() @@ -128,6 +141,11 @@ public class Updater return valueList.get(value); } + @Override + public String toString() { + return description; + } + static { for(Updater.UpdateResult result : Updater.UpdateResult.values()) @@ -240,7 +258,7 @@ public class Updater * True if the program should announce the progress of new updates in console * @return The result of the update process. */ - public UpdateResult update(UpdateType type, boolean announce) + public synchronized UpdateResult update(UpdateType type, boolean announce) { this.type = type; this.announce = announce; diff --git a/ProtocolLib/src/main/resources/config.yml b/ProtocolLib/src/main/resources/config.yml new file mode 100644 index 00000000..a16abc8f --- /dev/null +++ b/ProtocolLib/src/main/resources/config.yml @@ -0,0 +1,10 @@ +global: + # Settings for the automatic version updater + auto updater: + notify: true + download: true + + # Number of seconds to wait until a new update is downloaded + delay = 43200 # 12 hours + # Last update time + last = 0 \ No newline at end of file diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 2bc5f45d..c684d1d4 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -10,12 +10,12 @@ database: false commands: protocol: description: Performs administrative tasks regarding ProtocolLib. - usage: / [reload|update] + usage: / config|check|update permission: experiencemod.admin permission-message: You don't have packet: description: Adds or removes a simple packet listener. - usage: / [add|remove|clear] [ID start] [ID stop] + usage: / add|remove client|server|both [ID start] [ID stop] [detailed] permission: experiencemod.admin permission-message: You don't have @@ -29,5 +29,5 @@ permissions: description: Able to initiate the update process, and can configure debug mode. default: op protocol.info: - description: Can read update notifications, debug messages and error reports. + description: Can read update notifications and error reports. default: op \ No newline at end of file From 413eb283a48810bfda176266e313081470c9fc03 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 00:54:03 +0100 Subject: [PATCH 38/54] Fix handling of configuration. --- .../comphenix/protocol/ProtocolConfig.java | 3 ++- .../comphenix/protocol/ProtocolLibrary.java | 21 +++++++++++++++++-- ProtocolLib/src/main/resources/config.yml | 4 ++-- ProtocolLib/src/main/resources/plugin.yml | 2 +- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java index 516db0fc..517cf6f1 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -51,7 +51,8 @@ class ProtocolConfig { // Automatically copy defaults if (copyDefaults && (global == null || updater == null)) { - config.options().copyDefaults(true); + plugin.saveDefaultConfig(); + config = plugin.getConfig(); loadSections(false); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index e0acc802..c9da1902 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -17,6 +17,7 @@ package com.comphenix.protocol; +import java.io.File; import java.io.IOException; import java.util.logging.Logger; @@ -80,8 +81,17 @@ public class ProtocolLibrary extends JavaPlugin { // Load configuration updater = new Updater(this, "protocollib", getFile(), "protocol.info"); - config = new ProtocolConfig(this); - + + try { + config = new ProtocolConfig(this); + } catch (Exception e) { + reporter.reportWarning(this, "Cannot load configuration", e); + + // Load it again + deleteConfig(); + config = new ProtocolConfig(this); + } + try { unhookTask = new DelayedSingleTask(this); protocolManager = new PacketFilterManager(getClassLoader(), getServer(), unhookTask, reporter); @@ -97,6 +107,13 @@ public class ProtocolLibrary extends JavaPlugin { } } + private void deleteConfig() { + File configFile = new File(getDataFolder(), "config.yml"); + + // Delete the file + configFile.delete(); + } + @Override public void reloadConfig() { super.reloadConfig(); diff --git a/ProtocolLib/src/main/resources/config.yml b/ProtocolLib/src/main/resources/config.yml index a16abc8f..aa16c45f 100644 --- a/ProtocolLib/src/main/resources/config.yml +++ b/ProtocolLib/src/main/resources/config.yml @@ -5,6 +5,6 @@ global: download: true # Number of seconds to wait until a new update is downloaded - delay = 43200 # 12 hours + delay: 43200 # 12 hours # Last update time - last = 0 \ No newline at end of file + last: 0 \ No newline at end of file diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index c684d1d4..8fd825ea 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -14,7 +14,7 @@ commands: permission: experiencemod.admin permission-message: You don't have packet: - description: Adds or removes a simple packet listener. + description: Add or remove a simple packet listener. usage: / add|remove client|server|both [ID start] [ID stop] [detailed] permission: experiencemod.admin permission-message: You don't have From f16581cdf4a49c5f8711d74b858424cb666e6850 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 06:41:29 +0100 Subject: [PATCH 39/54] Massive update. --- .../com/comphenix/protocol/CommandPacket.java | 309 +++++++++++++++--- .../comphenix/protocol/ProtocolLibrary.java | 38 ++- .../concurrency/AbstractIntervalTree.java | 53 +-- .../protocol/events/ListeningWhitelist.java | 15 +- .../comphenix/protocol/metrics/Updater.java | 33 +- .../protocol/reflect/PrettyPrinter.java | 172 ++++++++++ .../protocol/utility/ChatExtensions.java | 85 +++++ ProtocolLib/src/main/resources/plugin.yml | 2 +- 8 files changed, 596 insertions(+), 111 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 510842a2..c0a85c49 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -1,10 +1,16 @@ package com.comphenix.protocol; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.commons.lang.builder.ToStringBuilder; -import org.apache.commons.lang.builder.ToStringStyle; +import net.minecraft.server.Packet; +import net.sf.cglib.proxy.Factory; + import org.bukkit.ChatColor; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -12,6 +18,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; import com.comphenix.protocol.concurrency.AbstractIntervalTree; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.events.ConnectionSide; import com.comphenix.protocol.events.ListenerPriority; import com.comphenix.protocol.events.ListeningWhitelist; @@ -19,6 +26,8 @@ import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.GamePhase; 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.Range; import com.google.common.collect.Ranges; @@ -48,16 +57,21 @@ class CommandPacket implements CommandExecutor { private Plugin plugin; private Logger logger; + private ErrorReporter reporter; private ProtocolManager manager; + + private ChatExtensions chatter; // Registered packet listeners private AbstractIntervalTree clientListeners = createTree(ConnectionSide.CLIENT_SIDE); private AbstractIntervalTree serverListeners = createTree(ConnectionSide.SERVER_SIDE); - public CommandPacket(Plugin plugin, Logger logger, ProtocolManager manager) { + public CommandPacket(Plugin plugin, Logger logger, ErrorReporter reporter, ProtocolManager manager) { this.plugin = plugin; this.logger = logger; + this.reporter = reporter; this.manager = manager; + this.chatter = new ChatExtensions(manager); } /** @@ -111,6 +125,33 @@ class CommandPacket implements CommandExecutor { }; } + /** + * Send a message without invoking the packet listeners. + * @param player - the player to send it to. + * @param message - the message to send. + * @return TRUE if the message was sent successfully, FALSE otherwise. + */ + public void sendMessageSilently(CommandSender receiver, String message) { + try { + chatter.sendMessageSilently(receiver, message); + } catch (InvocationTargetException e) { + reporter.reportDetailed(this, "Cannot send chat message.", e, receiver, message); + } + } + + /** + * Broadcast a message without invoking any packet listeners. + * @param message - message to send. + * @param permission - permission required to receieve the message. NULL to target everyone. + */ + public void broadcastMessageSilently(String message, String permission) { + try { + chatter.broadcastMessageSilently(message, permission); + } catch (InvocationTargetException e) { + reporter.reportDetailed(this, "Cannot send chat message.", e, message, message); + } + } + /* * Description: Adds or removes a simple packet listener. Usage: / add|remove client|server|both [ID start] [ID stop] [detailed] @@ -127,37 +168,189 @@ class CommandPacket implements CommandExecutor { SubCommand subCommand = parseCommand(args, 0); ConnectionSide side = parseSide(args, 1, ConnectionSide.BOTH); - int idStart = parseInteger(args, 2, 0); - int idStop = parseInteger(args, 3, 255); + Integer lastIndex = args.length - 1; + Boolean detailed = parseBoolean(args, lastIndex); + + // See if the last element is a boolean + if (detailed == null) { + detailed = false; + } else { + lastIndex--; + } // Make sure the packet IDs are valid - if (idStart < 0 || idStart > 255) - throw new IllegalAccessError("The starting packet ID must be within 0 - 255."); - if (idStop < 0 || idStop > 255) - throw new IllegalAccessError("The stop packet ID must be within 0 - 255."); + List> ranges = getRanges(args, 2, lastIndex, Ranges.closed(0, 255)); + + if (ranges.isEmpty()) { + // Use every packet ID + ranges.add(Ranges.closed(0, 255)); + } - // Special case. If stop is not set, but start is set, use a interval size of 1. - if (args.length == 3) - idStop = idStart + 1; - - boolean detailed = parseBoolean(args, 4, false); - // Perform command - if (subCommand == SubCommand.ADD) - addPacketListeners(side, idStart, idStop, detailed); - else - removePacketListeners(side, idStart, idStop, detailed); + if (subCommand == SubCommand.ADD) { + for (Range range : ranges) { + DetailedPacketListener listener = addPacketListeners(side, range.lowerEndpoint(), range.upperEndpoint(), detailed); + sendMessageSilently(sender, ChatColor.BLUE + "Added listener " + getWhitelistInfo(listener)); + } + + } else if (subCommand == SubCommand.REMOVE) { + int count = 0; + + // Remove each packet listener + for (Range range : ranges) { + count += removePacketListeners(side, range.lowerEndpoint(), range.upperEndpoint(), detailed).size(); + } + + sendMessageSilently(sender, ChatColor.BLUE + "Fully removed " + count + " listeners."); + } } catch (NumberFormatException e) { - sender.sendMessage(ChatColor.DARK_RED + "Cannot parse number: " + e.getMessage()); + sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage()); } catch (IllegalArgumentException e) { - sender.sendMessage(ChatColor.DARK_RED + e.getMessage()); + sendMessageSilently(sender, ChatColor.RED + e.getMessage()); } + + return true; } return false; } + /** + * Parse ranges from an array of tokens. + * @param args - array of tokens. + * @param offset - beginning offset. + * @param legalRange - range of legal values. + * @return The parsed ranges. + */ + public static List> getRanges(String[] args, int offset, int lastIndex, Range legalRange) { + List tokens = tokenizeInput(args, offset, lastIndex); + List> ranges = new ArrayList>(); + + for (int i = 0; i < tokens.size(); i++) { + Range range; + String current = tokens.get(i); + String next = i + 1 < tokens.size() ? tokens.get(i + 1) : null; + + // Yoda equality is done for null-safety + if ("-".equals(current)) { + throw new IllegalArgumentException("A hyphen must appear between two numbers."); + } else if ("-".equals(next)) { + if (i + 2 >= tokens.size()) + throw new IllegalArgumentException("Cannot form a range without a upper limit."); + + // This is a proper range + range = Ranges.closed(Integer.parseInt(current), Integer.parseInt(tokens.get(i + 2))); + ranges.add(range); + + // Skip the two next tokens + i += 2; + + } else { + // Just a single number + range = Ranges.singleton(Integer.parseInt(current)); + ranges.add(range); + } + + // Validate ranges + if (!legalRange.encloses(range)) { + throw new IllegalArgumentException(range + " is not in the range " + range.toString()); + } + } + + return simplify(ranges, legalRange.upperEndpoint()); + } + + /** + * Simplify a list of ranges by assuming a maximum value. + * @param ranges - the list of ranges to simplify. + * @param maximum - the maximum value (minimum value is always 0). + * @return A simplified list of ranges. + */ + private static List> simplify(List> ranges, int maximum) { + List> result = new ArrayList>(); + boolean[] set = new boolean[maximum + 1]; + int start = -1; + + // Set every ID + for (Range range : ranges) { + for (int id : range.asSet(DiscreteDomains.integers())) { + set[id] = true; + } + } + + // Generate ranges from this set + for (int i = 0; i <= set.length; i++) { + if (i < set.length && set[i]) { + if (start < 0) { + start = i; + } + } else { + if (start > 0) { + result.add(Ranges.closed(start, i - 1)); + start = -1; + } + } + } + + return result; + } + + private static List tokenizeInput(String[] args, int offset, int lastIndex) { + List tokens = new ArrayList(); + + // Tokenize the input + for (int i = offset; i <= lastIndex; i++) { + String text = args[i]; + StringBuilder number = new StringBuilder(); + + for (int j = 0; j < text.length(); j++) { + char current = text.charAt(j); + + if (Character.isDigit(current)) { + number.append(current); + } else if (Character.isWhitespace(current)) { + // That's ok + } else if (current == '-') { + // Add the number token first + if (number.length() > 0) { + tokens.add(number.toString()); + number.setLength(0); + } + + tokens.add(Character.toString(current)); + } else { + throw new IllegalArgumentException("Illegal character '" + current + "' found."); + } + } + + // Add the number token, if it hasn't already + if (number.length() > 0) + tokens.add(number.toString()); + } + + return tokens; + } + + /** + * Retrieve whitelist information about a given listener. + * @param listener - the given listener. + * @return Whitelist information. + */ + private String getWhitelistInfo(PacketListener listener) { + boolean sendingEmpty = ListeningWhitelist.isEmpty(listener.getSendingWhitelist()); + boolean receivingEmpty = ListeningWhitelist.isEmpty(listener.getReceivingWhitelist()); + + if (!sendingEmpty && !receivingEmpty) + return String.format("Sending: %s, Receiving: %s", listener.getSendingWhitelist(), listener.getReceivingWhitelist()); + else if (!sendingEmpty) + return listener.getSendingWhitelist().toString(); + else if (!receivingEmpty) + return listener.getReceivingWhitelist().toString(); + else + return "[None]"; + } + private Set getValidPackets(ConnectionSide side) throws FieldAccessException { if (side.isForClient()) return Packets.Client.getSupported(); @@ -174,7 +367,7 @@ class CommandPacket implements CommandExecutor { try { // Only use supported packet IDs - packets = getValidPackets(side); + packets = new HashSet(getValidPackets(side)); packets.retainAll(range); } catch (FieldAccessException e) { @@ -207,17 +400,32 @@ class CommandPacket implements CommandExecutor { private void printInformation(PacketEvent event) { String verb = side.isForClient() ? "Received" : "Sent"; String shortDescription = String.format( - "%s packet %s (%s)", + "%s %s (%s) from %s", verb, + Packets.getDeclaredName(event.getPacketID()), event.getPacketID(), - Packets.getDeclaredName(event.getPacketID()) + event.getPlayer().getName() ); // Detailed will print the packet's content too if (detailed) { - logger.info(shortDescription + ":\n" + - ToStringBuilder.reflectionToString(event.getPacket().getHandle(), ToStringStyle.MULTI_LINE_STYLE) - ); + try { + Packet packet = event.getPacket().getHandle(); + Class clazz = packet.getClass(); + + // Get the first Minecraft super class + while ((!clazz.getName().startsWith("net.minecraft.server") || + Factory.class.isAssignableFrom(clazz)) && clazz != Object.class) { + clazz = clazz.getSuperclass(); + } + + logger.info(shortDescription + ":\n" + + PrettyPrinter.printObject(packet, clazz, Packet.class) + ); + + } catch (IllegalAccessException e) { + logger.log(Level.WARNING, "Unable to use reflection.", e); + } } else { logger.info(shortDescription + "."); } @@ -244,20 +452,24 @@ class CommandPacket implements CommandExecutor { } }; } - - public void addPacketListeners(ConnectionSide side, int idStart, int idStop, boolean detailed) { + + public DetailedPacketListener addPacketListeners(ConnectionSide side, int idStart, int idStop, boolean detailed) { DetailedPacketListener listener = createPacketListener(side, idStart, idStop, detailed); // The trees will manage the listeners for us - if (listener != null) + if (listener != null) { getListenerTree(side).put(idStart, idStop, listener); - else + return listener; + } else { throw new IllegalArgumentException("No packets found in the range " + idStart + " - " + idStop + "."); + } } - public void removePacketListeners(ConnectionSide side, int idStart, int idStop, boolean detailed) { + public Set.Entry> removePacketListeners( + ConnectionSide side, int idStart, int idStop, boolean detailed) { + // The interval tree will automatically remove the listeners for us - getListenerTree(side).remove(idStart, idStop); + return getListenerTree(side).remove(idStart, idStop); } private AbstractIntervalTree getListenerTree(ConnectionSide side) { @@ -286,9 +498,7 @@ class CommandPacket implements CommandExecutor { String text = args[index].toLowerCase(); // Parse the side gracefully - if ("both".startsWith(text)) - return ConnectionSide.BOTH; - else if ("client".startsWith(text)) + if ("client".startsWith(text)) return ConnectionSide.CLIENT_SIDE; else if ("server".startsWith(text)) return ConnectionSide.SERVER_SIDE; @@ -299,27 +509,18 @@ class CommandPacket implements CommandExecutor { return defaultValue; } } - + // Parse a boolean - private boolean parseBoolean(String[] args, int index, boolean defaultValue) { + private Boolean parseBoolean(String[] args, int index) { if (index < args.length) { - return Boolean.parseBoolean(args[index]); + if (args[index].equalsIgnoreCase("true")) + return true; + else if (args[index].equalsIgnoreCase("false")) + return false; + else + return null; } else { - return defaultValue; + return null; } } - - // And an integer - private int parseInteger(String[] args, int index, int defaultValue) { - if (index < args.length) { - return Integer.parseInt(args[index]); - } else { - return defaultValue; - } - } - - public void cleanupAll() { - clientListeners.clear(); - serverListeners.clear(); - } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index c9da1902..eb713965 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -19,6 +19,8 @@ package com.comphenix.protocol; import java.io.File; import java.io.IOException; +import java.util.logging.Handler; +import java.util.logging.LogRecord; import java.util.logging.Logger; import org.bukkit.Server; @@ -42,6 +44,7 @@ import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; public class ProtocolLibrary extends JavaPlugin { private static final long MILLI_PER_SECOND = 1000; + private static final String PERMISSION_INFO = "protocol.info"; // There should only be one protocol manager, so we'll make it static private static PacketFilterManager protocolManager; @@ -70,17 +73,21 @@ public class ProtocolLibrary extends JavaPlugin { // Updater private Updater updater; + // Logger + private Logger logger; + // Commands private CommandProtocol commandProtocol; private CommandPacket commandPacket; @Override public void onLoad() { + // Load configuration + logger = getLoggerSafely(); + // Add global parameters DetailedErrorReporter reporter = new DetailedErrorReporter(); - - // Load configuration - updater = new Updater(this, "protocollib", getFile(), "protocol.info"); + updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); try { config = new ProtocolConfig(this); @@ -99,7 +106,10 @@ public class ProtocolLibrary extends JavaPlugin { // Initialize command handlers commandProtocol = new CommandProtocol(this, updater); - commandPacket = new CommandPacket(this, getLoggerSafely(), protocolManager); + commandPacket = new CommandPacket(this, logger, reporter, protocolManager); + + // Send logging information to player listeners too + broadcastUsers(PERMISSION_INFO); } catch (Throwable e) { reporter.reportDetailed(this, "Cannot load ProtocolLib.", e, protocolManager); @@ -121,6 +131,26 @@ public class ProtocolLibrary extends JavaPlugin { config = new ProtocolConfig(this); } + private void broadcastUsers(final String permission) { + // Broadcast information to every user too + logger.addHandler(new Handler() { + @Override + public void publish(LogRecord record) { + commandPacket.broadcastMessageSilently(record.getMessage(), permission); + } + + @Override + public void flush() { + // Not needed. + } + + @Override + public void close() throws SecurityException { + // Do nothing. + } + }); + } + @Override public void onEnable() { try { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java index de627887..e2748cbc 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java @@ -6,8 +6,6 @@ import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; -import org.apache.commons.lang.NotImplementedException; - import com.google.common.collect.Range; import com.google.common.collect.Ranges; @@ -35,11 +33,18 @@ public abstract class AbstractIntervalTree, TValue */ public class Entry implements Map.Entry, TValue> { private final Range key; - private final TValue value; - - public Entry(Range key, TValue value) { + private EndPoint left; + private EndPoint right; + + Entry(Range key, EndPoint left, EndPoint right) { + if (left == null) + throw new IllegalAccessError("left cannot be NUll"); + if (right == null) + throw new IllegalAccessError("right cannot be NUll"); + this.key = key; - this.value = value; + this.left = left; + this.right = right; } @Override @@ -49,12 +54,17 @@ public abstract class AbstractIntervalTree, TValue @Override public TValue getValue() { - return value; + return left.value; } @Override public TValue setValue(TValue value) { - throw new NotImplementedException(); + TValue old = left.value; + + // Set both end points + left.value = value; + right.value = value; + return old; } } @@ -83,8 +93,8 @@ public abstract class AbstractIntervalTree, TValue * @param lowerBound - lowest value to remove. * @param upperBound - highest value to remove. */ - public void remove(TKey lowerBound, TKey upperBound) { - remove(lowerBound, upperBound, false); + public Set remove(TKey lowerBound, TKey upperBound) { + return remove(lowerBound, upperBound, false); } /** @@ -93,13 +103,13 @@ public abstract class AbstractIntervalTree, TValue * @param upperBound - highest value to remove. * @param preserveOutside - whether or not to preserve the intervals that are partially outside. */ - public void remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) { + public Set remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) { checkBounds(lowerBound, upperBound); NavigableMap range = bounds.subMap(lowerBound, true, upperBound, true); boolean emptyRange = range.isEmpty(); - TKey first = emptyRange ? range.firstKey() : null; - TKey last = emptyRange ? range.lastKey() : null; + TKey first = !emptyRange ? range.firstKey() : null; + TKey last = !emptyRange ? range.lastKey() : null; Set resized = new HashSet(); Set removed = new HashSet(); @@ -136,6 +146,7 @@ public abstract class AbstractIntervalTree, TValue // Remove the range as well range.clear(); + return removed; } // Helper @@ -154,7 +165,8 @@ public abstract class AbstractIntervalTree, TValue if (endPoint != null) { endPoint.state = State.BOTH; } else { - endPoint = bounds.put(key, new EndPoint(state, value)); + endPoint = new EndPoint(state, value); + bounds.put(key, endPoint); } return endPoint; } @@ -184,11 +196,11 @@ public abstract class AbstractIntervalTree, TValue private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) { // OK. Add the end points now if (value != null) { - addEndPoint(lowerBound, value, State.OPEN); - addEndPoint(upperBound, value, State.CLOSE); + EndPoint left = addEndPoint(lowerBound, value, State.OPEN); + EndPoint right = addEndPoint(upperBound, value, State.CLOSE); Range range = Ranges.closed(lowerBound, upperBound); - return new Entry(range, value); + return new Entry(range, left, right); } else { return null; } @@ -248,11 +260,12 @@ public abstract class AbstractIntervalTree, TValue for (Map.Entry entry : bounds.entrySet()) { switch (entry.getValue().state) { case BOTH: - destination.add(new Entry(Ranges.singleton(entry.getKey()), entry.getValue().value)); + EndPoint point = entry.getValue(); + destination.add(new Entry(Ranges.singleton(entry.getKey()), point, point)); break; case CLOSE: Range range = Ranges.closed(last.getKey(), entry.getKey()); - destination.add(new Entry(range, entry.getValue().value)); + destination.add(new Entry(range, last.getValue(), entry.getValue())); break; case OPEN: // We don't know the full range yet @@ -271,7 +284,7 @@ public abstract class AbstractIntervalTree, TValue public void putAll(AbstractIntervalTree other) { // Naively copy every range. for (Entry entry : other.entrySet()) { - put(entry.key.lowerEndpoint(), entry.key.upperEndpoint(), entry.value); + put(entry.key.lowerEndpoint(), entry.key.upperEndpoint(), entry.getValue()); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java index 04dd6af9..79546e56 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java @@ -137,6 +137,20 @@ public class ListeningWhitelist { return false; } + /** + * Determine if the given whitelist is empty or not. + * @param whitelist - the whitelist to test. + * @return TRUE if the whitelist is empty, FALSE otherwise. + */ + public static boolean isEmpty(ListeningWhitelist whitelist) { + if (whitelist == EMPTY_WHITELIST) + return true; + else if (whitelist == null) + return true; + else + return whitelist.getWhitelist().isEmpty(); + } + @Override public boolean equals(final Object obj){ if(obj instanceof ListeningWhitelist){ @@ -157,5 +171,4 @@ public class ListeningWhitelist { .add("priority", priority) .add("packets", whitelist).toString(); } - } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java index 14eee6d5..7c2e18fc 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java @@ -212,43 +212,14 @@ public class Updater * @param permission * Permission needed to read the output of the update process. */ - public Updater(Plugin plugin, String slug, File file, String permission) + public Updater(Plugin plugin, Logger logger, String slug, File file, String permission) { this.plugin = plugin; this.file = file; this.slug = slug; - - // Prevent issues with older versions of Bukkit - try { - logger = plugin.getLogger(); - logger.getLevel(); - } catch (Throwable e) { - logger = Logger.getLogger("Minecraft"); - } - - broadcastUsers(plugin.getServer(), permission); + this.logger = logger; } - private void broadcastUsers(final Server server, final String permission) { - // Broadcast information to every user too - logger.addHandler(new Handler() { - @Override - public void publish(LogRecord record) { - server.broadcast(record.getMessage(), permission); - } - - @Override - public void flush() { - // Not needed. - } - - @Override - public void close() throws SecurityException { - // Do nothing. - } - }); - } - /** * Update the plugin. * diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java new file mode 100644 index 00000000..f440cc3b --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java @@ -0,0 +1,172 @@ +package com.comphenix.protocol.reflect; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Set; + +import com.google.common.primitives.Primitives; + +/** + * Used to print the content of an arbitrary class. + * + * @author Kristian + */ +public class PrettyPrinter { + + /** + * How far we will recurse. + */ + public final static int RECURSE_DEPTH = 3; + + /** + * Print the content of an object. + * @param object - the object to serialize. + * @param stop - superclass that will stop the process. + * @return String representation of the class. + * @throws IllegalAccessException + */ + public static String printObject(Object object, Class start, Class stop) throws IllegalAccessException { + return printObject(object, start, stop, RECURSE_DEPTH); + } + + /** + * Print the content of an object. + * @param object - the object to serialize. + * @param stop - superclass that will stop the process. + * @param depth - how far in the hierachy until we stop. + * @return String representation of the class. + * @throws IllegalAccessException + */ + public static String printObject(Object object, Class start, Class stop, int hierachyDepth) throws IllegalAccessException { + StringBuilder output = new StringBuilder(); + Set previous = new HashSet(); + + // Start and stop + output.append("{ "); + printObject(output, object, start, stop, previous, hierachyDepth); + output.append(" }"); + + return output.toString(); + } + + @SuppressWarnings("rawtypes") + private static void printIterables(StringBuilder output, Iterable iterable, Class current, Class stop, + Set previous, int hierachyIndex) throws IllegalAccessException { + + boolean first = true; + output.append("("); + + for (Object value : iterable) { + if (first) + first = false; + else + output.append(", "); + + // Handle exceptions + if (value != null) + printValue(output, value, value.getClass(), stop, previous, hierachyIndex - 1); + else + output.append("NULL"); + } + + output.append(")"); + } + + private static void printArray(StringBuilder output, Object array, Class current, Class stop, + Set previous, int hierachyIndex) throws IllegalAccessException { + + Class component = current.getComponentType(); + boolean first = true; + + if (!component.isArray()) + output.append(component.getName()); + output.append("["); + + for (int i = 0; i < Array.getLength(array); i++) { + if (first) + first = false; + else + output.append(", "); + + // Handle exceptions + try { + printValue(output, Array.get(array, i), component, stop, previous, hierachyIndex - 1); + } catch (ArrayIndexOutOfBoundsException e) { + e.printStackTrace(); + break; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + break; + } + } + + output.append("]"); + } + + // Internal recursion method + private static void printObject(StringBuilder output, Object object, Class current, Class stop, + Set previous, int hierachyIndex) throws IllegalAccessException { + // Trickery + boolean first = true; + + // See if we're supposed to skip this class + if (current == Object.class || (stop != null && current.equals(stop))) { + return; + } + + // Don't iterate twice + previous.add(object); + + // Hard coded limit + if (hierachyIndex < 0) { + output.append("..."); + return; + } + + for (Field field : current.getDeclaredFields()) { + int mod = field.getModifiers(); + + // Skip a good number of the fields + if (!Modifier.isTransient(mod) && !Modifier.isStatic(mod)) { + Class type = field.getType(); + Object value = FieldUtils.readField(field, object, true); + + if (first) + first = false; + else + output.append(", "); + + output.append(field.getName()); + output.append(" = "); + printValue(output, value, type, stop, previous, hierachyIndex - 1); + } + } + + // Recurse + printObject(output, object, current.getSuperclass(), stop, previous, hierachyIndex); + } + + @SuppressWarnings("rawtypes") + private static void printValue(StringBuilder output, Object value, Class type, + Class stop, Set previous, int hierachyIndex) throws IllegalAccessException { + // Just print primitive types + if (value == null) { + output.append("NULL"); + } else if (type.isPrimitive() || Primitives.isWrapperType(type) || type == String.class || hierachyIndex <= 0) { + output.append(value); + } else if (type.isArray()) { + printArray(output, value, type, stop, previous, hierachyIndex); + } else if (Iterable.class.isAssignableFrom(type)) { + printIterables(output, (Iterable) value, type, stop, previous, hierachyIndex); + } else if (ClassLoader.class.isAssignableFrom(type) || previous.contains(value)) { + // Don't print previous objects + output.append(value); + } else { + output.append("{ "); + printObject(output, value, value.getClass(), stop, previous, hierachyIndex); + output.append(" }"); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java new file mode 100644 index 00000000..fdf6bc21 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java @@ -0,0 +1,85 @@ +package com.comphenix.protocol.utility; + +import java.lang.reflect.InvocationTargetException; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.injector.PacketConstructor; +import com.comphenix.protocol.reflect.FieldAccessException; + +/** + * Utility methods for sending chat messages. + * + * @author Kristian + */ +public class ChatExtensions { + + // Used to sent chat messages + private PacketConstructor chatConstructor; + private ProtocolManager manager; + + public ChatExtensions(ProtocolManager manager) { + this.manager = manager; + } + + /** + * Send a message without invoking the packet listeners. + * @param player - the player to send it to. + * @param message - the message to send. + * @return TRUE if the message was sent successfully, FALSE otherwise. + * @throws InvocationTargetException If we were unable to send the message. + */ + public void sendMessageSilently(CommandSender receiver, String message) throws InvocationTargetException { + if (receiver == null) + throw new IllegalArgumentException("receiver cannot be NULL."); + if (message == null) + throw new IllegalArgumentException("message cannot be NULL."); + + // Handle the player case by manually sending packets + if (receiver instanceof Player) { + sendMessageSilently((Player) receiver, message); + } else { + receiver.sendMessage(message); + } + } + + /** + * Send a message without invoking the packet listeners. + * @param player - the player to send it to. + * @param message - the message to send. + * @return TRUE if the message was sent successfully, FALSE otherwise. + * @throws InvocationTargetException If we were unable to send the message. + */ + private void sendMessageSilently(Player player, String message) throws InvocationTargetException { + if (chatConstructor == null) + chatConstructor = manager.createPacketConstructor(Packets.Server.CHAT, message); + + try { + manager.sendServerPacket(player, chatConstructor.createPacket(message), false); + } catch (FieldAccessException e) { + throw new InvocationTargetException(e); + } + } + + /** + * Broadcast a message without invoking any packet listeners. + * @param message - message to send. + * @param permission - permission required to receieve the message. NULL to target everyone. + * @throws InvocationTargetException If we were unable to send the message. + */ + public void broadcastMessageSilently(String message, String permission) throws InvocationTargetException { + if (message == null) + throw new IllegalArgumentException("message cannot be NULL."); + + // Send this message to every online player + for (Player player : Bukkit.getServer().getOnlinePlayers()) { + if (permission == null || player.hasPermission(permission)) { + sendMessageSilently(player, message); + } + } + } +} diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 8fd825ea..6088ff69 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -15,7 +15,7 @@ commands: permission-message: You don't have packet: description: Add or remove a simple packet listener. - usage: / add|remove client|server|both [ID start] [ID stop] [detailed] + usage: / add|remove client|server [ID start]-[ID stop] [detailed] permission: experiencemod.admin permission-message: You don't have From 263d085590fef8f6f730123799647712d5458ced Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 06:48:58 +0100 Subject: [PATCH 40/54] Always create vanilla packets in the packet constructor. --- .../java/com/comphenix/protocol/injector/PacketConstructor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java index 7c2917f7..7c1f85e6 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketConstructor.java @@ -116,7 +116,7 @@ public class PacketConstructor { } } - Class packetType = MinecraftRegistry.getPacketClassFromID(id); + Class packetType = MinecraftRegistry.getPacketClassFromID(id, true); if (packetType == null) throw new IllegalArgumentException("Could not find a packet by the id " + id); From 27104c73503d74cc40413b277328bef1f891911b Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 06:51:37 +0100 Subject: [PATCH 41/54] Accept zero as a packet ID in the packet command. --- .../src/main/java/com/comphenix/protocol/CommandPacket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index c0a85c49..c185d42e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -286,7 +286,7 @@ class CommandPacket implements CommandExecutor { start = i; } } else { - if (start > 0) { + if (start >= 0) { result.add(Ranges.closed(start, i - 1)); start = -1; } From 72bfa3da9b299d9088766aff32a39679b3fc31bb Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 06:54:31 +0100 Subject: [PATCH 42/54] Adding the names sub-command. --- .../java/com/comphenix/protocol/CommandPacket.java | 14 ++++++++++++-- ProtocolLib/src/main/resources/plugin.yml | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index c185d42e..9c735a92 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -47,7 +47,7 @@ class CommandPacket implements CommandExecutor { } private enum SubCommand { - ADD, REMOVE; + ADD, REMOVE, NAMES; } /** @@ -202,7 +202,15 @@ class CommandPacket implements CommandExecutor { } sendMessageSilently(sender, ChatColor.BLUE + "Fully removed " + count + " listeners."); - } + } else if (subCommand == SubCommand.NAMES) { + + // Print the equivalent name of every given ID + for (Range range : ranges) { + for (int id : range.asSet(DiscreteDomains.integers())) { + sendMessageSilently(sender, ChatColor.BLUE + "" + id + ": " + Packets.getDeclaredName(id)); + } + } + } } catch (NumberFormatException e) { sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage()); @@ -489,6 +497,8 @@ class CommandPacket implements CommandExecutor { return SubCommand.ADD; else if ("remove".startsWith(text)) return SubCommand.REMOVE; + else if ("names".startsWith(text)) + return SubCommand.NAMES; else throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove."); } diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 6088ff69..5089f4d8 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -15,7 +15,7 @@ commands: permission-message: You don't have packet: description: Add or remove a simple packet listener. - usage: / add|remove client|server [ID start]-[ID stop] [detailed] + usage: / add|remove|names client|server [ID start]-[ID stop] [detailed] permission: experiencemod.admin permission-message: You don't have From f2e078ce6a1496410b7b51fad835ae72cc393182 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 06:59:47 +0100 Subject: [PATCH 43/54] Print strings with quotation marks. --- .../java/com/comphenix/protocol/reflect/PrettyPrinter.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java index f440cc3b..51f22558 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/PrettyPrinter.java @@ -154,15 +154,17 @@ public class PrettyPrinter { // Just print primitive types if (value == null) { output.append("NULL"); - } else if (type.isPrimitive() || Primitives.isWrapperType(type) || type == String.class || hierachyIndex <= 0) { + } else if (type.isPrimitive() || Primitives.isWrapperType(type)) { output.append(value); + } else if (type == String.class || hierachyIndex <= 0) { + output.append("\"" + value + "\""); } else if (type.isArray()) { printArray(output, value, type, stop, previous, hierachyIndex); } else if (Iterable.class.isAssignableFrom(type)) { printIterables(output, (Iterable) value, type, stop, previous, hierachyIndex); } else if (ClassLoader.class.isAssignableFrom(type) || previous.contains(value)) { // Don't print previous objects - output.append(value); + output.append("\"" + value + "\""); } else { output.append("{ "); printObject(output, value, value.getClass(), stop, previous, hierachyIndex); From da0d55fceafd8becfb52fd2dce61a227df7bad6a Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 07:16:20 +0100 Subject: [PATCH 44/54] Improve the names command. --- .../com/comphenix/protocol/CommandPacket.java | 30 ++++++++++++++++++- .../comphenix/protocol/metrics/Updater.java | 3 -- .../comphenix/protocol/reflect/IntEnum.java | 3 +- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 9c735a92..a833f57f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -204,10 +204,14 @@ class CommandPacket implements CommandExecutor { sendMessageSilently(sender, ChatColor.BLUE + "Fully removed " + count + " listeners."); } else if (subCommand == SubCommand.NAMES) { + Set named = getNamedPackets(side); + // Print the equivalent name of every given ID for (Range range : ranges) { for (int id : range.asSet(DiscreteDomains.integers())) { - sendMessageSilently(sender, ChatColor.BLUE + "" + id + ": " + Packets.getDeclaredName(id)); + if (named.contains(id)) { + sendMessageSilently(sender, ChatColor.BLUE + "" + id + ": " + Packets.getDeclaredName(id)); + } } } } @@ -368,6 +372,30 @@ class CommandPacket implements CommandExecutor { throw new IllegalArgumentException("Illegal side: " + side); } + private Set getNamedPackets(ConnectionSide side) { + + Set valids = null; + Set result = null; + + try { + valids = getValidPackets(side); + } catch (FieldAccessException e) { + valids = Ranges.closed(0, 255).asSet(DiscreteDomains.integers()); + } + + // 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); + + // Remove invalid packets + result.retainAll(valids); + return result; + } + public DetailedPacketListener createPacketListener(final ConnectionSide side, int idStart, int idStop, final boolean detailed) { Set range = Ranges.closed(idStart, idStop).asSet(DiscreteDomains.integers()); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java index 7c2e18fc..f3f18939 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java @@ -12,8 +12,6 @@ import java.net.URLConnection; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; -import java.util.logging.Handler; -import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -22,7 +20,6 @@ import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.XMLEvent; -import org.bukkit.Server; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.Plugin; diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/IntEnum.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/IntEnum.java index 9a8f3b40..22ac99ec 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/IntEnum.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/IntEnum.java @@ -18,6 +18,7 @@ package com.comphenix.protocol.reflect; import java.lang.reflect.Field; +import java.util.HashSet; import java.util.Set; import com.google.common.collect.BiMap; @@ -100,6 +101,6 @@ public class IntEnum { * @return Enumeration of every value. */ public Set values() { - return members.keySet(); + return new HashSet(members.keySet()); } } From 188f03b39e6bff8fbd6f8ef52bbaf7666582642e Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 07:32:14 +0100 Subject: [PATCH 45/54] Fixed the columns slightly. Still needs tweaking. --- .../com/comphenix/protocol/CommandPacket.java | 37 ++++++++++++++++++- .../protocol/utility/ChatExtensions.java | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index a833f57f..bee9c430 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -28,6 +28,7 @@ import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.PrettyPrinter; import com.comphenix.protocol.utility.ChatExtensions; +import com.google.common.base.Strings; import com.google.common.collect.DiscreteDomains; import com.google.common.collect.Range; import com.google.common.collect.Ranges; @@ -54,6 +55,11 @@ class CommandPacket implements CommandExecutor { * Name of this command. */ public static final String NAME = "packet"; + + /** + * Default width of the chat window. + */ + private static final int CHAT_WIDTH = 90; private Plugin plugin; private Logger logger; @@ -127,7 +133,7 @@ class CommandPacket implements CommandExecutor { /** * Send a message without invoking the packet listeners. - * @param player - the player to send it to. + * @param receiver - the player to send it to. * @param message - the message to send. * @return TRUE if the message was sent successfully, FALSE otherwise. */ @@ -205,15 +211,24 @@ class CommandPacket implements CommandExecutor { } else if (subCommand == SubCommand.NAMES) { Set named = getNamedPackets(side); + List messages = new ArrayList(); // Print the equivalent name of every given ID for (Range range : ranges) { for (int id : range.asSet(DiscreteDomains.integers())) { if (named.contains(id)) { - sendMessageSilently(sender, ChatColor.BLUE + "" + id + ": " + Packets.getDeclaredName(id)); + messages.add(ChatColor.BLUE + "" + id + ": " + Packets.getDeclaredName(id)); } } } + + // Convert to two rows + messages = getMessagesInRows(messages, 2, CHAT_WIDTH); + + // Print that + for (String message : messages) { + sendMessageSilently(sender, message); + } } } catch (NumberFormatException e) { @@ -228,6 +243,24 @@ class CommandPacket implements CommandExecutor { return false; } + private List getMessagesInRows(List messages, int rows, int totalWidth) { + List output = new ArrayList(); + int columnWidth = totalWidth / rows; + + for (int i = 0; i < messages.size(); i++) { + int mapped = i / rows; + + // Either create a new row, or add to an existing row + if (mapped < output.size()) { + output.set(mapped, Strings.padEnd(output.get(mapped), columnWidth, ' ') + messages.get(i)); + } else { + output.add(messages.get(i)); + } + } + + return output; + } + /** * Parse ranges from an array of tokens. * @param args - array of tokens. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java index fdf6bc21..ff7621a5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/ChatExtensions.java @@ -28,7 +28,7 @@ public class ChatExtensions { /** * Send a message without invoking the packet listeners. - * @param player - the player to send it to. + * @param receiver - the receiver. * @param message - the message to send. * @return TRUE if the message was sent successfully, FALSE otherwise. * @throws InvocationTargetException If we were unable to send the message. From 626dea07fa10d647546872856f2272f6b66328ff Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 08:51:03 +0100 Subject: [PATCH 46/54] Move range parser out of CommandPacket. Make a common command base. --- .../com/comphenix/protocol/CommandBase.java | 51 ++++ .../com/comphenix/protocol/CommandPacket.java | 269 +++++------------- .../comphenix/protocol/CommandProtocol.java | 38 +-- .../com/comphenix/protocol/RangeParser.java | 142 +++++++++ 4 files changed, 284 insertions(+), 216 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/RangeParser.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java new file mode 100644 index 00000000..be72d60f --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandBase.java @@ -0,0 +1,51 @@ +package com.comphenix.protocol; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +/** + * Base class for all our commands. + * + * @author Kristian + */ +abstract class CommandBase implements CommandExecutor { + + public static final String PERMISSION_ADMIN = "protocol.admin"; + + private String permission; + private String name; + private int minimumArgumentCount; + + public CommandBase(String permission, String name) { + this(permission, name, 0); + } + + public CommandBase(String permission, String name, int minimumArgumentCount) { + this.name = name; + this.permission = permission; + this.minimumArgumentCount = minimumArgumentCount; + } + + @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."); + return true; + } + + // Check argument length + if (args != null && args.length >= minimumArgumentCount) { + return handleCommand(sender, args); + } else { + return false; + } + } + + 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 bee9c430..46a2715d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -12,8 +12,6 @@ import net.minecraft.server.Packet; import net.sf.cglib.proxy.Factory; import org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; @@ -38,7 +36,8 @@ import com.google.common.collect.Ranges; * * @author Kristian */ -class CommandPacket implements CommandExecutor { +class CommandPacket extends CommandBase { + private interface DetailedPacketListener extends PacketListener { /** * Determine whether or not the given packet listener is detailed or not. @@ -73,6 +72,7 @@ class CommandPacket implements CommandExecutor { private AbstractIntervalTree serverListeners = createTree(ConnectionSide.SERVER_SIDE); public CommandPacket(Plugin plugin, Logger logger, ErrorReporter reporter, ProtocolManager manager) { + super(CommandBase.PERMISSION_ADMIN, NAME, 1); this.plugin = plugin; this.logger = logger; this.reporter = reporter; @@ -163,86 +163,87 @@ class CommandPacket implements CommandExecutor { Usage: / add|remove client|server|both [ID start] [ID stop] [detailed] */ @Override - public 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; - - // We need at least one argument - if (args != null && args.length > 0) { - try { - SubCommand subCommand = parseCommand(args, 0); - ConnectionSide side = parseSide(args, 1, ConnectionSide.BOTH); - - Integer lastIndex = args.length - 1; - Boolean detailed = parseBoolean(args, lastIndex); + protected boolean handleCommand(CommandSender sender, String[] args) { + try { + SubCommand subCommand = parseCommand(args, 0); + ConnectionSide side = parseSide(args, 1, ConnectionSide.BOTH); + + Integer lastIndex = args.length - 1; + Boolean detailed = parseBoolean(args, lastIndex); - // See if the last element is a boolean - if (detailed == null) { - detailed = false; - } else { - lastIndex--; - } - - // Make sure the packet IDs are valid - List> ranges = getRanges(args, 2, lastIndex, Ranges.closed(0, 255)); - - if (ranges.isEmpty()) { - // Use every packet ID - ranges.add(Ranges.closed(0, 255)); - } - - // Perform command - if (subCommand == SubCommand.ADD) { - for (Range range : ranges) { - DetailedPacketListener listener = addPacketListeners(side, range.lowerEndpoint(), range.upperEndpoint(), detailed); - sendMessageSilently(sender, ChatColor.BLUE + "Added listener " + getWhitelistInfo(listener)); - } - - } else if (subCommand == SubCommand.REMOVE) { - int count = 0; - - // Remove each packet listener - for (Range range : ranges) { - count += removePacketListeners(side, range.lowerEndpoint(), range.upperEndpoint(), detailed).size(); - } - - sendMessageSilently(sender, ChatColor.BLUE + "Fully removed " + count + " listeners."); - } else if (subCommand == SubCommand.NAMES) { - - Set named = getNamedPackets(side); - List messages = new ArrayList(); - - // Print the equivalent name of every given ID - for (Range range : ranges) { - for (int id : range.asSet(DiscreteDomains.integers())) { - if (named.contains(id)) { - messages.add(ChatColor.BLUE + "" + id + ": " + Packets.getDeclaredName(id)); - } - } - } - - // Convert to two rows - messages = getMessagesInRows(messages, 2, CHAT_WIDTH); - - // Print that - for (String message : messages) { - sendMessageSilently(sender, message); - } - } - - } catch (NumberFormatException e) { - sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage()); - } catch (IllegalArgumentException e) { - sendMessageSilently(sender, ChatColor.RED + e.getMessage()); + // See if the last element is a boolean + if (detailed == null) { + detailed = false; + } else { + lastIndex--; } - return true; + // Make sure the packet IDs are valid + List> ranges = RangeParser.getRanges(args, 2, lastIndex, Ranges.closed(0, 255)); + + if (ranges.isEmpty()) { + // Use every packet ID + ranges.add(Ranges.closed(0, 255)); + } + + // Perform commands + if (subCommand == SubCommand.ADD) { + executeAddCommand(sender, side, detailed, ranges); + } else if (subCommand == SubCommand.REMOVE) { + executeRemoveCommand(sender, side, detailed, ranges); + } else if (subCommand == SubCommand.NAMES) { + executeNamesCommand(sender, side, ranges); + } + + } catch (NumberFormatException e) { + sendMessageSilently(sender, ChatColor.RED + "Cannot parse number: " + e.getMessage()); + } catch (IllegalArgumentException e) { + sendMessageSilently(sender, ChatColor.RED + e.getMessage()); } - return false; + return true; + } + + private void executeAddCommand(CommandSender sender, ConnectionSide side, Boolean detailed, List> ranges) { + for (Range range : ranges) { + DetailedPacketListener listener = addPacketListeners(side, range.lowerEndpoint(), range.upperEndpoint(), detailed); + sendMessageSilently(sender, ChatColor.BLUE + "Added listener " + getWhitelistInfo(listener)); + } } + private void executeRemoveCommand(CommandSender sender, ConnectionSide side, Boolean detailed, List> ranges) { + int count = 0; + + // Remove each packet listener + for (Range range : ranges) { + count += removePacketListeners(side, range.lowerEndpoint(), range.upperEndpoint(), detailed).size(); + } + + sendMessageSilently(sender, ChatColor.BLUE + "Fully removed " + count + " listeners."); + } + + private void executeNamesCommand(CommandSender sender, ConnectionSide side, List> ranges) { + Set named = getNamedPackets(side); + List messages = new ArrayList(); + + // Print the equivalent name of every given ID + for (Range range : ranges) { + for (int id : range.asSet(DiscreteDomains.integers())) { + if (named.contains(id)) { + messages.add(ChatColor.BLUE + "" + id + ": " + Packets.getDeclaredName(id)); + } + } + } + + // Convert to two rows + messages = getMessagesInRows(messages, 2, CHAT_WIDTH); + + // Print that + for (String message : messages) { + sendMessageSilently(sender, message); + } + } + private List getMessagesInRows(List messages, int rows, int totalWidth) { List output = new ArrayList(); int columnWidth = totalWidth / rows; @@ -260,123 +261,7 @@ class CommandPacket implements CommandExecutor { return output; } - - /** - * Parse ranges from an array of tokens. - * @param args - array of tokens. - * @param offset - beginning offset. - * @param legalRange - range of legal values. - * @return The parsed ranges. - */ - public static List> getRanges(String[] args, int offset, int lastIndex, Range legalRange) { - List tokens = tokenizeInput(args, offset, lastIndex); - List> ranges = new ArrayList>(); - for (int i = 0; i < tokens.size(); i++) { - Range range; - String current = tokens.get(i); - String next = i + 1 < tokens.size() ? tokens.get(i + 1) : null; - - // Yoda equality is done for null-safety - if ("-".equals(current)) { - throw new IllegalArgumentException("A hyphen must appear between two numbers."); - } else if ("-".equals(next)) { - if (i + 2 >= tokens.size()) - throw new IllegalArgumentException("Cannot form a range without a upper limit."); - - // This is a proper range - range = Ranges.closed(Integer.parseInt(current), Integer.parseInt(tokens.get(i + 2))); - ranges.add(range); - - // Skip the two next tokens - i += 2; - - } else { - // Just a single number - range = Ranges.singleton(Integer.parseInt(current)); - ranges.add(range); - } - - // Validate ranges - if (!legalRange.encloses(range)) { - throw new IllegalArgumentException(range + " is not in the range " + range.toString()); - } - } - - return simplify(ranges, legalRange.upperEndpoint()); - } - - /** - * Simplify a list of ranges by assuming a maximum value. - * @param ranges - the list of ranges to simplify. - * @param maximum - the maximum value (minimum value is always 0). - * @return A simplified list of ranges. - */ - private static List> simplify(List> ranges, int maximum) { - List> result = new ArrayList>(); - boolean[] set = new boolean[maximum + 1]; - int start = -1; - - // Set every ID - for (Range range : ranges) { - for (int id : range.asSet(DiscreteDomains.integers())) { - set[id] = true; - } - } - - // Generate ranges from this set - for (int i = 0; i <= set.length; i++) { - if (i < set.length && set[i]) { - if (start < 0) { - start = i; - } - } else { - if (start >= 0) { - result.add(Ranges.closed(start, i - 1)); - start = -1; - } - } - } - - return result; - } - - private static List tokenizeInput(String[] args, int offset, int lastIndex) { - List tokens = new ArrayList(); - - // Tokenize the input - for (int i = offset; i <= lastIndex; i++) { - String text = args[i]; - StringBuilder number = new StringBuilder(); - - for (int j = 0; j < text.length(); j++) { - char current = text.charAt(j); - - if (Character.isDigit(current)) { - number.append(current); - } else if (Character.isWhitespace(current)) { - // That's ok - } else if (current == '-') { - // Add the number token first - if (number.length() > 0) { - tokens.add(number.toString()); - number.setLength(0); - } - - tokens.add(Character.toString(current)); - } else { - throw new IllegalArgumentException("Illegal character '" + current + "' found."); - } - } - - // Add the number token, if it hasn't already - if (number.length() > 0) - tokens.add(number.toString()); - } - - return tokens; - } - /** * Retrieve whitelist information about a given listener. * @param listener - the given listener. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java index e4d504d7..9f5a0eb0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java @@ -1,8 +1,6 @@ package com.comphenix.protocol; import org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; @@ -15,7 +13,7 @@ import com.comphenix.protocol.metrics.Updater.UpdateType; * * @author Kristian */ -class CommandProtocol implements CommandExecutor { +class CommandProtocol extends CommandBase { /** * Name of this command. */ @@ -25,33 +23,25 @@ class CommandProtocol implements CommandExecutor { private Updater updater; public CommandProtocol(Plugin plugin, Updater updater) { + super(CommandBase.PERMISSION_ADMIN, NAME, 1); this.plugin = plugin; this.updater = updater; } @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - // Make sure we're dealing with the correct command - if (!command.getName().equalsIgnoreCase(NAME)) + protected boolean handleCommand(CommandSender sender, String[] args) { + String subCommand = args[0]; + + // Only return TRUE if we executed the correct command + if (subCommand.equalsIgnoreCase("config")) + reloadConfiguration(sender); + else if (subCommand.equalsIgnoreCase("check")) + checkVersion(sender); + else if (subCommand.equalsIgnoreCase("update")) + updateVersion(sender); + else return false; - - // We need one argument (the sub-command) - if (args != null && args.length == 1) { - String subCommand = args[0]; - - // Only return TRUE if we executed the correct command - if (subCommand.equalsIgnoreCase("config")) - reloadConfiguration(sender); - else if (subCommand.equalsIgnoreCase("check")) - checkVersion(sender); - else if (subCommand.equalsIgnoreCase("update")) - updateVersion(sender); - else - return false; - return true; - } - - return false; + return true; } public void checkVersion(final CommandSender sender) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/RangeParser.java b/ProtocolLib/src/main/java/com/comphenix/protocol/RangeParser.java new file mode 100644 index 00000000..d396be43 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/RangeParser.java @@ -0,0 +1,142 @@ +package com.comphenix.protocol; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.DiscreteDomains; +import com.google.common.collect.Range; +import com.google.common.collect.Ranges; + +/** + * Used to parse ranges in CommandPacket. + * + * @author Kristian + */ +class RangeParser { + /** + * Parse a range from a given text. + * @param text - the text. + * @param legalRange - range of legal values. + * @return The parsed ranges. + */ + public static List> getRanges(String text, Range legalRange) { + return getRanges(new String[] { text }, 0, 0, legalRange); + } + + /** + * Parse ranges from an array of elements. + * @param args - array of elements. + * @param offset - beginning offset. + * @param lastIndex - the last index of the array to read. + * @param legalRange - range of legal values. + * @return The parsed ranges. + */ + public static List> getRanges(String[] args, int offset, int lastIndex, Range legalRange) { + List tokens = tokenizeInput(args, offset, lastIndex); + List> ranges = new ArrayList>(); + + for (int i = 0; i < tokens.size(); i++) { + Range range; + String current = tokens.get(i); + String next = i + 1 < tokens.size() ? tokens.get(i + 1) : null; + + // Yoda equality is done for null-safety + if ("-".equals(current)) { + throw new IllegalArgumentException("A hyphen must appear between two numbers."); + } else if ("-".equals(next)) { + if (i + 2 >= tokens.size()) + throw new IllegalArgumentException("Cannot form a range without a upper limit."); + + // This is a proper range + range = Ranges.closed(Integer.parseInt(current), Integer.parseInt(tokens.get(i + 2))); + ranges.add(range); + + // Skip the two next tokens + i += 2; + + } else { + // Just a single number + range = Ranges.singleton(Integer.parseInt(current)); + ranges.add(range); + } + + // Validate ranges + if (!legalRange.encloses(range)) { + throw new IllegalArgumentException(range + " is not in the range " + range.toString()); + } + } + + return simplify(ranges, legalRange.upperEndpoint()); + } + + /** + * Simplify a list of ranges by assuming a maximum value. + * @param ranges - the list of ranges to simplify. + * @param maximum - the maximum value (minimum value is always 0). + * @return A simplified list of ranges. + */ + private static List> simplify(List> ranges, int maximum) { + List> result = new ArrayList>(); + boolean[] set = new boolean[maximum + 1]; + int start = -1; + + // Set every ID + for (Range range : ranges) { + for (int id : range.asSet(DiscreteDomains.integers())) { + set[id] = true; + } + } + + // Generate ranges from this set + for (int i = 0; i <= set.length; i++) { + if (i < set.length && set[i]) { + if (start < 0) { + start = i; + } + } else { + if (start >= 0) { + result.add(Ranges.closed(start, i - 1)); + start = -1; + } + } + } + + return result; + } + + private static List tokenizeInput(String[] args, int offset, int lastIndex) { + List tokens = new ArrayList(); + + // Tokenize the input + for (int i = offset; i <= lastIndex; i++) { + String text = args[i]; + StringBuilder number = new StringBuilder(); + + for (int j = 0; j < text.length(); j++) { + char current = text.charAt(j); + + if (Character.isDigit(current)) { + number.append(current); + } else if (Character.isWhitespace(current)) { + // That's ok + } else if (current == '-') { + // Add the number token first + if (number.length() > 0) { + tokens.add(number.toString()); + number.setLength(0); + } + + tokens.add(Character.toString(current)); + } else { + throw new IllegalArgumentException("Illegal character '" + current + "' found."); + } + } + + // Add the number token, if it hasn't already + if (number.length() > 0) + tokens.add(number.toString()); + } + + return tokens; + } +} From a9fe6afa016245884ef68dc8b32ed069f4c322b5 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 3 Nov 2012 09:27:20 +0100 Subject: [PATCH 47/54] Divide names output into pages instead. --- .../com/comphenix/protocol/CommandPacket.java | 88 ++++++++++++------- 1 file changed, 58 insertions(+), 30 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 46a2715d..8bcc9f4e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -4,7 +4,9 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -13,6 +15,7 @@ import net.sf.cglib.proxy.Factory; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; import com.comphenix.protocol.concurrency.AbstractIntervalTree; @@ -26,7 +29,6 @@ import com.comphenix.protocol.injector.GamePhase; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.PrettyPrinter; import com.comphenix.protocol.utility.ChatExtensions; -import com.google.common.base.Strings; import com.google.common.collect.DiscreteDomains; import com.google.common.collect.Range; import com.google.common.collect.Ranges; @@ -47,7 +49,7 @@ class CommandPacket extends CommandBase { } private enum SubCommand { - ADD, REMOVE, NAMES; + ADD, REMOVE, NAMES, PAGE; } /** @@ -56,9 +58,9 @@ class CommandPacket extends CommandBase { public static final String NAME = "packet"; /** - * Default width of the chat window. + * Number of lines per page. */ - private static final int CHAT_WIDTH = 90; + public static final int PAGE_LINE_COUNT = 9; private Plugin plugin; private Logger logger; @@ -67,12 +69,15 @@ class CommandPacket extends CommandBase { private ChatExtensions chatter; + // Paged message + private Map> pagedMessage = new WeakHashMap>(); + // Registered packet listeners 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, 1); + super(CommandBase.PERMISSION_ADMIN, NAME, 2); this.plugin = plugin; this.logger = logger; this.reporter = reporter; @@ -158,6 +163,29 @@ class CommandPacket extends CommandBase { } } + private void printPage(CommandSender sender, int pageIndex) { + List paged = pagedMessage.get(sender); + + // Make sure the player has any pages + if (paged != null) { + int lastPage = ((paged.size() - 1) / PAGE_LINE_COUNT) + 1; + + for (int i = PAGE_LINE_COUNT * (pageIndex - 1); i < PAGE_LINE_COUNT * pageIndex; i++) { + if (i < paged.size()) { + sendMessageSilently(sender, " " + paged.get(i)); + } + } + + // More data? + if (pageIndex < lastPage) { + sendMessageSilently(sender, "Send /packet page " + (pageIndex + 1) + " for the next page."); + } + + } else { + sendMessageSilently(sender, ChatColor.RED + "No pages found."); + } + } + /* * Description: Adds or removes a simple packet listener. Usage: / add|remove client|server|both [ID start] [ID stop] [detailed] @@ -166,6 +194,18 @@ class CommandPacket extends CommandBase { protected boolean handleCommand(CommandSender sender, String[] args) { try { SubCommand subCommand = parseCommand(args, 0); + + // Commands with different parameters + if (subCommand == SubCommand.PAGE) { + int page = Integer.parseInt(args[1]); + + if (page > 0) + printPage(sender, page); + else + sendMessageSilently(sender, ChatColor.RED + "Page index must be greater than zero."); + return true; + } + ConnectionSide side = parseSide(args, 1, ConnectionSide.BOTH); Integer lastIndex = args.length - 1; @@ -230,38 +270,24 @@ class CommandPacket extends CommandBase { for (Range range : ranges) { for (int id : range.asSet(DiscreteDomains.integers())) { if (named.contains(id)) { - messages.add(ChatColor.BLUE + "" + id + ": " + Packets.getDeclaredName(id)); + messages.add(ChatColor.WHITE + "" + id + ": " + ChatColor.BLUE + Packets.getDeclaredName(id)); } } } - // Convert to two rows - messages = getMessagesInRows(messages, 2, CHAT_WIDTH); - - // Print that - for (String message : messages) { - sendMessageSilently(sender, message); + if (sender instanceof Player && messages.size() > 0 && messages.size() > PAGE_LINE_COUNT) { + // Divide the messages into chuncks + pagedMessage.put(sender, messages); + printPage(sender, 1); + + } else { + // Just print the damn thing + for (String message : messages) { + sendMessageSilently(sender, message); + } } } - private List getMessagesInRows(List messages, int rows, int totalWidth) { - List output = new ArrayList(); - int columnWidth = totalWidth / rows; - - for (int i = 0; i < messages.size(); i++) { - int mapped = i / rows; - - // Either create a new row, or add to an existing row - if (mapped < output.size()) { - output.set(mapped, Strings.padEnd(output.get(mapped), columnWidth, ' ') + messages.get(i)); - } else { - output.add(messages.get(i)); - } - } - - return output; - } - /** * Retrieve whitelist information about a given listener. * @param listener - the given listener. @@ -445,6 +471,8 @@ class CommandPacket extends CommandBase { return SubCommand.REMOVE; else if ("names".startsWith(text)) return SubCommand.NAMES; + else if ("page".startsWith(text)) + return SubCommand.PAGE; else throw new IllegalArgumentException(text + " is not a valid sub command. Must be add or remove."); } From 6c8bda24fd670fe71338aa954f0bff5ad945ac92 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 4 Nov 2012 00:06:44 +0100 Subject: [PATCH 48/54] Parse "detailed" as TRUE. --- .../src/main/java/com/comphenix/protocol/CommandPacket.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java index 8bcc9f4e..86eccd23 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandPacket.java @@ -209,7 +209,7 @@ class CommandPacket extends CommandBase { ConnectionSide side = parseSide(args, 1, ConnectionSide.BOTH); Integer lastIndex = args.length - 1; - Boolean detailed = parseBoolean(args, lastIndex); + Boolean detailed = parseBoolean(args, "detailed", lastIndex); // See if the last element is a boolean if (detailed == null) { @@ -495,10 +495,12 @@ class CommandPacket extends CommandBase { } // Parse a boolean - private Boolean parseBoolean(String[] args, int index) { + private Boolean parseBoolean(String[] args, String parameterName, int index) { if (index < args.length) { if (args[index].equalsIgnoreCase("true")) return true; + else if (args[index].equalsIgnoreCase(parameterName)) + return true; else if (args[index].equalsIgnoreCase("false")) return false; else From 2f2eb148fab64d88562c622d606c0de71b1b5ccd Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 4 Nov 2012 01:10:05 +0100 Subject: [PATCH 49/54] Added a timeout listener. --- .../protocol/AsynchronousManager.java | 18 +++++ .../protocol/async/AsyncFilterManager.java | 73 ++++++++++++++++++- .../comphenix/protocol/async/AsyncMarker.java | 2 +- .../protocol/async/PacketSendingQueue.java | 24 +++++- .../injector/PacketFilterManager.java | 18 ++++- .../injector/SortedPacketListenerList.java | 2 +- 6 files changed, 124 insertions(+), 13 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/AsynchronousManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/AsynchronousManager.java index a449b1ca..4d1d8f35 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/AsynchronousManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/AsynchronousManager.java @@ -99,4 +99,22 @@ public interface AsynchronousManager { * @param packet - packet to signal. */ public abstract void signalPacketTransmission(PacketEvent packet); + + /** + * Register a synchronous listener that handles packets when they time out. + * @param listener - synchronous listener that will handle timed out packets. + */ + public abstract void registerTimeoutHandler(PacketListener listener); + + /** + * Unregisters a given timeout listener. + * @param listener - the timeout listener to unregister. + */ + public abstract void unregisterTimeoutHandler(PacketListener listener); + + /** + * Get a immutable list of every registered timeout handler. + * @return List of every registered timeout handler. + */ + public abstract Set getTimeoutHandlers(); } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java index 7b5744e7..7dd7b504 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java @@ -20,6 +20,7 @@ package com.comphenix.protocol.async; import java.util.Collection; import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.bukkit.plugin.Plugin; @@ -34,7 +35,10 @@ import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PrioritizedListener; +import com.comphenix.protocol.injector.SortedPacketListenerList; import com.google.common.base.Objects; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; /** * Represents a filter manager for asynchronous packets. @@ -43,9 +47,14 @@ import com.google.common.base.Objects; */ public class AsyncFilterManager implements AsynchronousManager { + private SortedPacketListenerList serverTimeoutListeners; + private SortedPacketListenerList clientTimeoutListeners; + private Set timeoutListeners; + private PacketProcessingQueue serverProcessingQueue; private PacketSendingQueue serverQueue; + private PacketProcessingQueue clientProcessingQueue; private PacketSendingQueue clientQueue; @@ -68,11 +77,30 @@ public class AsyncFilterManager implements AsynchronousManager { public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler, ProtocolManager manager) { + // Initialize timeout listeners + serverTimeoutListeners = new SortedPacketListenerList(); + clientTimeoutListeners = new SortedPacketListenerList(); + timeoutListeners = Sets.newSetFromMap(new ConcurrentHashMap()); + // Server packets are synchronized already - this.serverQueue = new PacketSendingQueue(false); + this.serverQueue = new PacketSendingQueue(false) { + @Override + protected void onPacketTimeout(PacketEvent event) { + if (!cleaningUp) { + serverTimeoutListeners.invokePacketSending(AsyncFilterManager.this.reporter, event); + } + } + }; // Client packets must be synchronized - this.clientQueue = new PacketSendingQueue(true); + this.clientQueue = new PacketSendingQueue(true) { + @Override + protected void onPacketTimeout(PacketEvent event) { + if (!cleaningUp) { + clientTimeoutListeners.invokePacketSending(AsyncFilterManager.this.reporter, event); + } + } + }; this.serverProcessingQueue = new PacketProcessingQueue(serverQueue); this.clientProcessingQueue = new PacketProcessingQueue(clientQueue); @@ -89,6 +117,27 @@ public class AsyncFilterManager implements AsynchronousManager { return registerAsyncHandler(listener, true); } + @Override + public void registerTimeoutHandler(PacketListener listener) { + if (listener == null) + throw new IllegalArgumentException("listener cannot be NULL."); + if (!timeoutListeners.add(listener)) + return; + + ListeningWhitelist sending = listener.getSendingWhitelist(); + ListeningWhitelist receiving = listener.getReceivingWhitelist(); + + if (!ListeningWhitelist.isEmpty(sending)) + serverTimeoutListeners.addListener(listener, sending); + if (!ListeningWhitelist.isEmpty(receiving)) + serverTimeoutListeners.addListener(listener, receiving); + } + + @Override + public Set getTimeoutHandlers() { + return ImmutableSet.copyOf(timeoutListeners); + } + /** * Registers an asynchronous packet handler. *

    @@ -131,6 +180,21 @@ public class AsyncFilterManager implements AsynchronousManager { return whitelist != null && whitelist.getWhitelist().size() > 0; } + @Override + public void unregisterTimeoutHandler(PacketListener listener) { + if (listener == null) + throw new IllegalArgumentException("listener cannot be NULL."); + + ListeningWhitelist sending = listener.getSendingWhitelist(); + ListeningWhitelist receiving = listener.getReceivingWhitelist(); + + // Do it in the opposite order + if (serverTimeoutListeners.removeListener(listener, sending).size() > 0 || + clientTimeoutListeners.removeListener(listener, receiving).size() > 0) { + timeoutListeners.remove(listener); + } + } + @Override public void unregisterAsyncHandler(AsyncListenerHandler handler) { if (handler == null) @@ -276,6 +340,10 @@ public class AsyncFilterManager implements AsynchronousManager { cleaningUp = true; serverProcessingQueue.cleanupAll(); serverQueue.cleanupAll(); + + timeoutListeners.clear(); + serverTimeoutListeners = null; + clientTimeoutListeners = null; } @Override @@ -333,7 +401,6 @@ public class AsyncFilterManager implements AsynchronousManager { * Send any due packets, or clean up packets that have expired. */ public void sendProcessedPackets(int tickCounter, boolean onMainThread) { - // The server queue is unlikely to need checking that often if (tickCounter % 10 == 0) { serverQueue.trySendPackets(onMainThread); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java index 989545fc..febbbbcf 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncMarker.java @@ -418,7 +418,7 @@ public class AsyncMarker implements Serializable, Comparable { // We're in 1.2.5 alwaysSync = true; } else { - System.err.println("Cannot determine asynchronous state of packets!"); + System.err.println("[ProtocolLib] Cannot determine asynchronous state of packets!"); alwaysSync = true; } } 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 e9816259..698c2def 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/PacketSendingQueue.java @@ -27,13 +27,14 @@ 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; /** * Represents packets ready to be transmitted to a client. * @author Kristian */ -class PacketSendingQueue { +abstract class PacketSendingQueue { public static final int INITIAL_CAPACITY = 64; @@ -77,7 +78,7 @@ class PacketSendingQueue { AsyncMarker marker = packetUpdated.getAsyncMarker(); // Should we reorder the event? - if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex()) { + if (marker.getQueuedSendingIndex() != marker.getNewSendingIndex() && !marker.hasExpired()) { PacketEvent copy = PacketEvent.fromSynchronous(packetUpdated, marker); // "Cancel" the original event @@ -127,6 +128,7 @@ class PacketSendingQueue { if (holder != null) { PacketEvent current = holder.getEvent(); AsyncMarker marker = current.getAsyncMarker(); + boolean hasExpired = marker.hasExpired(); // Abort if we're not on the main thread if (synchronizeMain) { @@ -144,8 +146,16 @@ class PacketSendingQueue { } } - if (marker.isProcessed() || marker.hasExpired()) { - if (marker.isProcessed() && !current.isCancelled()) { + if (marker.isProcessed() || hasExpired) { + if (hasExpired) { + // Notify timeout listeners + onPacketTimeout(current); + + // Recompute + marker = current.getAsyncMarker(); + hasExpired = marker.hasExpired(); + } + if (marker.isProcessed() && !current.isCancelled() && !hasExpired) { // Silently skip players that have logged out if (isOnline(current.getPlayer())) { sendPacket(current); @@ -162,6 +172,12 @@ class PacketSendingQueue { } } + /** + * Invoked when a packet has timed out. + * @param event - the timed out packet. + */ + protected abstract void onPacketTimeout(PacketEvent event); + private boolean isOnline(Player player) { return player != null && player.isOnline(); } 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 e84998eb..29ee6f9d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -108,8 +108,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok private PlayerInjectionHandler playerInjection; // The two listener containers - private SortedPacketListenerList recievedListeners = new SortedPacketListenerList(); - private SortedPacketListenerList sendingListeners = new SortedPacketListenerList(); + private SortedPacketListenerList recievedListeners; + private SortedPacketListenerList sendingListeners; // Whether or not this class has been closed private volatile boolean hasClosed; @@ -150,6 +150,10 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Just boilerplate final DelayedSingleTask finalUnhookTask = unhookTask; + // Listener containers + this.recievedListeners = new SortedPacketListenerList(); + this.sendingListeners = new SortedPacketListenerList(); + // References this.unhookTask = unhookTask; this.server = server; @@ -366,12 +370,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok @Override public void invokePacketRecieving(PacketEvent event) { - handlePacket(recievedListeners, event, false); + if (!hasClosed) { + handlePacket(recievedListeners, event, false); + } } @Override public void invokePacketSending(PacketEvent event) { - handlePacket(sendingListeners, event, true); + if (!hasClosed) { + handlePacket(sendingListeners, event, true); + } } /** @@ -812,6 +820,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Remove listeners packetListeners.clear(); + recievedListeners = null; + sendingListeners = null; // Clean up async handlers. We have to do this last. asyncFilterManager.cleanupAll(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java index 5a1ef288..d3184b10 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/SortedPacketListenerList.java @@ -29,7 +29,7 @@ import com.comphenix.protocol.events.PacketListener; * * @author Kristian */ -class SortedPacketListenerList extends AbstractConcurrentListenerMultimap { +public final class SortedPacketListenerList extends AbstractConcurrentListenerMultimap { /** * Invokes the given packet event for every registered listener. From 64e3ba7f141baf84281d348072a269452e32f287 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 4 Nov 2012 02:15:16 +0100 Subject: [PATCH 50/54] Add the ability to disable metrics in the config file. --- .../comphenix/protocol/ProtocolConfig.java | 21 +++++++++++++++++++ .../comphenix/protocol/ProtocolLibrary.java | 7 +++++-- .../protocol/metrics/Statistics.java | 3 ++- .../comphenix/protocol/metrics/Updater.java | 2 +- ProtocolLib/src/main/resources/config.yml | 4 +++- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java index 517cf6f1..cb1acd17 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -14,6 +14,8 @@ class ProtocolConfig { private static final String SECTION_GLOBAL = "global"; private static final String SECTION_AUTOUPDATER = "auto updater"; + private static final String METRICS_ENABLED = "metrics"; + private static final String UPDATER_NOTIFY = "notify"; private static final String UPDATER_DOWNLAD = "download"; private static final String UPDATER_DELAY = "delay"; @@ -119,6 +121,25 @@ class ProtocolConfig { return updater.getLong(UPDATER_LAST_TIME, 0); } + /** + * Retrieve whether or not metrics is enabled. + * @return TRUE if metrics is enabled, FALSE otherwise. + */ + public boolean isMetricsEnabled() { + return global.getBoolean(METRICS_ENABLED, true); + } + + /** + * Set whether or not metrics is enabled. + *

    + * This setting will take effect next time ProtocolLib is started. + * + * @param enabled - whether or not metrics is enabled. + */ + public void setMetricsEnabled(boolean enabled) { + global.set(METRICS_ENABLED, enabled); + } + /** * Set the last time we updated, in seconds since 1970.01.01 00:00. * @param lastTimeSeconds - new last update time. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index eb713965..6547a959 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -189,7 +189,9 @@ public class ProtocolLibrary extends JavaPlugin { // Try to enable statistics try { - statistisc = new Statistics(this); + if (config.isMetricsEnabled()) { + statistisc = new Statistics(this); + } } catch (IOException e) { reporter.reportDetailed(this, "Unable to enable metrics.", e, statistisc); } catch (Throwable e) { @@ -310,7 +312,8 @@ public class ProtocolLibrary extends JavaPlugin { /** * Retrieve the metrics instance used to measure users of this library. *

    - * Note that this method may return NULL when the server is reloading or shutting down. + * Note that this method may return NULL when the server is reloading or shutting down. It is also + * NULL if metrics has been disabled. * @return Metrics instance container. */ public Statistics getStatistics() { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Statistics.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Statistics.java index 5bf12478..0cc29faa 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Statistics.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Statistics.java @@ -36,10 +36,11 @@ public class Statistics { public Statistics(Plugin plugin) throws IOException { metrics = new Metrics(plugin); - metrics.start(); // Determine who is using this library addPluginUserGraph(metrics); + + metrics.start(); } private void addPluginUserGraph(Metrics metrics) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java index f3f18939..dee8708e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java @@ -333,7 +333,7 @@ public class Updater downloaded += count; fout.write(data, 0, count); int percent = (int) (downloaded * 100 / fileLength); - if(announce & (percent % 10 == 0)) + if(announce && (percent % 10 == 0)) { logger.info("Downloading update: " + percent + "% of " + fileLength + " bytes."); } diff --git a/ProtocolLib/src/main/resources/config.yml b/ProtocolLib/src/main/resources/config.yml index aa16c45f..8bc3ca2d 100644 --- a/ProtocolLib/src/main/resources/config.yml +++ b/ProtocolLib/src/main/resources/config.yml @@ -7,4 +7,6 @@ global: # Number of seconds to wait until a new update is downloaded delay: 43200 # 12 hours # Last update time - last: 0 \ No newline at end of file + last: 0 + + metrics: true \ No newline at end of file From f5dce019dff5523ef8e1e424df9ba8f3bddd9c1c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 4 Nov 2012 02:30:47 +0100 Subject: [PATCH 51/54] Save the last update check, even if it was initiated from a command. --- .../comphenix/protocol/CommandProtocol.java | 22 +++++++++++++++---- .../comphenix/protocol/ProtocolLibrary.java | 12 +++++----- .../comphenix/protocol/metrics/Updater.java | 11 ++++++---- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java index 9f5a0eb0..7039907a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java @@ -21,11 +21,13 @@ class CommandProtocol extends CommandBase { private Plugin plugin; private Updater updater; + private ProtocolConfig config; - public CommandProtocol(Plugin plugin, Updater updater) { + public CommandProtocol(Plugin plugin, Updater updater, ProtocolConfig config) { super(CommandBase.PERMISSION_ADMIN, NAME, 1); this.plugin = plugin; this.updater = updater; + this.config = config; } @Override @@ -50,9 +52,11 @@ class CommandProtocol extends CommandBase { @Override public void run() { UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true); - sender.sendMessage(ChatColor.DARK_BLUE + "Version check: " + result.toString()); + sender.sendMessage(ChatColor.BLUE + "Version check: " + result.toString()); } }); + + updateFinished(); } public void updateVersion(final CommandSender sender) { @@ -61,14 +65,24 @@ class CommandProtocol extends CommandBase { @Override public void run() { UpdateResult result = updater.update(UpdateType.DEFAULT, true); - sender.sendMessage(ChatColor.DARK_BLUE + "Update: " + result.toString()); + sender.sendMessage(ChatColor.BLUE + "Update: " + result.toString()); } }); + + updateFinished(); + } + + /** + * Prevent further automatic updates until the next delay. + */ + public void updateFinished() { + config.setAutoLastTime(System.currentTimeMillis()); + config.saveAll(); } public void reloadConfiguration(CommandSender sender) { plugin.saveConfig(); plugin.reloadConfig(); - sender.sendMessage(ChatColor.DARK_BLUE + "Reloaded configuration!"); + sender.sendMessage(ChatColor.BLUE + "Reloaded configuration!"); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 6547a959..c74a6858 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -105,7 +105,7 @@ public class ProtocolLibrary extends JavaPlugin { reporter.addGlobalParameter("manager", protocolManager); // Initialize command handlers - commandProtocol = new CommandProtocol(this, updater); + commandProtocol = new CommandProtocol(this, updater, config); commandPacket = new CommandPacket(this, logger, reporter, protocolManager); // Send logging information to player listeners too @@ -237,16 +237,14 @@ public class ProtocolLibrary extends JavaPlugin { long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND; // Should we update? - if (currentTime < config.getAutoLastTime() + config.getAutoDelay()) { - // Great. Save this check. - config.setAutoLastTime(currentTime); - config.saveAll(); - - // Initiate the update from the console + 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(); } } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java index dee8708e..eaea4a7b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/metrics/Updater.java @@ -547,11 +547,13 @@ public class Updater { if (type != UpdateType.NO_VERSION_CHECK) { + String[] parts = title.split(" "); String version = plugin.getDescription().getVersion(); - if(title.split("v").length == 2) + + if(parts.length == 2) { - String remoteVersion = title.split("v")[1].split(" ")[0]; // Get the newest file's version number - int remVer = -1,curVer=0; + String remoteVersion = parts[1].split(" ")[0]; // Get the newest file's version number + int remVer = -1, curVer=0; try { remVer = calVer(remoteVersion); @@ -559,8 +561,9 @@ public class Updater } catch(NumberFormatException nfe) { - remVer=-1; + remVer=-1; } + if(hasTag(version)||version.equalsIgnoreCase(remoteVersion)||curVer>=remVer) { // We already have the latest version, or this build is tagged for no-update From abafb7a6c5f186f3f995aa09a5a59e955422d98d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 4 Nov 2012 03:03:38 +0100 Subject: [PATCH 52/54] Fix configuration handling. --- .../comphenix/protocol/CommandProtocol.java | 11 +++--- .../comphenix/protocol/ProtocolConfig.java | 39 +++++++++++++++++-- .../comphenix/protocol/ProtocolLibrary.java | 20 +++++----- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java index 7039907a..494d6f84 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/CommandProtocol.java @@ -35,7 +35,7 @@ class CommandProtocol extends CommandBase { String subCommand = args[0]; // Only return TRUE if we executed the correct command - if (subCommand.equalsIgnoreCase("config")) + if (subCommand.equalsIgnoreCase("config") || subCommand.equalsIgnoreCase("reload")) reloadConfiguration(sender); else if (subCommand.equalsIgnoreCase("check")) checkVersion(sender); @@ -52,7 +52,7 @@ class CommandProtocol extends CommandBase { @Override public void run() { UpdateResult result = updater.update(UpdateType.NO_DOWNLOAD, true); - sender.sendMessage(ChatColor.BLUE + "Version check: " + result.toString()); + sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString()); } }); @@ -65,7 +65,7 @@ class CommandProtocol extends CommandBase { @Override public void run() { UpdateResult result = updater.update(UpdateType.DEFAULT, true); - sender.sendMessage(ChatColor.BLUE + "Update: " + result.toString()); + sender.sendMessage(ChatColor.BLUE + "[ProtocolLib] " + result.toString()); } }); @@ -76,12 +76,13 @@ class CommandProtocol extends CommandBase { * Prevent further automatic updates until the next delay. */ public void updateFinished() { - config.setAutoLastTime(System.currentTimeMillis()); + long currentTime = System.currentTimeMillis() / ProtocolLibrary.MILLI_PER_SECOND; + + config.setAutoLastTime(currentTime); config.saveAll(); } public void reloadConfiguration(CommandSender sender) { - plugin.saveConfig(); plugin.reloadConfig(); sender.sendMessage(ChatColor.BLUE + "Reloaded configuration!"); } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java index cb1acd17..09e5313d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolConfig.java @@ -1,5 +1,7 @@ package com.comphenix.protocol; +import java.io.File; + import org.bukkit.configuration.Configuration; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.plugin.Plugin; @@ -26,6 +28,7 @@ class ProtocolConfig { private Plugin plugin; private Configuration config; + private boolean loadingSections; private ConfigurationSection global; private ConfigurationSection updater; @@ -35,29 +38,57 @@ class ProtocolConfig { } public ProtocolConfig(Plugin plugin, Configuration config) { - this.config = config; + this.plugin = plugin; + reloadConfig(); + } + + /** + * Reload configuration file. + */ + public void reloadConfig() { + this.config = plugin.getConfig(); loadSections(true); } - + /** * Load data sections. * @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); } if (global != null) { updater = global.getConfigurationSection(SECTION_AUTOUPDATER); } - + // Automatically copy defaults - if (copyDefaults && (global == null || updater == null)) { + if (copyDefaults && (!getFile().exists() || global == null || updater == null)) { + loadingSections = true; + + if (config != null) + config.options().copyDefaults(true); plugin.saveDefaultConfig(); config = plugin.getConfig(); + + loadingSections = false; loadSections(false); + + // Inform the user + System.out.println("[ProtocolLib] Created default configuration."); } } + + /** + * Retrieve a reference to the configuration file. + * @return Configuration file on disk. + */ + public File getFile() { + return new File(plugin.getDataFolder(), "config.yml"); + } /** * Retrieve whether or not ProtocolLib should determine if a new version has been released. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index c74a6858..1a1d5db3 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -17,7 +17,6 @@ package com.comphenix.protocol; -import java.io.File; import java.io.IOException; import java.util.logging.Handler; import java.util.logging.LogRecord; @@ -43,7 +42,11 @@ import com.comphenix.protocol.reflect.compiler.BackgroundCompiler; */ public class ProtocolLibrary extends JavaPlugin { - private static final long MILLI_PER_SECOND = 1000; + /** + * The number of milliseconds per second. + */ + static final long MILLI_PER_SECOND = 1000; + private static final String PERMISSION_INFO = "protocol.info"; // There should only be one protocol manager, so we'll make it static @@ -93,7 +96,7 @@ public class ProtocolLibrary extends JavaPlugin { config = new ProtocolConfig(this); } catch (Exception e) { reporter.reportWarning(this, "Cannot load configuration", e); - + // Load it again deleteConfig(); config = new ProtocolConfig(this); @@ -118,17 +121,16 @@ public class ProtocolLibrary extends JavaPlugin { } private void deleteConfig() { - File configFile = new File(getDataFolder(), "config.yml"); - - // Delete the file - configFile.delete(); + config.getFile().delete(); } @Override public void reloadConfig() { super.reloadConfig(); // Reload configuration - config = new ProtocolConfig(this); + if (config != null) { + config.reloadConfig(); + } } private void broadcastUsers(final String permission) { @@ -237,7 +239,7 @@ public class ProtocolLibrary extends JavaPlugin { long currentTime = System.currentTimeMillis() / MILLI_PER_SECOND; // Should we update? - if (currentTime < config.getAutoLastTime() + config.getAutoDelay()) { + if (currentTime > config.getAutoLastTime() + config.getAutoDelay()) { // Initiate the update as if it came from the console if (config.isAutoDownload()) commandProtocol.updateVersion(getServer().getConsoleSender()); From a5dbbc6170d3e6182a66911f9ae03568be0c5ee7 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 4 Nov 2012 03:38:00 +0100 Subject: [PATCH 53/54] Release of 1.5.1 --- 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 ea188dd7..685bbf5f 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 1.5.1-SNAPSHOT + 1.5.1 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 5089f4d8..78a71f62 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-SNAPSHOT +version: 1.5.1 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 2a5d240df9186c3c164fe44ae7f1ca572ac1f9dc Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 4 Nov 2012 03:39:38 +0100 Subject: [PATCH 54/54] Update POM. --- 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 59e0de0c..dc9bd600 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-SNAPSHOT + 1.5.1 Provides read/write access to the Minecraft protocol. http://dev.bukkit.org/server-mods/protocollib/