From 256581d4ed96bbe46ba9aea3d27f1eec608c9a11 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 23 Jun 2013 21:35:00 +0200 Subject: [PATCH 01/20] Incrementing to 2.4.6-SNAPSHOT --- 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 695153b4..39b96deb 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 2.4.5 + 2.4.6-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 010f513f..0543ab74 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 2.4.5 +version: 2.4.6-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 8d0e8139de024f7fc705d8b972d52f215d50040c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 23 Jun 2013 21:37:37 +0200 Subject: [PATCH 02/20] Add support for the Lilypad Bukkit Connector. FIXES Ticket-101 --- .../injector/player/PlayerInjector.java | 2 +- .../player/ProxyPlayerInjectionHandler.java | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) 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 bf5a0cd9..79bb4c00 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 @@ -284,7 +284,7 @@ public abstract class PlayerInjector implements SocketInjector { /** * Retrieve the associated remote address of a player. - * @return The associated remote address.. + * @return The associated remote address. * @throws IllegalAccessException If we're unable to read the socket address field. */ @Override diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 1f3fa663..15570caf 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -21,6 +21,7 @@ import java.io.DataInputStream; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; +import java.net.Socket; import java.net.SocketAddress; import java.util.Map; import java.util.Set; @@ -51,6 +52,7 @@ import com.comphenix.protocol.injector.server.SocketInjector; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; +import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @@ -350,6 +352,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { return null; SocketInjector previous = inputStreamLookup.peekSocketInjector(address); + Socket socket = injector.getSocket(); // Close any previously associated hooks before we proceed if (previous != null && !(player instanceof Factory)) { @@ -363,8 +366,7 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { } injector.injectManager(); - // Save injector - inputStreamLookup.setSocketInjector(address, injector); + saveAddressLookup(address, socket, injector); break; } @@ -413,6 +415,17 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { return injector; } + private void saveAddressLookup(SocketAddress address, Socket socket, SocketInjector injector) { + SocketAddress socketAddress = socket != null ? socket.getRemoteSocketAddress() : null; + + if (socketAddress != null && !Objects.equal(socketAddress, address)) { + // Save this version as well + inputStreamLookup.setSocketInjector(socketAddress, injector); + } + // Save injector + inputStreamLookup.setSocketInjector(address, injector); + } + private void cleanupHook(PlayerInjector injector) { // Clean up as much as possible try { From ef4476a72e540e490f73f0d8cb86858ab651fdab Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 23 Jun 2013 21:56:53 +0200 Subject: [PATCH 03/20] Print a warning message instead of crashing. We shouldn't prevent a plugin from adding packet listeners just because the plugin verifier failed. --- .../protocol/injector/PacketFilterManager.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 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 5666765b..b8ff35f5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -85,6 +85,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok public static final ReportType REPORT_CANNOT_INJECT_PLAYER = new ReportType("Unable to inject player."); public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin."); + public static final ReportType REPORT_PLUGIN_VERIFIER_ERROR = new ReportType("Verifier error: %s"); /** * Sets the inject hook type. Different types allow for maximum compatibility. @@ -298,12 +299,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * @param plugin - plugin to check. */ private void printPluginWarnings(Plugin plugin) { - switch (pluginVerifier.verify(plugin)) { - case NO_DEPEND: - reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_DEPEND_MISSING).messageParam(plugin.getName())); - case VALID: - // Do nothing - break; + try { + switch (pluginVerifier.verify(plugin)) { + case NO_DEPEND: + reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_DEPEND_MISSING).messageParam(plugin.getName())); + case VALID: + // Do nothing + break; + } + } catch (IllegalStateException e) { + reporter.reportWarning(this, Report.newBuilder(REPORT_PLUGIN_VERIFIER_ERROR).messageParam(e.getMessage())); } } From 0b3fe5470a4ad534b95ad8a01d69e8e8faa64f2e Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 27 Jun 2013 03:05:53 +0200 Subject: [PATCH 04/20] Added a plugin verifier test. Attempting to solve a problem discovered by Silthus, but I need more information. --- .../protocol/injector/PluginVerifier.java | 5 +- .../protocol/injector/PluginVerifierTest.java | 89 +++++++++++++++++++ 2 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 ProtocolLib/src/test/java/com/comphenix/protocol/injector/PluginVerifierTest.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PluginVerifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PluginVerifier.java index 2e66cf5d..30bf4e51 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PluginVerifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PluginVerifier.java @@ -5,7 +5,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginLoadOrder; @@ -61,7 +60,7 @@ class PluginVerifier { * Reference to ProtocolLib. */ private final Plugin dependency; - + /** * Construct a new plugin verifier. * @param dependency - reference to ProtocolLib, a dependency we require of plugins. @@ -98,7 +97,7 @@ class PluginVerifier { * @return The retrieved plugin, or NULL if not found. */ private Plugin getPluginOrDefault(String pluginName) { - return Bukkit.getPluginManager().getPlugin(pluginName); + return dependency.getServer().getPluginManager().getPlugin(pluginName); } /** diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/injector/PluginVerifierTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/injector/PluginVerifierTest.java new file mode 100644 index 00000000..daa3cacd --- /dev/null +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/injector/PluginVerifierTest.java @@ -0,0 +1,89 @@ +package com.comphenix.protocol.injector; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.List; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.bukkit.Server; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginLoadOrder; +import org.bukkit.plugin.PluginManager; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.core.classloader.annotations.PrepareForTest; +import com.comphenix.protocol.injector.PluginVerifier.VerificationResult; +import com.google.common.base.Objects; +import com.google.common.collect.Lists; + +// Damn final classes +@RunWith(org.powermock.modules.junit4.PowerMockRunner.class) +@PrepareForTest(PluginDescriptionFile.class) +public class PluginVerifierTest { + @Test + public void testDependecies() { + List plugins = Lists.newArrayList(); + Server server = mockServer(plugins); + + Plugin library = mockPlugin(server, "ProtocolLib", PluginLoadOrder.POSTWORLD); + Plugin skillPlugin = mockPlugin(server, "SkillPlugin", "RaidCraft-API", "RCPermissions", "RCConversations"); + Plugin raidCraftAPI = mockPlugin(server, "RaidCraft-API", "WorldGuard", "WorldEdit"); + Plugin conversations = mockPlugin(server, "RCConversations", "RaidCraft-API"); + Plugin permissions = mockPlugin(server, "RCPermissions", "RaidCraft-API"); + + // Add the plugins + plugins.addAll(Arrays.asList(library, skillPlugin, raidCraftAPI, conversations, permissions)); + PluginVerifier verifier = new PluginVerifier(library); + + // Verify the root - it should have no dependencies on ProtocolLib + assertEquals(VerificationResult.NO_DEPEND, verifier.verify(skillPlugin)); + } + + private Server mockServer(final List plugins) { + Server mockServer = mock(Server.class); + PluginManager manager = mock(PluginManager.class); + + when(mockServer.getPluginManager()).thenReturn(manager); + when(manager.getPlugin(anyString())).thenAnswer(new Answer() { + @Override + public Plugin answer(InvocationOnMock invocation) throws Throwable { + String name = (String) invocation.getArguments()[0]; + + for (Plugin plugin : plugins) { + if (Objects.equal(name, plugin.getName())) { + return plugin; + } + } + return null; + } + }); + return mockServer; + } + + private Plugin mockPlugin(Server server, String name,String... depend) { + return mockPlugin(server, name, PluginLoadOrder.POSTWORLD, depend); + } + + private Plugin mockPlugin(Server server, String name, PluginLoadOrder order, String... depend) { + Plugin plugin = mock(Plugin.class); + PluginDescriptionFile file = mock(PluginDescriptionFile.class); + + when(plugin.getServer()).thenReturn(server); + when(plugin.getName()).thenReturn(name); + when(plugin.toString()).thenReturn(name); + when(plugin.getDescription()).thenReturn(file); + + // This is the difficult part + when(file.getLoad()).thenReturn(order); + when(file.getDepend()).thenReturn(Arrays.asList(depend)); + when(file.getSoftDepend()).thenReturn(null); + return plugin; + } +} From 5e5243e3fb9cde8fe93026cb151d28ddb54d5a49 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 2 Jul 2013 17:41:43 +0200 Subject: [PATCH 05/20] Update ProtocolLib for 1.6.1. A lot of methods changed from accepting DataInputStream to DataInput, which messed with the part that dynamically finds the "readPacket" method. Changed to accepting any method whose signature contains a parameter derived from DataInput. --- .../injector/packet/ProxyPacketInjector.java | 26 +- .../injector/packet/ReadPacketModifier.java | 280 +++++++++--------- .../reflect/fuzzy/FuzzyMethodContract.java | 29 +- 3 files changed, 189 insertions(+), 146 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java index a6744fb0..800f264e 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java @@ -17,6 +17,7 @@ package com.comphenix.protocol.injector.packet; +import java.io.DataInput; import java.io.DataInputStream; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -33,6 +34,8 @@ import net.sf.cglib.proxy.NoOp; import com.comphenix.protocol.Packets; import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.injector.ListenerInvoker; @@ -50,6 +53,8 @@ import com.comphenix.protocol.utility.MinecraftReflection; * @author Kristian */ class ProxyPacketInjector implements PacketInjector { + public static final ReportType REPORT_CANNOT_FIND_READ_PACKET_METHOD = new ReportType("Cannot find read packet method for ID %s."); + /** * Represents a way to update the packet ID to class lookup table. * @author Kristian @@ -134,7 +139,7 @@ class ProxyPacketInjector implements PacketInjector { */ private static FuzzyMethodContract readPacket = FuzzyMethodContract.newBuilder(). returnTypeVoid(). - parameterExactType(DataInputStream.class). + parameterDerivedOf(DataInput.class). parameterCount(1). build(); @@ -154,6 +159,9 @@ class ProxyPacketInjector implements PacketInjector { // Share callback filter private CallbackFilter filter; + + // Determine if the read packet method was found + private boolean readPacketIntercepted = false; public ProxyPacketInjector(ClassLoader classLoader, ListenerInvoker manager, PlayerInjectionHandler playerInjection, ErrorReporter reporter) throws FieldAccessException { @@ -224,16 +232,20 @@ class ProxyPacketInjector implements PacketInjector { } if (filter == null) { + readPacketIntercepted = false; + filter = new CallbackFilter() { @Override public int accept(Method method) { // Skip methods defined in Object - if (method.getDeclaringClass().equals(Object.class)) + if (method.getDeclaringClass().equals(Object.class)) { return 0; - else if (readPacket.isMatch(MethodInfo.fromMethod(method), null)) + } else if (readPacket.isMatch(MethodInfo.fromMethod(method), null)) { + readPacketIntercepted = true; return 1; - else + } else { return 2; + } } }; } @@ -252,6 +264,12 @@ class ProxyPacketInjector implements PacketInjector { // Add a static reference Enhancer.registerStaticCallbacks(proxy, new Callback[] { NoOp.INSTANCE, modifierReadPacket, modifierRest }); + // Check that we found the read method + if (!readPacketIntercepted) { + reporter.reportWarning(this, + Report.newBuilder(REPORT_CANNOT_FIND_READ_PACKET_METHOD).messageParam(packetID)); + } + // Override values previous.put(packetID, old); registry.put(proxy, packetID); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java index d88d8282..0c8ace3f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ReadPacketModifier.java @@ -1,140 +1,140 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector.packet; - -import java.io.DataInputStream; -import java.lang.reflect.Method; -import java.util.Map; - -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.events.PacketEvent; -import com.google.common.collect.MapMaker; - -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; - -class ReadPacketModifier implements MethodInterceptor { - public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet."); - - // A cancel marker - private static final Object CANCEL_MARKER = new Object(); - - // Common for all packets of the same type - private ProxyPacketInjector packetInjector; - private int packetID; - - // Report errors - private ErrorReporter reporter; - - // If this is a read packet data method - private boolean isReadPacketDataMethod; - - // Whether or not a packet has been cancelled - private static Map override = new MapMaker().weakKeys().makeMap(); - - public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) { - this.packetID = packetID; - this.packetInjector = packetInjector; - this.reporter = reporter; - this.isReadPacketDataMethod = isReadPacketDataMethod; - } - - /** - * Remove any packet overrides. - * @param packet - the packet to rever - */ - public static void removeOverride(Object packet) { - override.remove(packet); - } - - /** - * Retrieve the packet that overrides the methods of the given packet. - * @param packet - the given packet. - * @return Overriden object. - */ - public static Object getOverride(Object packet) { - return override.get(packet); - } - - /** - * Determine if the given packet has been cancelled before. - * @param packet - the packet to check. - * @return TRUE if it has been cancelled, FALSE otherwise. - */ - public static boolean hasCancelled(Object packet) { - return getOverride(packet) == CANCEL_MARKER; - } - - @Override - public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - // Atomic retrieval - Object overridenObject = override.get(thisObj); - Object returnValue = null; - - if (overridenObject != null) { - // This packet has been cancelled - if (overridenObject == CANCEL_MARKER) { - // So, cancel all void methods - if (method.getReturnType().equals(Void.TYPE)) - return null; - else // Revert to normal for everything else - overridenObject = thisObj; - } - - returnValue = proxy.invokeSuper(overridenObject, args); - } else { - returnValue = proxy.invokeSuper(thisObj, args); - } - - // Is this a readPacketData method? - if (isReadPacketDataMethod) { - 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, thisObj); - PacketEvent event = packetInjector.packetRecieved(container, input); - - // Handle override - if (event != null) { - Object result = event.getPacket().getHandle(); - - if (event.isCancelled()) { - override.put(thisObj, CANCEL_MARKER); - } else if (!objectEquals(thisObj, result)) { - override.put(thisObj, result); - } - } - } catch (Throwable e) { - // Minecraft cannot handle this error - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e) - ); - } - } - return returnValue; - } - - private boolean objectEquals(Object a, Object b) { - return System.identityHashCode(a) != System.identityHashCode(b); - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector.packet; + +import java.io.DataInputStream; +import java.lang.reflect.Method; +import java.util.Map; + +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import com.google.common.collect.MapMaker; + +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; + +class ReadPacketModifier implements MethodInterceptor { + public static final ReportType REPORT_CANNOT_HANDLE_CLIENT_PACKET = new ReportType("Cannot handle client packet."); + + // A cancel marker + private static final Object CANCEL_MARKER = new Object(); + + // Common for all packets of the same type + private ProxyPacketInjector packetInjector; + private int packetID; + + // Report errors + private ErrorReporter reporter; + + // If this is a read packet data method + private boolean isReadPacketDataMethod; + + // Whether or not a packet has been cancelled + private static Map override = new MapMaker().weakKeys().makeMap(); + + public ReadPacketModifier(int packetID, ProxyPacketInjector packetInjector, ErrorReporter reporter, boolean isReadPacketDataMethod) { + this.packetID = packetID; + this.packetInjector = packetInjector; + this.reporter = reporter; + this.isReadPacketDataMethod = isReadPacketDataMethod; + } + + /** + * Remove any packet overrides. + * @param packet - the packet to rever + */ + public static void removeOverride(Object packet) { + override.remove(packet); + } + + /** + * Retrieve the packet that overrides the methods of the given packet. + * @param packet - the given packet. + * @return Overriden object. + */ + public static Object getOverride(Object packet) { + return override.get(packet); + } + + /** + * Determine if the given packet has been cancelled before. + * @param packet - the packet to check. + * @return TRUE if it has been cancelled, FALSE otherwise. + */ + public static boolean hasCancelled(Object packet) { + return getOverride(packet) == CANCEL_MARKER; + } + + @Override + public Object intercept(Object thisObj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + // Atomic retrieval + Object overridenObject = override.get(thisObj); + Object returnValue = null; + + if (overridenObject != null) { + // This packet has been cancelled + if (overridenObject == CANCEL_MARKER) { + // So, cancel all void methods + if (method.getReturnType().equals(Void.TYPE)) + return null; + else // Revert to normal for everything else + overridenObject = thisObj; + } + + returnValue = proxy.invokeSuper(overridenObject, args); + } else { + returnValue = proxy.invokeSuper(thisObj, args); + } + + // Is this a readPacketData method? + if (isReadPacketDataMethod) { + 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, thisObj); + PacketEvent event = packetInjector.packetRecieved(container, input); + + // Handle override + if (event != null) { + Object result = event.getPacket().getHandle(); + + if (event.isCancelled()) { + override.put(thisObj, CANCEL_MARKER); + } else if (!objectEquals(thisObj, result)) { + override.put(thisObj, result); + } + } + } catch (Throwable e) { + // Minecraft cannot handle this error + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_HANDLE_CLIENT_PACKET).callerParam(args[0]).error(e) + ); + } + } + return returnValue; + } + + private boolean objectEquals(Object a, Object b) { + return System.identityHashCode(a) != System.identityHashCode(b); + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java index 2fe0da85..35b92d9a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/reflect/fuzzy/FuzzyMethodContract.java @@ -161,14 +161,26 @@ public class FuzzyMethodContract extends AbstractFuzzyMember { /** * Add a new required parameter whose type must be a superclass of the given type. *

- * If a parameter is of type Number, any derived class (Integer, Long, etc.) will match it. - * @param type - a type or derived type of the matching parameter. + * If a method parameter is of type Number, then any derived class here (Integer, Long, etc.) will match it. + * @param type - a type or less derived type of the matching parameter. * @return This builder, for chaining. */ public Builder parameterSuperOf(Class type) { member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchSuper(type))); return this; } + + /** + * Add a new required parameter whose type must be a derived class of the given class. + *

+ * If the method parameter has the type Integer, then the class Number here will match it. + * @param type - a type or more derived type of the matching parameter. + * @return This builder, for chaining. + */ + public Builder parameterDerivedOf(Class type) { + member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type))); + return this; + } /** * Add a new required parameter whose type must match the given class matcher. @@ -204,6 +216,19 @@ public class FuzzyMethodContract extends AbstractFuzzyMember { return this; } + /** + * Add a new required parameter whose type must be a derived class of the given class. + *

+ * If the method parameter has the type Integer, then the class Number here will match it. + * @param type - a type or more derived type of the matching parameter. + * @param index - the expected position in the parameter list. + * @return This builder, for chaining. + */ + public Builder parameterDerivedOf(Class type, int index) { + member.paramMatchers.add(new ParameterClassMatcher(FuzzyMatchers.matchDerived(type), index)); + return this; + } + /** * Add a new required parameter whose type must match the given class matcher and index. * @param classMatcher - the class matcher. From 0ec2a705da8dcac6cfadcff7561de438381f5584 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Tue, 2 Jul 2013 17:42:51 +0200 Subject: [PATCH 06/20] Mark 1.6.1 as tested. Things seems to work now. --- .../src/main/java/com/comphenix/protocol/ProtocolLibrary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index a9c4315f..962532c9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -85,7 +85,7 @@ public class ProtocolLibrary extends JavaPlugin { /** * The maximum version ProtocolLib has been tested with, */ - private static final String MAXIMUM_MINECRAFT_VERSION = "1.5.2"; + private static final String MAXIMUM_MINECRAFT_VERSION = "1.6.1"; /** * The number of milliseconds per second. From 49eb39e45f009f8ba70ae50539cb20a900354f0d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 4 Jul 2013 01:12:39 +0200 Subject: [PATCH 07/20] Improve packet class lookup performance by mainaining a inverse map. --- .../protocol/injector/packet/InverseMaps.java | 130 ++++ .../injector/packet/PacketRegistry.java | 636 +++++++++--------- .../protocol/utility/MinecraftReflection.java | 2 +- 3 files changed, 464 insertions(+), 304 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java new file mode 100644 index 00000000..96f8ed5f --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/InverseMaps.java @@ -0,0 +1,130 @@ +package com.comphenix.protocol.injector.packet; + +import java.lang.reflect.Field; +import java.util.Map; + +import com.comphenix.protocol.reflect.FieldUtils; +import com.google.common.base.Predicate; +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.ForwardingMultimap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; + +public class InverseMaps { + private InverseMaps() { + // Not constructable + } + + public static Multimap inverseMultimap(final Map map, final Predicate> filter) { + final MapContainer container = new MapContainer(map); + + return new ForwardingMultimap() { + // The cached multimap + private Multimap inverseMultimap; + + @Override + protected Multimap delegate() { + if (container.hasChanged()) { + inverseMultimap = HashMultimap.create(); + + // Construct the inverse map + for (Map.Entry entry : map.entrySet()) { + if (filter.apply(entry)) { + inverseMultimap.put(entry.getValue(), entry.getKey()); + } + } + container.setChanged(false); + } + return inverseMultimap; + } + }; + } + + public static Map inverseMap(final Map map, final Predicate> filter) { + final MapContainer container = new MapContainer(map); + + return new ForwardingMap() { + // The cached map + private Map inverseMap; + + @Override + protected Map delegate() { + if (container.hasChanged()) { + inverseMap = Maps.newHashMap(); + + // Construct the inverse map + for (Map.Entry entry : map.entrySet()) { + if (filter.apply(entry)) { + inverseMap.put(entry.getValue(), entry.getKey()); + } + } + container.setChanged(false); + } + return inverseMap; + } + }; + } + + /** + * Represents a class that can detect if a map has changed. + * @author Kristian + */ + private static class MapContainer { + // For detecting changes + private Field modCountField; + private int lastModCount; + + // The object along with whether or not this is the initial run + private Object source; + private boolean changed; + + public MapContainer(Object source) { + this.source = source; + this.changed = true; + this.modCountField = FieldUtils.getField(source.getClass(), "modCount", true); + } + + /** + * Determine if the map has changed. + * @return TRUE if it has, FALSE otherwise. + */ + public boolean hasChanged() { + // Check if unchanged + checkChanged(); + return changed; + } + + /** + * Mark the map as changed or unchanged. + * @param changed - TRUE if the map has changed, FALSE otherwise. + */ + public void setChanged(boolean changed) { + this.changed = changed; + } + + /** + * Check for modifications to the current map. + */ + protected void checkChanged() { + if (!changed) { + if (getModificationCount() != lastModCount) { + lastModCount = getModificationCount(); + changed = true; + } + } + } + + /** + * Retrieve the current modification count. + * @return The current count, or something different than lastModCount if not accessible. + */ + private int getModificationCount() { + try { + return modCountField != null ? modCountField.getInt(source) : lastModCount + 1; + } catch (Exception e) { + throw new RuntimeException("Unable to retrieve modCount.", e); + } + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java index 72d70457..9448aff7 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/PacketRegistry.java @@ -1,303 +1,333 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector.packet; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import net.sf.cglib.proxy.Factory; - -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; -import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.wrappers.TroveWrapper; -import com.google.common.base.Objects; -import com.google.common.collect.ImmutableSet; - -/** - * Static packet registry in Minecraft. - * - * @author Kristian - */ -@SuppressWarnings("rawtypes") -public class PacketRegistry { - public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value."); - - public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s"); - public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s"); - - private static final int MIN_SERVER_PACKETS = 5; - private static final int MIN_CLIENT_PACKETS = 5; - - // Fuzzy reflection - private static FuzzyReflection packetRegistry; - - // The packet class to packet ID translator - private static Map packetToID; - - // Whether or not certain packets are sent by the client or the server - private static ImmutableSet serverPackets; - private static ImmutableSet clientPackets; - - // The underlying sets - private static Set serverPacketsRef; - private static Set clientPacketsRef; - - // New proxy values - private static Map overwrittenPackets = new HashMap(); - - // Vanilla packets - private static Map previousValues = new HashMap(); - - @SuppressWarnings({ "unchecked" }) - public static Map getPacketToID() { - // Initialize it, if we haven't already - if (packetToID == null) { - try { - Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class); - packetToID = (Map) FieldUtils.readStaticField(packetsField, true); - } catch (IllegalArgumentException e) { - // Spigot 1.2.5 MCPC workaround - try { - packetToID = getSpigotWrapper(); - } catch (Exception e2) { - // Very bad indeed - throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2); - } - - } catch (IllegalAccessException e) { - throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e); - } - } - - return packetToID; - } - - private static Map getSpigotWrapper() throws IllegalAccessException { - // If it talks like a duck, etc. - // Perhaps it would be nice to have a proper duck typing library as well - FuzzyClassContract mapLike = FuzzyClassContract.newBuilder(). - method(FuzzyMethodContract.newBuilder(). - nameExact("size").returnTypeExact(int.class)). - method(FuzzyMethodContract.newBuilder(). - nameExact("put").parameterCount(2)). - method(FuzzyMethodContract.newBuilder(). - nameExact("get").parameterCount(1)). - build(); - - Field packetsField = getPacketRegistry().getField( - FuzzyFieldContract.newBuilder().typeMatches(mapLike).build()); - Object troveMap = FieldUtils.readStaticField(packetsField, true); - - // Check for stupid no_entry_values - try { - Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true); - Integer value = (Integer) FieldUtils.readField(field, troveMap, true); - - if (value >= 0 && value < 256) { - // Someone forgot to set the no entry value. Let's help them. - FieldUtils.writeField(field, troveMap, -1); - } - } catch (IllegalArgumentException e) { - // Whatever - ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class, - Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e)); - } - - // We'll assume this a Trove map - return TroveWrapper.getDecoratedMap(troveMap); - } - - /** - * Retrieve the cached fuzzy reflection instance allowing access to the packet registry. - * @return Reflected packet registry. - */ - private static FuzzyReflection getPacketRegistry() { - if (packetRegistry == null) - packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true); - return packetRegistry; - } - - /** - * Retrieve the injected proxy classes handlig each packet ID. - * @return Injected classes. - */ - public static Map getOverwrittenPackets() { - return overwrittenPackets; - } - - /** - * Retrieve the vanilla classes handling each packet ID. - * @return Vanilla classes. - */ - public static Map getPreviousPackets() { - return previousValues; - } - - /** - * Retrieve every known and supported server packet. - * @return An immutable set of every known server packet. - * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft. - */ - public static Set getServerPackets() throws FieldAccessException { - initializeSets(); - - // Sanity check. This is impossible! - if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS) - throw new FieldAccessException("Server packet list is empty. Seems to be unsupported"); - return serverPackets; - } - - /** - * Retrieve every known and supported client packet. - * @return An immutable set of every known client packet. - * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft. - */ - public static Set getClientPackets() throws FieldAccessException { - initializeSets(); - - // As above - if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS) - throw new FieldAccessException("Client packet list is empty. Seems to be unsupported"); - return clientPackets; - } - - @SuppressWarnings("unchecked") - private static void initializeSets() throws FieldAccessException { - if (serverPacketsRef == null || clientPacketsRef == null) { - List sets = getPacketRegistry().getFieldListByType(Set.class); - - try { - if (sets.size() > 1) { - serverPacketsRef = (Set) FieldUtils.readStaticField(sets.get(0), true); - clientPacketsRef = (Set) FieldUtils.readStaticField(sets.get(1), true); - - // Impossible - if (serverPacketsRef == null || clientPacketsRef == null) - throw new FieldAccessException("Packet sets are in an illegal state."); - - // NEVER allow callers to modify the underlying sets - serverPackets = ImmutableSet.copyOf(serverPacketsRef); - clientPackets = ImmutableSet.copyOf(clientPacketsRef); - - // Check sizes - if (serverPackets.size() < MIN_SERVER_PACKETS) - ProtocolLibrary.getErrorReporter().reportWarning( - PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size()) - ); - if (clientPackets.size() < MIN_CLIENT_PACKETS) - ProtocolLibrary.getErrorReporter().reportWarning( - PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size()) - ); - - } else { - throw new FieldAccessException("Cannot retrieve packet client/server sets."); - } - - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot access field.", e); - } - - } else { - // Copy over again if it has changed - if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size()) - serverPackets = ImmutableSet.copyOf(serverPacketsRef); - if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size()) - clientPackets = ImmutableSet.copyOf(clientPacketsRef); - } - } - - /** - * Retrieves the correct packet class from a given packet ID. - * @param packetID - the packet ID. - * @return The associated class. - */ - public static Class getPacketClassFromID(int packetID) { - return getPacketClassFromID(packetID, false); - } - - /** - * 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 static Class getPacketClassFromID(int packetID, boolean forceVanilla) { - - Map lookup = forceVanilla ? previousValues : overwrittenPackets; - - // Optimized lookup - if (lookup.containsKey(packetID)) { - return removeEnhancer(lookup.get(packetID), forceVanilla); - } - - // Will most likely not be used - for (Map.Entry entry : getPacketToID().entrySet()) { - if (Objects.equal(entry.getValue(), packetID)) { - // Attempt to get the vanilla class here too - if (!forceVanilla || MinecraftReflection.isMinecraftClass(entry.getKey())) - return removeEnhancer(entry.getKey(), forceVanilla); - } - } - - throw new IllegalArgumentException("The packet ID " + packetID + " is not registered."); - } - - /** - * Retrieve the packet ID of a given packet. - * @param packet - the type of packet to check. - * @return The ID of the given packet. - * @throws IllegalArgumentException If this is not a valid packet. - */ - public static int getPacketID(Class packet) { - if (packet == null) - throw new IllegalArgumentException("Packet type class cannot be NULL."); - if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet)) - throw new IllegalArgumentException("Type must be a packet."); - - // The registry contains both the overridden and original packets - return getPacketToID().get(packet); - } - - /** - * Find the first superclass that is not a CBLib proxy object. - * @param clazz - the class whose hierachy we're going to search through. - * @param remove - whether or not to skip enhanced (proxy) classes. - * @return If remove is TRUE, the first superclass that is not a proxy. - */ - private static Class removeEnhancer(Class clazz, boolean remove) { - if (remove) { - // Get the underlying vanilla class - while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) { - clazz = clazz.getSuperclass(); - } - } - - return clazz; - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector.packet; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import javax.annotation.Nullable; + +import net.sf.cglib.proxy.Factory; + +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.TroveWrapper; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; + +/** + * Static packet registry in Minecraft. + * + * @author Kristian + */ +@SuppressWarnings("rawtypes") +public class PacketRegistry { + public static final ReportType REPORT_CANNOT_CORRECT_TROVE_MAP = new ReportType("Unable to correct no entry value."); + + public static final ReportType REPORT_INSUFFICIENT_SERVER_PACKETS = new ReportType("Too few server packets detected: %s"); + public static final ReportType REPORT_INSUFFICIENT_CLIENT_PACKETS = new ReportType("Too few client packets detected: %s"); + + private static final int MIN_SERVER_PACKETS = 5; + private static final int MIN_CLIENT_PACKETS = 5; + + // Fuzzy reflection + private static FuzzyReflection packetRegistry; + + // The packet class to packet ID translator + private static Map packetToID; + + // Packet IDs to classes, grouped by whether or not they're vanilla or custom defined + private static Multimap customIdToPacket; + private static Map vanillaIdToPacket; + + // Whether or not certain packets are sent by the client or the server + private static ImmutableSet serverPackets; + private static ImmutableSet clientPackets; + + // The underlying sets + private static Set serverPacketsRef; + private static Set clientPacketsRef; + + // New proxy values + private static Map overwrittenPackets = new HashMap(); + + // Vanilla packets + private static Map previousValues = new HashMap(); + + @SuppressWarnings({ "unchecked" }) + public static Map getPacketToID() { + // Initialize it, if we haven't already + if (packetToID == null) { + try { + Field packetsField = getPacketRegistry().getFieldByType("packetsField", Map.class); + packetToID = (Map) FieldUtils.readStaticField(packetsField, true); + } catch (IllegalArgumentException e) { + // Spigot 1.2.5 MCPC workaround + try { + packetToID = getSpigotWrapper(); + } catch (Exception e2) { + // Very bad indeed + throw new IllegalArgumentException(e.getMessage() + "; Spigot workaround failed.", e2); + } + + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to retrieve the packetClassToIdMap", e); + } + + // Create the inverse maps + customIdToPacket = InverseMaps.inverseMultimap(packetToID, new Predicate>() { + @Override + public boolean apply(@Nullable Entry entry) { + return !MinecraftReflection.isMinecraftClass(entry.getKey()); + } + }); + + // And the vanilla pack - here we assume a unique ID to class mapping + vanillaIdToPacket = InverseMaps.inverseMap(packetToID, new Predicate>() { + @Override + public boolean apply(@Nullable Entry entry) { + return MinecraftReflection.isMinecraftClass(entry.getKey()); + } + }); + } + return packetToID; + } + + private static Map getSpigotWrapper() throws IllegalAccessException { + // If it talks like a duck, etc. + // Perhaps it would be nice to have a proper duck typing library as well + FuzzyClassContract mapLike = FuzzyClassContract.newBuilder(). + method(FuzzyMethodContract.newBuilder(). + nameExact("size").returnTypeExact(int.class)). + method(FuzzyMethodContract.newBuilder(). + nameExact("put").parameterCount(2)). + method(FuzzyMethodContract.newBuilder(). + nameExact("get").parameterCount(1)). + build(); + + Field packetsField = getPacketRegistry().getField( + FuzzyFieldContract.newBuilder().typeMatches(mapLike).build()); + Object troveMap = FieldUtils.readStaticField(packetsField, true); + + // Check for stupid no_entry_values + try { + Field field = FieldUtils.getField(troveMap.getClass(), "no_entry_value", true); + Integer value = (Integer) FieldUtils.readField(field, troveMap, true); + + if (value >= 0 && value < 256) { + // Someone forgot to set the no entry value. Let's help them. + FieldUtils.writeField(field, troveMap, -1); + } + } catch (IllegalArgumentException e) { + // Whatever + ProtocolLibrary.getErrorReporter().reportWarning(PacketRegistry.class, + Report.newBuilder(REPORT_CANNOT_CORRECT_TROVE_MAP).error(e)); + } + + // We'll assume this a Trove map + return TroveWrapper.getDecoratedMap(troveMap); + } + + /** + * Retrieve the cached fuzzy reflection instance allowing access to the packet registry. + * @return Reflected packet registry. + */ + private static FuzzyReflection getPacketRegistry() { + if (packetRegistry == null) + packetRegistry = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass(), true); + return packetRegistry; + } + + /** + * Retrieve the injected proxy classes handlig each packet ID. + * @return Injected classes. + */ + public static Map getOverwrittenPackets() { + return overwrittenPackets; + } + + /** + * Retrieve the vanilla classes handling each packet ID. + * @return Vanilla classes. + */ + public static Map getPreviousPackets() { + return previousValues; + } + + /** + * Retrieve every known and supported server packet. + * @return An immutable set of every known server packet. + * @throws FieldAccessException If we're unable to retrieve the server packet data from Minecraft. + */ + public static Set getServerPackets() throws FieldAccessException { + initializeSets(); + + // Sanity check. This is impossible! + if (serverPackets != null && serverPackets.size() < MIN_SERVER_PACKETS) + throw new FieldAccessException("Server packet list is empty. Seems to be unsupported"); + return serverPackets; + } + + /** + * Retrieve every known and supported client packet. + * @return An immutable set of every known client packet. + * @throws FieldAccessException If we're unable to retrieve the client packet data from Minecraft. + */ + public static Set getClientPackets() throws FieldAccessException { + initializeSets(); + + // As above + if (clientPackets != null && clientPackets.size() < MIN_CLIENT_PACKETS) + throw new FieldAccessException("Client packet list is empty. Seems to be unsupported"); + return clientPackets; + } + + @SuppressWarnings("unchecked") + private static void initializeSets() throws FieldAccessException { + if (serverPacketsRef == null || clientPacketsRef == null) { + List sets = getPacketRegistry().getFieldListByType(Set.class); + + try { + if (sets.size() > 1) { + serverPacketsRef = (Set) FieldUtils.readStaticField(sets.get(0), true); + clientPacketsRef = (Set) FieldUtils.readStaticField(sets.get(1), true); + + // Impossible + if (serverPacketsRef == null || clientPacketsRef == null) + throw new FieldAccessException("Packet sets are in an illegal state."); + + // NEVER allow callers to modify the underlying sets + serverPackets = ImmutableSet.copyOf(serverPacketsRef); + clientPackets = ImmutableSet.copyOf(clientPacketsRef); + + // Check sizes + if (serverPackets.size() < MIN_SERVER_PACKETS) + ProtocolLibrary.getErrorReporter().reportWarning( + PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_SERVER_PACKETS).messageParam(serverPackets.size()) + ); + if (clientPackets.size() < MIN_CLIENT_PACKETS) + ProtocolLibrary.getErrorReporter().reportWarning( + PacketRegistry.class, Report.newBuilder(REPORT_INSUFFICIENT_CLIENT_PACKETS).messageParam(clientPackets.size()) + ); + + } else { + throw new FieldAccessException("Cannot retrieve packet client/server sets."); + } + + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot access field.", e); + } + + } else { + // Copy over again if it has changed + if (serverPacketsRef != null && serverPacketsRef.size() != serverPackets.size()) + serverPackets = ImmutableSet.copyOf(serverPacketsRef); + if (clientPacketsRef != null && clientPacketsRef.size() != clientPackets.size()) + clientPackets = ImmutableSet.copyOf(clientPacketsRef); + } + } + + /** + * Retrieves the correct packet class from a given packet ID. + * @param packetID - the packet ID. + * @return The associated class. + */ + public static Class getPacketClassFromID(int packetID) { + return getPacketClassFromID(packetID, false); + } + + /** + * 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 static Class getPacketClassFromID(int packetID, boolean forceVanilla) { + Map lookup = forceVanilla ? previousValues : overwrittenPackets; + Class result = null; + + // Optimized lookup + if (lookup.containsKey(packetID)) { + return removeEnhancer(lookup.get(packetID), forceVanilla); + } + + // Refresh lookup tables + getPacketToID(); + + // See if we can look for non-vanilla classes + if (!forceVanilla) { + result = Iterables.getFirst(customIdToPacket.get(packetID), null); + } + if (result == null) { + result = vanillaIdToPacket.get(packetID); + } + + // See if we got it + if (result != null) + return result; + else + throw new IllegalArgumentException("The packet ID " + packetID + " is not registered."); + } + + /** + * Retrieve the packet ID of a given packet. + * @param packet - the type of packet to check. + * @return The ID of the given packet. + * @throws IllegalArgumentException If this is not a valid packet. + */ + public static int getPacketID(Class packet) { + if (packet == null) + throw new IllegalArgumentException("Packet type class cannot be NULL."); + if (!MinecraftReflection.getPacketClass().isAssignableFrom(packet)) + throw new IllegalArgumentException("Type must be a packet."); + + // The registry contains both the overridden and original packets + return getPacketToID().get(packet); + } + + /** + * Find the first superclass that is not a CBLib proxy object. + * @param clazz - the class whose hierachy we're going to search through. + * @param remove - whether or not to skip enhanced (proxy) classes. + * @return If remove is TRUE, the first superclass that is not a proxy. + */ + private static Class removeEnhancer(Class clazz, boolean remove) { + if (remove) { + // Get the underlying vanilla class + while (Factory.class.isAssignableFrom(clazz) && !clazz.equals(Object.class)) { + clazz = clazz.getSuperclass(); + } + } + + return clazz; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 9261f7bb..cd6af1d2 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -287,7 +287,7 @@ public class MinecraftReflection { public static boolean isMinecraftClass(@Nonnull Class clazz) { if (clazz == null) throw new IllegalArgumentException("Class cannot be NULL."); - + return getMinecraftObjectMatcher().isMatch(clazz, null); } From 1159b4541ece955a264e21ea87f307d37d356e20 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 5 Jul 2013 06:14:57 +0200 Subject: [PATCH 08/20] Increment to 2.4.7 for Minecraft 1.6.1 support. --- 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 39b96deb..9aa0f273 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 2.4.6-SNAPSHOT + 2.4.7 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 0543ab74..5970db6d 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 2.4.6-SNAPSHOT +version: 2.4.7 description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 81e158d74a97b41f0d9ffae1cbef3876ffc8f00c Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Fri, 5 Jul 2013 06:23:40 +0200 Subject: [PATCH 09/20] Increment to 2.4.8-SNAPSHOT for development towards the next version. --- ProtocolLib/pom.xml | 2 +- ProtocolLib/src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 9aa0f273..4ef25e3f 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.comphenix.protocol ProtocolLib - 2.4.7 + 2.4.8-SNAPSHOT jar Provides read/write access to the Minecraft protocol. diff --git a/ProtocolLib/src/main/resources/plugin.yml b/ProtocolLib/src/main/resources/plugin.yml index 5970db6d..cfc25b27 100644 --- a/ProtocolLib/src/main/resources/plugin.yml +++ b/ProtocolLib/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ProtocolLib -version: 2.4.7 +version: 2.4.8-SNAPSHOT description: Provides read/write access to the Minecraft protocol. author: Comphenix website: http://www.comphenix.net/ProtocolLib From 5e35f46b967fc30ab88472af5fe67540292cfdce Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 6 Jul 2013 00:24:11 +0200 Subject: [PATCH 10/20] Added support for Spigot with Netty disabled. I've also added BukkitExecutors to ProtocolLib, which any plugin that depend on ProtocolLib may now use. --- ProtocolLib/pom.xml | 6 + .../comphenix/protocol/ProtocolLibrary.java | 10 +- .../protocol/async/AsyncFilterManager.java | 26 +- .../protocol/injector/BukkitUnwrapper.java | 426 +++++----- .../injector/DelayedPacketManager.java | 385 +++++++++ .../protocol/injector/InternalManager.java | 38 + .../protocol/injector/ListenerInvoker.java | 134 ++-- .../injector/PacketFilterManager.java | 190 +++-- .../player/InjectedServerConnection.java | 754 ++++++++++-------- .../injector/player/NetLoginInjector.java | 308 +++---- .../player/PlayerInjectionHandler.java | 5 - .../player/ProxyPlayerInjectionHandler.java | 8 - .../server/AbstractInputStreamLookup.java | 167 ++-- .../injector/server/BukkitSocketInjector.java | 30 +- .../server/InputStreamReflectLookup.java | 377 +++++---- .../injector/server/QueuedSendPacket.java | 31 + .../server/TemporaryPlayerFactory.java | 394 ++++----- .../injector/spigot/DummyPlayerHandler.java | 7 +- 18 files changed, 1921 insertions(+), 1375 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/InternalManager.java create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 4ef25e3f..ddbd5aca 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -200,6 +200,12 @@ 2.2.2 compile + + com.comphenix.executors + BukkitExecutors + 1.0.0 + compile + org.bukkit craftbukkit diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 962532c9..4677a5f8 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -41,6 +41,7 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.injector.DelayedSingleTask; +import com.comphenix.protocol.injector.InternalManager; import com.comphenix.protocol.injector.PacketFilterManager; import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; import com.comphenix.protocol.metrics.Statistics; @@ -95,7 +96,7 @@ public class ProtocolLibrary extends JavaPlugin { 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; + private static InternalManager protocolManager; // Error reporter private static ErrorReporter reporter = new BasicErrorReporter(); @@ -172,7 +173,7 @@ public class ProtocolLibrary extends JavaPlugin { updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); unhookTask = new DelayedSingleTask(this); - protocolManager = new PacketFilterManager( + protocolManager = PacketFilterManager.createManager( getClassLoader(), getServer(), this, version, unhookTask, reporter); // Setup error reporter @@ -181,7 +182,7 @@ public class ProtocolLibrary extends JavaPlugin { // Update injection hook try { PlayerInjectHooks hook = config.getInjectionMethod(); - + // Only update the hook if it's different if (!protocolManager.getPlayerHook().equals(hook)) { logger.info("Changing player hook from " + protocolManager.getPlayerHook() + " to " + hook); @@ -302,9 +303,6 @@ public class ProtocolLibrary extends JavaPlugin { return; } - // Perform logic when the world has loaded - protocolManager.postWorldLoaded(); - // Initialize background compiler if (backgroundCompiler == null && config.isBackgroundCompilerEnabled()) { backgroundCompiler = new BackgroundCompiler(getClassLoader(), reporter); 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 1b3bd8cc..6d8c1345 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/async/AsyncFilterManager.java @@ -69,12 +69,12 @@ public class AsyncFilterManager implements AsynchronousManager { // Default scheduler private final BukkitScheduler scheduler; - // Our protocol manager - private final ProtocolManager manager; - // Current packet index private final AtomicInteger currentSendingIndex = new AtomicInteger(); + // Our protocol manager + private ProtocolManager manager; + /** * Initialize a asynchronous filter manager. *

@@ -83,7 +83,7 @@ public class AsyncFilterManager implements AsynchronousManager { * @param scheduler - task scheduler. * @param manager - protocol manager. */ - public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler, ProtocolManager manager) { + public AsyncFilterManager(ErrorReporter reporter, BukkitScheduler scheduler) { // Initialize timeout listeners this.serverTimeoutListeners = new SortedPacketListenerList(); this.clientTimeoutListeners = new SortedPacketListenerList(); @@ -95,12 +95,26 @@ public class AsyncFilterManager implements AsynchronousManager { this.playerSendingHandler.initializeScheduler(); this.scheduler = scheduler; - this.manager = manager; - this.reporter = reporter; this.mainThread = Thread.currentThread(); } + /** + * Retrieve the protocol manager. + * @return The protocol manager. + */ + public ProtocolManager getManager() { + return manager; + } + + /** + * Set the associated protocol manager. + * @param manager - the new manager. + */ + public void setManager(ProtocolManager manager) { + this.manager = manager; + } + @Override public AsyncListenerHandler registerAsyncHandler(PacketListener listener) { return registerAsyncHandler(listener, true); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java index 7a1a724d..b7db2d7f 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/BukkitUnwrapper.java @@ -1,213 +1,213 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.injector.PacketConstructor.Unwrapper; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.instances.DefaultInstances; -import com.google.common.primitives.Primitives; - -/** - * Represents an object capable of converting wrapped Bukkit objects into NMS objects. - *

- * Typical conversions include: - *

    - *
  • org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer
  • - *
  • org.bukkit.World -> net.minecraft.server.WorldServer
  • - *
- * - * @author Kristian - */ -public class BukkitUnwrapper implements Unwrapper { - public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument."); - public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation."); - public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method."); - - public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'."); - - private static Map, Unwrapper> unwrapperCache = new ConcurrentHashMap, Unwrapper>(); - - // The current error reporter - private final ErrorReporter reporter; - - /** - * Construct a new Bukkit unwrapper with ProtocolLib's default error reporter. - */ - public BukkitUnwrapper() { - this(ProtocolLibrary.getErrorReporter()); - } - - /** - * Construct a new Bukkit unwrapper with the given error reporter. - * @param reporter - the error reporter to use. - */ - public BukkitUnwrapper(ErrorReporter reporter) { - this.reporter = reporter; - } - - @SuppressWarnings("unchecked") - @Override - public Object unwrapItem(Object wrappedObject) { - // Special case - if (wrappedObject == null) - return null; - Class currentClass = wrappedObject.getClass(); - - // Next, check for types that doesn't have a getHandle() - if (wrappedObject instanceof Collection) { - return handleCollection((Collection) wrappedObject); - } else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) { - return null; - } - - Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass); - - // Retrieve the handle - if (specificUnwrapper != null) - return specificUnwrapper.unwrapItem(wrappedObject); - else - return null; - } - - // Handle a collection of items - private Object handleCollection(Collection wrappedObject) { - - @SuppressWarnings("unchecked") - Collection copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass()); - - if (copy != null) { - // Unwrap every element - for (Object element : wrappedObject) { - copy.add(unwrapItem(element)); - } - return copy; - - } else { - // Impossible - return null; - } - } - - /** - * Retrieve a cached class unwrapper for the given class. - * @param type - the type of the class. - * @return An unwrapper for the given class. - */ - private Unwrapper getSpecificUnwrapper(Class type) { - // See if we're already determined this - if (unwrapperCache.containsKey(type)) { - // We will never remove from the cache, so this ought to be thread safe - return unwrapperCache.get(type); - } - - try { - final Method find = type.getMethod("getHandle"); - - // It's thread safe, as getMethod should return the same handle - Unwrapper methodUnwrapper = new Unwrapper() { - @Override - public Object unwrapItem(Object wrappedObject) { - - try { - return find.invoke(wrappedObject); - - } catch (IllegalArgumentException e) { - reporter.reportDetailed(this, - Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find) - ); - } catch (IllegalAccessException e) { - // Should not occur either - return null; - } catch (InvocationTargetException e) { - // This is really bad - throw new RuntimeException("Minecraft error.", e); - } - - return null; - } - }; - - unwrapperCache.put(type, methodUnwrapper); - return methodUnwrapper; - - } catch (SecurityException e) { - reporter.reportDetailed(this, - Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type) - ); - } catch (NoSuchMethodException e) { - // Try getting the field unwrapper too - Unwrapper fieldUnwrapper = getFieldUnwrapper(type); - - if (fieldUnwrapper != null) - return fieldUnwrapper; - else - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type)); - } - - // Default method - return null; - } - - /** - * Retrieve a cached unwrapper using the handle field. - * @param type - a cached field unwrapper. - * @return The cached field unwrapper. - */ - private Unwrapper getFieldUnwrapper(Class type) { - final Field find = FieldUtils.getField(type, "handle", true); - - // See if we succeeded - if (find != null) { - Unwrapper fieldUnwrapper = new Unwrapper() { - @Override - public Object unwrapItem(Object wrappedObject) { - try { - return FieldUtils.readField(find, wrappedObject, true); - } catch (IllegalAccessException e) { - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find) - ); - return null; - } - } - }; - - unwrapperCache.put(type, fieldUnwrapper); - return fieldUnwrapper; - - } else { - // Inform about this too - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find) - ); - return null; - } - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.injector.PacketConstructor.Unwrapper; +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.instances.DefaultInstances; +import com.google.common.primitives.Primitives; + +/** + * Represents an object capable of converting wrapped Bukkit objects into NMS objects. + *

+ * Typical conversions include: + *

    + *
  • org.bukkit.entity.Player -> net.minecraft.server.EntityPlayer
  • + *
  • org.bukkit.World -> net.minecraft.server.WorldServer
  • + *
+ * + * @author Kristian + */ +public class BukkitUnwrapper implements Unwrapper { + public static final ReportType REPORT_ILLEGAL_ARGUMENT = new ReportType("Illegal argument."); + public static final ReportType REPORT_SECURITY_LIMITATION = new ReportType("Security limitation."); + public static final ReportType REPORT_CANNOT_FIND_UNWRAP_METHOD = new ReportType("Cannot find method."); + + public static final ReportType REPORT_CANNOT_READ_FIELD_HANDLE = new ReportType("Cannot read field 'handle'."); + + private static Map, Unwrapper> unwrapperCache = new ConcurrentHashMap, Unwrapper>(); + + // The current error reporter + private final ErrorReporter reporter; + + /** + * Construct a new Bukkit unwrapper with ProtocolLib's default error reporter. + */ + public BukkitUnwrapper() { + this(ProtocolLibrary.getErrorReporter()); + } + + /** + * Construct a new Bukkit unwrapper with the given error reporter. + * @param reporter - the error reporter to use. + */ + public BukkitUnwrapper(ErrorReporter reporter) { + this.reporter = reporter; + } + + @SuppressWarnings("unchecked") + @Override + public Object unwrapItem(Object wrappedObject) { + // Special case + if (wrappedObject == null) + return null; + Class currentClass = wrappedObject.getClass(); + + // Next, check for types that doesn't have a getHandle() + if (wrappedObject instanceof Collection) { + return handleCollection((Collection) wrappedObject); + } else if (Primitives.isWrapperType(currentClass) || wrappedObject instanceof String) { + return null; + } + + Unwrapper specificUnwrapper = getSpecificUnwrapper(currentClass); + + // Retrieve the handle + if (specificUnwrapper != null) + return specificUnwrapper.unwrapItem(wrappedObject); + else + return null; + } + + // Handle a collection of items + private Object handleCollection(Collection wrappedObject) { + + @SuppressWarnings("unchecked") + Collection copy = DefaultInstances.DEFAULT.getDefault(wrappedObject.getClass()); + + if (copy != null) { + // Unwrap every element + for (Object element : wrappedObject) { + copy.add(unwrapItem(element)); + } + return copy; + + } else { + // Impossible + return null; + } + } + + /** + * Retrieve a cached class unwrapper for the given class. + * @param type - the type of the class. + * @return An unwrapper for the given class. + */ + private Unwrapper getSpecificUnwrapper(Class type) { + // See if we're already determined this + if (unwrapperCache.containsKey(type)) { + // We will never remove from the cache, so this ought to be thread safe + return unwrapperCache.get(type); + } + + try { + final Method find = type.getMethod("getHandle"); + + // It's thread safe, as getMethod should return the same handle + Unwrapper methodUnwrapper = new Unwrapper() { + @Override + public Object unwrapItem(Object wrappedObject) { + + try { + return find.invoke(wrappedObject); + + } catch (IllegalArgumentException e) { + reporter.reportDetailed(this, + Report.newBuilder(REPORT_ILLEGAL_ARGUMENT).error(e).callerParam(wrappedObject, find) + ); + } catch (IllegalAccessException e) { + // Should not occur either + return null; + } catch (InvocationTargetException e) { + // This is really bad + throw new RuntimeException("Minecraft error.", e); + } + + return null; + } + }; + + unwrapperCache.put(type, methodUnwrapper); + return methodUnwrapper; + + } catch (SecurityException e) { + reporter.reportDetailed(this, + Report.newBuilder(REPORT_SECURITY_LIMITATION).error(e).callerParam(type) + ); + } catch (NoSuchMethodException e) { + // Try getting the field unwrapper too + Unwrapper fieldUnwrapper = getFieldUnwrapper(type); + + if (fieldUnwrapper != null) + return fieldUnwrapper; + else + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_FIND_UNWRAP_METHOD).error(e).callerParam(type)); + } + + // Default method + return null; + } + + /** + * Retrieve a cached unwrapper using the handle field. + * @param type - a cached field unwrapper. + * @return The cached field unwrapper. + */ + private Unwrapper getFieldUnwrapper(Class type) { + final Field find = FieldUtils.getField(type, "handle", true); + + // See if we succeeded + if (find != null) { + Unwrapper fieldUnwrapper = new Unwrapper() { + @Override + public Object unwrapItem(Object wrappedObject) { + try { + return FieldUtils.readField(find, wrappedObject, true); + } catch (IllegalAccessException e) { + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).error(e).callerParam(wrappedObject, find) + ); + return null; + } + } + }; + + unwrapperCache.put(type, fieldUnwrapper); + return fieldUnwrapper; + + } else { + // Inform about this too + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_READ_FIELD_HANDLE).callerParam(find) + ); + return null; + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java new file mode 100644 index 00000000..eaef213f --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java @@ -0,0 +1,385 @@ +package com.comphenix.protocol.injector; + +import java.lang.reflect.InvocationTargetException; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; + +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; + +import com.comphenix.protocol.AsynchronousManager; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.events.ConnectionSide; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketListener; +import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +/** + * A protocol manager that delays all packet listener registrations and unregistrations until + * an underlying protocol manager can be constructed. + * + * @author Kristian + */ +public class DelayedPacketManager implements ProtocolManager, InternalManager { + // Registering packet IDs that are not supported + public static final ReportType REPORT_CANNOT_SEND_QUEUED_PACKET = new ReportType("Cannot send queued packet %s."); + public static final ReportType REPORT_CANNOT_REGISTER_QUEUED_LISTENER = new ReportType("Cannot register queued listener %s."); + + /** + * Represents a packet that will be transmitted later. + * @author Kristian + * + */ + private static class QueuedPacket { + private final Player player; + private final PacketContainer packet; + private final boolean filtered; + private final ConnectionSide side; + + public QueuedPacket(Player player, PacketContainer packet, boolean filtered, ConnectionSide side) { + this.player = player; + this.packet = packet; + this.filtered = filtered; + this.side = side; + } + + /** + * Retrieve the packet that will be transmitted or receieved. + * @return The packet. + */ + public PacketContainer getPacket() { + return packet; + } + + /** + * Retrieve the player that will send or recieve the packet. + * @return The source. + */ + public Player getPlayer() { + return player; + } + + /** + * Retrieve whether or not the packet will the sent or received. + * @return The connection side. + */ + public ConnectionSide getSide() { + return side; + } + + /** + * Determine if the packet should be intercepted by packet listeners. + * @return TRUE if it should, FALSE otherwise. + */ + public boolean isFiltered() { + return filtered; + } + } + + private volatile InternalManager delegate; + + // Packet listeners that will be registered + private final Set queuedListeners = Sets.newSetFromMap(Maps.newConcurrentMap()); + private final List queuedPackets = Collections.synchronizedList(Lists.newArrayList()); + + private AsynchronousManager asyncManager; + private ErrorReporter reporter; + + // The current hook + private PlayerInjectHooks hook = PlayerInjectHooks.NETWORK_SERVER_OBJECT; + + // If we have been closed + private boolean closed; + + // Queued registration + private PluginManager queuedManager; + private Plugin queuedPlugin; + + public DelayedPacketManager(@Nonnull ErrorReporter reporter) { + Preconditions.checkNotNull(reporter, "reporter cannot be NULL."); + + this.reporter = reporter; + } + + /** + * Retrieve the underlying protocol manager. + * @return The underlying manager. + */ + public InternalManager getDelegate() { + return delegate; + } + + /** + * Update the delegate to the underlying manager. + *

+ * This will prompt this packet manager to immediately transmit and + * register all queued packets an listeners. + * @param delegate - delegate to the new manager. + */ + protected void setDelegate(InternalManager delegate) { + this.delegate = delegate; + + if (delegate != null) { + // Update the hook if needed + if (!Objects.equal(delegate.getPlayerHook(), hook)) { + delegate.setPlayerHook(hook); + } + // Register events as well + if (queuedManager != null && queuedPlugin != null) { + delegate.registerEvents(queuedManager, queuedPlugin); + } + + for (PacketListener listener : queuedListeners) { + try { + delegate.addPacketListener(listener); + } catch (IllegalArgumentException e) { + // Inform about this plugin error + reporter.reportWarning(this, + Report.newBuilder(REPORT_CANNOT_REGISTER_QUEUED_LISTENER). + callerParam(delegate).messageParam(listener).error(e)); + } + } + + synchronized (queuedPackets) { + for (QueuedPacket packet : queuedPackets) { + try { + // Attempt to send it now + switch (packet.getSide()) { + case CLIENT_SIDE: + delegate.recieveClientPacket(packet.getPlayer(), packet.getPacket(), packet.isFiltered()); + break; + case SERVER_SIDE: + delegate.sendServerPacket(packet.getPlayer(), packet.getPacket(), packet.isFiltered()); + break; + default: + + } + } catch (Exception e) { + // Inform about this plugin error + reporter.reportWarning(this, + Report.newBuilder(REPORT_CANNOT_SEND_QUEUED_PACKET). + callerParam(delegate).messageParam(packet).error(e)); + } + } + } + + // Don't keep this around anymore + queuedListeners.clear(); + queuedPackets.clear(); + } + } + + @Override + public void setPlayerHook(PlayerInjectHooks playerHook) { + this.hook = playerHook; + } + + @Override + public PlayerInjectHooks getPlayerHook() { + return hook; + } + + @Override + public void sendServerPacket(Player reciever, PacketContainer packet) throws InvocationTargetException { + sendServerPacket(reciever, packet, true); + } + + @Override + public void sendServerPacket(Player reciever, PacketContainer packet, boolean filters) throws InvocationTargetException { + if (delegate != null) { + delegate.sendServerPacket(reciever, packet, filters); + } else { + queuedPackets.add(new QueuedPacket(reciever, packet, filters, ConnectionSide.SERVER_SIDE)); + } + } + + @Override + public void recieveClientPacket(Player sender, PacketContainer packet) throws IllegalAccessException, InvocationTargetException { + recieveClientPacket(sender, packet, true); + } + + @Override + public void recieveClientPacket(Player sender, PacketContainer packet, boolean filters) throws IllegalAccessException, InvocationTargetException { + if (delegate != null) { + delegate.recieveClientPacket(sender, packet, filters); + } else { + queuedPackets.add(new QueuedPacket(sender, packet, filters, ConnectionSide.CLIENT_SIDE)); + } + } + + @Override + public ImmutableSet getPacketListeners() { + if (delegate != null) + return delegate.getPacketListeners(); + else + return ImmutableSet.copyOf(queuedListeners); + } + + @Override + public void addPacketListener(PacketListener listener) { + if (delegate != null) + delegate.addPacketListener(listener); + else + queuedListeners.add(listener); + } + + @Override + public void removePacketListener(PacketListener listener) { + if (delegate != null) + delegate.removePacketListener(listener); + else + queuedListeners.remove(listener); + } + + @Override + public void removePacketListeners(Plugin plugin) { + if (delegate != null) { + delegate.removePacketListeners(plugin); + } else { + for (Iterator it = queuedListeners.iterator(); it.hasNext(); ) { + // Remove listeners of the same plugin + if (Objects.equal(it.next().getPlugin(), plugin)) { + it.remove(); + } + } + } + } + + @Override + public PacketContainer createPacket(int id) { + if (delegate != null) + return delegate.createPacket(id); + return createPacket(id, true); + } + + @Override + public PacketContainer createPacket(int id, boolean forceDefaults) { + if (delegate != null) { + return delegate.createPacket(id); + } else { + // Fallback implementation + PacketContainer packet = new PacketContainer(id); + + // Use any default values if possible + if (forceDefaults) { + try { + packet.getModifier().writeDefaults(); + } catch (FieldAccessException e) { + throw new RuntimeException("Security exception.", e); + } + } + return packet; + } + } + + @Override + public PacketConstructor createPacketConstructor(int id, Object... arguments) { + if (delegate != null) + return delegate.createPacketConstructor(id, arguments); + else + return PacketConstructor.DEFAULT.withPacket(id, arguments); + } + + @Override + public Set getSendingFilters() { + if (delegate != null) { + return delegate.getSendingFilters(); + } else { + // Linear scan is fast enough here + Set sending = Sets.newHashSet(); + + for (PacketListener listener : queuedListeners) { + sending.addAll(listener.getSendingWhitelist().getWhitelist()); + } + return sending; + } + } + + @Override + public Set getReceivingFilters() { + if (delegate != null) { + return delegate.getReceivingFilters(); + } else { + Set recieving = Sets.newHashSet(); + + for (PacketListener listener : queuedListeners) { + recieving.addAll(listener.getReceivingWhitelist().getWhitelist()); + } + return recieving; + } + } + + @Override + public void updateEntity(Entity entity, List observers) throws FieldAccessException { + if (delegate != null) + delegate.updateEntity(entity, observers); + else + EntityUtilities.updateEntity(entity, observers); + } + + @Override + public Entity getEntityFromID(World container, int id) throws FieldAccessException { + if (delegate != null) + return delegate.getEntityFromID(container, id); + else + return EntityUtilities.getEntityFromID(container, id); + } + + @Override + public List getEntityTrackers(Entity entity) throws FieldAccessException { + if (delegate != null) + return delegate.getEntityTrackers(entity); + else + return EntityUtilities.getEntityTrackers(entity); + } + + @Override + public boolean isClosed() { + return closed || (delegate != null && delegate.isClosed()); + } + + @Override + public AsynchronousManager getAsynchronousManager() { + if (delegate != null) + return delegate.getAsynchronousManager(); + else + return asyncManager; + } + + public void setAsynchronousManager(AsynchronousManager asyncManager) { + this.asyncManager = asyncManager; + } + + @Override + public void registerEvents(PluginManager manager, Plugin plugin) { + if (delegate != null) { + delegate.registerEvents(manager, plugin); + } else { + queuedManager = manager; + queuedPlugin = plugin; + } + } + + @Override + public void close() { + if (delegate != null) + delegate.close(); + closed = true; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/InternalManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/InternalManager.java new file mode 100644 index 00000000..fe37d341 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/InternalManager.java @@ -0,0 +1,38 @@ +package com.comphenix.protocol.injector; + +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginManager; + +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.injector.PacketFilterManager.PlayerInjectHooks; + +/** + * Yields access to the internal hook configuration. + * + * @author Kristian + */ +public interface InternalManager extends ProtocolManager { + /** + * Retrieves how the server packets are read. + * @return Injection method for reading server packets. + */ + public PlayerInjectHooks getPlayerHook(); + + /** + * Sets how the server packets are read. + * @param playerHook - the new injection method for reading server packets. + */ + public void setPlayerHook(PlayerInjectHooks playerHook); + + /** + * Register this protocol manager on Bukkit. + * @param manager - Bukkit plugin manager that provides player join/leave events. + * @param plugin - the parent plugin. + */ + public void registerEvents(PluginManager manager, final Plugin plugin); + + /** + * Called when ProtocolLib is closing. + */ + public void close(); +} 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 52b702fc..e0da7b83 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java @@ -1,68 +1,68 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector; - -import com.comphenix.protocol.events.PacketEvent; - -/** - * Represents an object that initiate the packet listeners. - * - * @author Kristian - */ -public interface ListenerInvoker { - - /** - * Invokes the given packet event for every registered listener. - * @param event - the packet event to invoke. - */ - public abstract void invokePacketRecieving(PacketEvent event); - - /** - * Invokes the given packet event for every registered listener. - * @param event - the packet event to invoke. - */ - public abstract void invokePacketSending(PacketEvent event); - - /** - * Retrieve the associated ID of a packet. - * @param packet - the packet. - * @return The packet ID. - */ - public abstract int getPacketID(Object packet); - - /** - * Associate a given class with the given packet ID. Internal method. - * @param clazz - class to associate. - */ - public abstract void unregisterPacketClass(Class clazz); - - /** - * Register a given class in the packet registry. Internal method. - * @param clazz - class to register. - * @param packetID - the the new associated packet ID. - */ - 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); +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector; + +import com.comphenix.protocol.events.PacketEvent; + +/** + * Represents an object that initiate the packet listeners. + * + * @author Kristian + */ +public interface ListenerInvoker { + + /** + * Invokes the given packet event for every registered listener. + * @param event - the packet event to invoke. + */ + public abstract void invokePacketRecieving(PacketEvent event); + + /** + * Invokes the given packet event for every registered listener. + * @param event - the packet event to invoke. + */ + public abstract void invokePacketSending(PacketEvent event); + + /** + * Retrieve the associated ID of a packet. + * @param packet - the packet. + * @return The packet ID. + */ + public abstract int getPacketID(Object packet); + + /** + * Associate a given class with the given packet ID. Internal method. + * @param clazz - class to associate. + */ + public abstract void unregisterPacketClass(Class clazz); + + /** + * Register a given class in the packet registry. Internal method. + * @param clazz - class to register. + * @param packetID - the the new associated packet ID. + */ + 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/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index b8ff35f5..c40fd81d 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -42,9 +42,11 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.PluginDisableEvent; +import org.bukkit.event.world.WorldInitEvent; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; +import com.comphenix.executors.BukkitFutures; import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.async.AsyncFilterManager; @@ -56,6 +58,7 @@ import com.comphenix.protocol.events.*; import com.comphenix.protocol.injector.packet.PacketInjector; import com.comphenix.protocol.injector.packet.PacketInjectorBuilder; import com.comphenix.protocol.injector.packet.PacketRegistry; +import com.comphenix.protocol.injector.player.InjectedServerConnection; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectorBuilder; import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; @@ -67,8 +70,11 @@ import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; -public final class PacketFilterManager implements ProtocolManager, ListenerInvoker { +public final class PacketFilterManager implements ProtocolManager, ListenerInvoker, InternalManager { + public static final ReportType REPORT_CANNOT_LOAD_PACKET_LIST = new ReportType("Cannot load server and client packet list."); public static final ReportType REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR = new ReportType("Unable to initialize packet injector"); @@ -87,6 +93,8 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin."); public static final ReportType REPORT_PLUGIN_VERIFIER_ERROR = new ReportType("Verifier error: %s"); + public static final ReportType REPORT_TEMPORARY_EVENT_ERROR = new ReportType("Unable to register or handle temporary event."); + /** * Sets the inject hook type. Different types allow for maximum compatibility. * @author Kristian @@ -173,23 +181,31 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok /** * Only create instances of this class if protocol lib is disabled. */ - public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library, DelayedSingleTask unhookTask, ErrorReporter reporter) { - this(classLoader, server, library, new MinecraftVersion(server), unhookTask, reporter); - } - - /** - * Only create instances of this class if protocol lib is disabled. - */ - public PacketFilterManager(ClassLoader classLoader, Server server, Plugin library, - MinecraftVersion mcVersion, DelayedSingleTask unhookTask, ErrorReporter reporter) { + public PacketFilterManager( + ClassLoader classLoader, Server server, Plugin library, + AsyncFilterManager asyncManager, MinecraftVersion mcVersion, + final DelayedSingleTask unhookTask, + ErrorReporter reporter, boolean nettyEnabled) { if (reporter == null) throw new IllegalArgumentException("reporter cannot be NULL."); if (classLoader == null) throw new IllegalArgumentException("classLoader cannot be NULL."); - // Just boilerplate - final DelayedSingleTask finalUnhookTask = unhookTask; + // Used to determine if injection is needed + Predicate isInjectionNecessary = new Predicate() { + @Override + public boolean apply(@Nullable GamePhase phase) { + boolean result = true; + + if (phase.hasLogin()) + result &= getPhaseLoginCount() > 0; + // Note that we will still hook players if the unhooking has been delayed + if (phase.hasPlaying()) + result &= getPhasePlayingCount() > 0 || unhookTask.isRunning(); + return result; + } + }; // Listener containers this.recievedListeners = new SortedPacketListenerList(); @@ -204,70 +220,99 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // The plugin verifier this.pluginVerifier = new PluginVerifier(library); - // Used to determine if injection is needed - Predicate isInjectionNecessary = new Predicate() { - @Override - public boolean apply(@Nullable GamePhase phase) { - boolean result = true; - - if (phase.hasLogin()) - result &= getPhaseLoginCount() > 0; - // Note that we will still hook players if the unhooking has been delayed - if (phase.hasPlaying()) - result &= getPhasePlayingCount() > 0 || finalUnhookTask.isRunning(); - return result; - } - }; + // Use the correct injection type + if (nettyEnabled) { + spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server); + this.playerInjection = spigotInjector.getPlayerHandler(); + this.packetInjector = spigotInjector.getPacketInjector(); + + } else { + // Initialize standard injection mangers + this.playerInjection = PlayerInjectorBuilder.newBuilder(). + invoker(this). + server(server). + reporter(reporter). + classLoader(classLoader). + packetListeners(packetListeners). + injectionFilter(isInjectionNecessary). + version(mcVersion). + buildHandler(); + this.packetInjector = PacketInjectorBuilder.newBuilder(). + invoker(this). + reporter(reporter). + classLoader(classLoader). + playerInjection(playerInjection). + buildInjector(); + } + this.asyncFilterManager = asyncManager; + + // Attempt to load the list of server and client packets try { - // Spigot - if (SpigotPacketInjector.canUseSpigotListener()) { - spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server); - this.playerInjection = spigotInjector.getPlayerHandler(); - this.packetInjector = spigotInjector.getPacketInjector(); - - } else { - // Initialize standard injection mangers - this.playerInjection = PlayerInjectorBuilder.newBuilder(). - invoker(this). - server(server). - reporter(reporter). - classLoader(classLoader). - packetListeners(packetListeners). - injectionFilter(isInjectionNecessary). - version(mcVersion). - buildHandler(); - - this.packetInjector = PacketInjectorBuilder.newBuilder(). - invoker(this). - reporter(reporter). - classLoader(classLoader). - playerInjection(playerInjection). - buildInjector(); - } - - this.asyncFilterManager = new AsyncFilterManager(reporter, server.getScheduler(), this); - - // Attempt to load the list of server and client packets - try { - knowsServerPackets = PacketRegistry.getServerPackets() != null; - knowsClientPackets = PacketRegistry.getClientPackets() != null; - } catch (FieldAccessException e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e)); - } - + knowsServerPackets = PacketRegistry.getServerPackets() != null; + knowsClientPackets = PacketRegistry.getClientPackets() != null; } catch (FieldAccessException e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_INITIALIZE_PACKET_INJECTOR).error(e)); + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e)); } } - /** - * Initiate logic that is performed after the world has loaded. - */ - public void postWorldLoaded() { - playerInjection.postWorldLoaded(); + public static InternalManager createManager( + final ClassLoader classLoader, final Server server, final Plugin library, + final MinecraftVersion mcVersion, final DelayedSingleTask unhookTask, + final ErrorReporter reporter) { + + final AsyncFilterManager asyncManager = new AsyncFilterManager(reporter, server.getScheduler()); + + // Spigot + if (SpigotPacketInjector.canUseSpigotListener()) { + // We need to delay this until we know if Netty is enabled + final DelayedPacketManager delayed = new DelayedPacketManager(reporter); + + Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), new FutureCallback() { + @Override + public void onSuccess(WorldInitEvent event) { + // Nevermind + if (delayed.isClosed()) + return; + + try { + // Now we are probably able to check for Netty + InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null); + Object connection = inspector.getServerConnection(); + + // Use netty if we have a non-standard ServerConnection class + boolean useNetty = !MinecraftReflection.isMinecraftObject(connection); + + // Switch to the standard manager + delayed.setDelegate(new PacketFilterManager( + classLoader, server, library, asyncManager, mcVersion, unhookTask, reporter, useNetty) + ); + // Reference this manager directly + asyncManager.setManager(delayed.getDelegate()); + + } catch (Exception e) { + onFailure(e); + } + } + + @Override + public void onFailure(Throwable error) { + reporter.reportWarning(this, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); + } + }); + + // Let plugins use this version instead + return delayed; + } else { + // The standard manager + PacketFilterManager manager = new PacketFilterManager( + classLoader, server, library, asyncManager, mcVersion, unhookTask, reporter, false); + + asyncManager.setManager(manager); + return manager; + } } - + @Override public AsynchronousManager getAsynchronousManager() { return asyncFilterManager; @@ -277,6 +322,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * Retrieves how the server packets are read. * @return Injection method for reading server packets. */ + @Override public PlayerInjectHooks getPlayerHook() { return playerInjection.getPlayerHook(); } @@ -285,6 +331,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * Sets how the server packets are read. * @param playerHook - the new injection method for reading server packets. */ + @Override public void setPlayerHook(PlayerInjectHooks playerHook) { playerInjection.setPlayerHook(playerHook); } @@ -707,6 +754,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok * @param manager - Bukkit plugin manager that provides player join/leave events. * @param plugin - the parent plugin. */ + @Override public void registerEvents(PluginManager manager, final Plugin plugin) { if (spigotInjector != null && !spigotInjector.register(plugin)) throw new IllegalArgumentException("Spigot has already been registered."); @@ -974,9 +1022,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok return hasClosed; } - /** - * Called when ProtocolLib is closing. - */ + @Override public void close() { // Guard if (hasClosed) 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 3f09fc5a..b114feaf 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 @@ -1,338 +1,416 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector.player; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - -import net.sf.cglib.proxy.Factory; - -import org.bukkit.Server; - -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.comphenix.protocol.reflect.ObjectWriter; -import com.comphenix.protocol.reflect.VolatileField; -import com.comphenix.protocol.utility.MinecraftReflection; - -/** - * Used to ensure that the 1.3 server is referencing the correct server handler. - * - * @author Kristian - */ -class InjectedServerConnection { - // A number of things can go wrong ... - public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit."); - public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen."); - - public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer."); - public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread."); - - public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection"); - public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s"); - public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread."); - public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s."); - - public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new."); - - private static Field listenerThreadField; - private static Field minecraftServerField; - private static Field listField; - private static Field dedicatedThreadField; - - private static Method serverConnectionMethod; - - private List listFields; - private List> replacedLists; - - // Used to inject net handlers - private NetLoginInjector netLoginInjector; - - // Inject server connections - private AbstractInputStreamLookup socketInjector; - - private Server server; - private ErrorReporter reporter; - private boolean hasAttempted; - private boolean hasSuccess; - - private Object minecraftServer = null; - - public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) { - this.listFields = new ArrayList(); - this.replacedLists = new ArrayList>(); - this.reporter = reporter; - this.server = server; - this.socketInjector = socketInjector; - this.netLoginInjector = netLoginInjector; - } - - public void injectList() { - // Only execute this method once - if (!hasAttempted) - hasAttempted = true; - else - return; - - if (minecraftServerField == null) - minecraftServerField = FuzzyReflection.fromObject(server, true). - getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass()); - - try { - minecraftServer = FieldUtils.readField(minecraftServerField, server, true); - } catch (IllegalAccessException e1) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER)); - return; - } - - try { - if (serverConnectionMethod == null) - serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()). - getMethodByParameters("getServerConnection", - MinecraftReflection.getServerConnectionClass(), new Class[] {}); - // We're using Minecraft 1.3.1 - injectServerConnection(); - - } catch (IllegalArgumentException e) { - - // Minecraft 1.2.5 or lower - injectListenerThread(); - - } catch (Exception e) { - // Oh damn - inform the player - reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e)); - } - } - - private void injectListenerThread() { - try { - if (listenerThreadField == null) - listenerThreadField = FuzzyReflection.fromObject(minecraftServer). - getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass()); - } catch (RuntimeException e) { - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e) - ); - return; - } - - Object listenerThread = null; - - // Attempt to get the thread - try { - listenerThread = listenerThreadField.get(minecraftServer); - } catch (Exception e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e)); - return; - } - - // Inject the server socket too - injectServerSocket(listenerThread); - - // Just inject every list field we can get - injectEveryListField(listenerThread, 1); - hasSuccess = true; - } - - private void injectServerConnection() { - Object serverConnection = null; - - // Careful - we might fail - try { - serverConnection = serverConnectionMethod.invoke(minecraftServer); - } catch (Exception e) { - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e) - ); - return; - } - - if (listField == null) - listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true). - getFieldByType("netServerHandlerList", List.class); - if (dedicatedThreadField == null) { - List matches = FuzzyReflection.fromObject(serverConnection, true). - getFieldListByType(Thread.class); - - // Verify the field count - if (matches.size() != 1) - reporter.reportWarning(this, - Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size()) - ); - else - dedicatedThreadField = matches.get(0); - } - - // Next, try to get the dedicated thread - try { - if (dedicatedThreadField != null) { - Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true); - - // Inject server socket and NetServerHandlers. - injectServerSocket(dedicatedThread); - injectEveryListField(dedicatedThread, 1); - } - } catch (IllegalAccessException e) { - reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e)); - } - - injectIntoList(serverConnection, listField); - hasSuccess = true; - } - - private void injectServerSocket(Object container) { - socketInjector.inject(container); - } - - /** - * Automatically inject into every List-compatible public or private field of the given object. - * @param container - container object with the fields to inject. - * @param minimum - the minimum number of fields we expect exists. - */ - private void injectEveryListField(Object container, int minimum) { - // Ok, great. Get every list field - List lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class); - - for (Field list : lists) { - injectIntoList(container, list); - } - - // Warn about unexpected errors - if (lists.size() < minimum) { - reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass())); - } - } - - @SuppressWarnings("unchecked") - private void injectIntoList(Object instance, Field field) { - VolatileField listFieldRef = new VolatileField(field, instance, true); - List list = (List) listFieldRef.getValue(); - - // Careful not to inject twice - if (list instanceof ReplacedArrayList) { - replacedLists.add((ReplacedArrayList) list); - } else { - ReplacedArrayList injectedList = createReplacement(list); - - replacedLists.add(injectedList); - listFieldRef.setValue(injectedList); - listFields.add(listFieldRef); - } - } - - // Hack to avoid the "moved to quickly" error - private ReplacedArrayList createReplacement(List list) { - return new ReplacedArrayList(list) { - /** - * Shut up Eclipse! - */ - private static final long serialVersionUID = 2070481080950500367L; - - // Object writer we'll use - private final ObjectWriter writer = new ObjectWriter(); - - @Override - protected void onReplacing(Object inserting, Object replacement) { - // Is this a normal Minecraft object? - if (!(inserting instanceof Factory)) { - // If so, copy the content of the old element to the new - try { - writer.copyTo(inserting, replacement, inserting.getClass()); - } catch (Throwable e) { - reporter.reportDetailed(InjectedServerConnection.this, - Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e) - ); - } - } - } - - @Override - protected void onInserting(Object inserting) { - // Ready for some login handler injection? - if (MinecraftReflection.isLoginHandler(inserting)) { - Object replaced = netLoginInjector.onNetLoginCreated(inserting); - - // Only replace if it has changed - if (inserting != replaced) - addMapping(inserting, replaced, true); - } - } - - @Override - protected void onRemoved(Object removing) { - // Clean up? - if (MinecraftReflection.isLoginHandler(removing)) { - netLoginInjector.cleanup(removing); - } - } - }; - } - - /** - * Replace the server handler instance kept by the "keep alive" object. - * @param oldHandler - old server handler. - * @param newHandler - new, proxied server handler. - */ - public void replaceServerHandler(Object oldHandler, Object newHandler) { - if (!hasAttempted) { - injectList(); - } - - if (hasSuccess) { - for (ReplacedArrayList replacedList : replacedLists) { - replacedList.addMapping(oldHandler, newHandler); - } - } - } - - /** - * Revert to the old vanilla server handler, if it has been replaced. - * @param oldHandler - old vanilla server handler. - */ - public void revertServerHandler(Object oldHandler) { - if (hasSuccess) { - for (ReplacedArrayList replacedList : replacedLists) { - replacedList.removeMapping(oldHandler); - } - } - } - - /** - * Undoes everything. - */ - public void cleanupAll() { - if (replacedLists.size() > 0) { - // Repair the underlying lists - for (ReplacedArrayList replacedList : replacedLists) { - replacedList.revertAll(); - } - for (VolatileField field : listFields) { - field.revertValue(); - } - - listFields.clear(); - replacedLists.clear(); - } - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector.player; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import net.sf.cglib.proxy.Factory; + +import org.bukkit.Server; + +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.ObjectWriter; +import com.comphenix.protocol.reflect.VolatileField; +import com.comphenix.protocol.utility.MinecraftReflection; + +/** + * Used to ensure that the 1.3 server is referencing the correct server handler. + * + * @author Kristian + */ +public class InjectedServerConnection { + // A number of things can go wrong ... + public static final ReportType REPORT_CANNOT_FIND_MINECRAFT_SERVER = new ReportType("Cannot extract minecraft server from Bukkit."); + public static final ReportType REPORT_CANNOT_INJECT_SERVER_CONNECTION = new ReportType("Cannot inject into server connection. Bad things will happen."); + + public static final ReportType REPORT_CANNOT_FIND_LISTENER_THREAD = new ReportType("Cannot find listener thread in MinecraftServer."); + public static final ReportType REPORT_CANNOT_READ_LISTENER_THREAD = new ReportType("Unable to read the listener thread."); + + public static final ReportType REPORT_CANNOT_FIND_SERVER_CONNECTION = new ReportType("Unable to retrieve server connection"); + public static final ReportType REPORT_UNEXPECTED_THREAD_COUNT = new ReportType("Unexpected number of threads in %s: %s"); + public static final ReportType REPORT_CANNOT_FIND_NET_HANDLER_THREAD = new ReportType("Unable to retrieve net handler thread."); + public static final ReportType REPORT_INSUFFICENT_THREAD_COUNT = new ReportType("Unable to inject %s lists in %s."); + + public static final ReportType REPORT_CANNOT_COPY_OLD_TO_NEW = new ReportType("Cannot copy old %s to new."); + + private static Field listenerThreadField; + private static Field minecraftServerField; + private static Field listField; + private static Field dedicatedThreadField; + + private static Method serverConnectionMethod; + + private List listFields; + private List> replacedLists; + + // The current detected server socket + public enum ServerSocketType { + SERVER_CONNECTION, + LISTENER_THREAD, + } + + // Used to inject net handlers + private NetLoginInjector netLoginInjector; + + // Inject server connections + private AbstractInputStreamLookup socketInjector; + + // Detected by the initializer + private ServerSocketType socketType; + + private Server server; + private ErrorReporter reporter; + private boolean hasAttempted; + private boolean hasSuccess; + + private Object minecraftServer = null; + + public InjectedServerConnection(ErrorReporter reporter, AbstractInputStreamLookup socketInjector, Server server, NetLoginInjector netLoginInjector) { + this.listFields = new ArrayList(); + this.replacedLists = new ArrayList>(); + this.reporter = reporter; + this.server = server; + this.socketInjector = socketInjector; + this.netLoginInjector = netLoginInjector; + } + + /** + * Initial reflective detective work. Will be automatically called by most methods in this class. + */ + public void initialize() { + // Only execute this method once + if (!hasAttempted) + hasAttempted = true; + else + return; + + if (minecraftServerField == null) + minecraftServerField = FuzzyReflection.fromObject(server, true). + getFieldByType("MinecraftServer", MinecraftReflection.getMinecraftServerClass()); + + try { + minecraftServer = FieldUtils.readField(minecraftServerField, server, true); + } catch (IllegalAccessException e1) { + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_MINECRAFT_SERVER)); + return; + } + + try { + if (serverConnectionMethod == null) + serverConnectionMethod = FuzzyReflection.fromClass(minecraftServerField.getType()). + getMethodByParameters("getServerConnection", + MinecraftReflection.getServerConnectionClass(), new Class[] {}); + // We're using Minecraft 1.3.1 + socketType = ServerSocketType.SERVER_CONNECTION; + + } catch (IllegalArgumentException e) { + // Minecraft 1.2.5 or lower + socketType = ServerSocketType.LISTENER_THREAD; + + } catch (Exception e) { + // Oh damn - inform the player + reporter.reportDetailed(this, Report.newBuilder(REPORT_CANNOT_INJECT_SERVER_CONNECTION).error(e)); + } + } + + /** + * Retrieve the known server socket type. + *

+ * This depends on the version of CraftBukkit we are using. + * @return The server socket type. + */ + public ServerSocketType getServerSocketType() { + return socketType; + } + + /** + * Inject the connection interceptor into the correct server socket implementation. + */ + public void injectList() { + initialize(); + + if (socketType == ServerSocketType.SERVER_CONNECTION) { + injectServerConnection(); + } else if (socketType == ServerSocketType.LISTENER_THREAD) { + injectListenerThread(); + } else { + // Damn it + throw new IllegalStateException("Unable to detected server connection."); + } + } + + /** + * Retrieve the listener thread field. + */ + private void initializeListenerField() { + if (listenerThreadField == null) + listenerThreadField = FuzzyReflection.fromObject(minecraftServer). + getFieldByType("networkListenThread", MinecraftReflection.getNetworkListenThreadClass()); + } + + /** + * Retrieve the listener thread object, or NULL the server isn't using this socket implementation. + * @return The listener thread, or NULL. + * @throws IllegalAccessException Cannot access field. + * @hrows RuntimeException Unexpected class structure - the field doesn't exist. + */ + public Object getListenerThread() throws RuntimeException, IllegalAccessException { + initialize(); + + if (socketType == ServerSocketType.LISTENER_THREAD) { + initializeListenerField(); + return listenerThreadField.get(minecraftServer); + } else { + return null; + } + } + + /** + * Retrieve the server connection object, or NULL if the server isn't using it as the socket implementation. + * @return The socket connection, or NULL. + * @throws IllegalAccessException If the reflective operation failed. + * @throws IllegalArgumentException If the reflective operation failed. + * @throws InvocationTargetException If the reflective operation failed. + */ + public Object getServerConnection() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + initialize(); + + if (socketType == ServerSocketType.SERVER_CONNECTION) + return serverConnectionMethod.invoke(minecraftServer); + else + return null; + } + + private void injectListenerThread() { + try { + initializeListenerField(); + } catch (RuntimeException e) { + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_FIND_LISTENER_THREAD).callerParam(minecraftServer).error(e) + ); + return; + } + + Object listenerThread = null; + + // Attempt to get the thread + try { + listenerThread = getListenerThread(); + } catch (Exception e) { + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_LISTENER_THREAD).error(e)); + return; + } + + // Inject the server socket too + injectServerSocket(listenerThread); + + // Just inject every list field we can get + injectEveryListField(listenerThread, 1); + hasSuccess = true; + } + + private void injectServerConnection() { + Object serverConnection = null; + + // Careful - we might fail + try { + serverConnection = getServerConnection(); + } catch (Exception e) { + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_FIND_SERVER_CONNECTION).callerParam(minecraftServer).error(e) + ); + return; + } + + if (listField == null) + listField = FuzzyReflection.fromClass(serverConnectionMethod.getReturnType(), true). + getFieldByType("netServerHandlerList", List.class); + if (dedicatedThreadField == null) { + List matches = FuzzyReflection.fromObject(serverConnection, true). + getFieldListByType(Thread.class); + + // Verify the field count + if (matches.size() != 1) + reporter.reportWarning(this, + Report.newBuilder(REPORT_UNEXPECTED_THREAD_COUNT).messageParam(serverConnection.getClass(), matches.size()) + ); + else + dedicatedThreadField = matches.get(0); + } + + // Next, try to get the dedicated thread + try { + if (dedicatedThreadField != null) { + Object dedicatedThread = FieldUtils.readField(dedicatedThreadField, serverConnection, true); + + // Inject server socket and NetServerHandlers. + injectServerSocket(dedicatedThread); + injectEveryListField(dedicatedThread, 1); + } + } catch (IllegalAccessException e) { + reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_FIND_NET_HANDLER_THREAD).error(e)); + } + + injectIntoList(serverConnection, listField); + hasSuccess = true; + } + + private void injectServerSocket(Object container) { + socketInjector.inject(container); + } + + /** + * Automatically inject into every List-compatible public or private field of the given object. + * @param container - container object with the fields to inject. + * @param minimum - the minimum number of fields we expect exists. + */ + private void injectEveryListField(Object container, int minimum) { + // Ok, great. Get every list field + List lists = FuzzyReflection.fromObject(container, true).getFieldListByType(List.class); + + for (Field list : lists) { + injectIntoList(container, list); + } + + // Warn about unexpected errors + if (lists.size() < minimum) { + reporter.reportWarning(this, Report.newBuilder(REPORT_INSUFFICENT_THREAD_COUNT).messageParam(minimum, container.getClass())); + } + } + + @SuppressWarnings("unchecked") + private void injectIntoList(Object instance, Field field) { + VolatileField listFieldRef = new VolatileField(field, instance, true); + List list = (List) listFieldRef.getValue(); + + // Careful not to inject twice + if (list instanceof ReplacedArrayList) { + replacedLists.add((ReplacedArrayList) list); + } else { + ReplacedArrayList injectedList = createReplacement(list); + + replacedLists.add(injectedList); + listFieldRef.setValue(injectedList); + listFields.add(listFieldRef); + } + } + + // Hack to avoid the "moved to quickly" error + private ReplacedArrayList createReplacement(List list) { + return new ReplacedArrayList(list) { + /** + * Shut up Eclipse! + */ + private static final long serialVersionUID = 2070481080950500367L; + + // Object writer we'll use + private final ObjectWriter writer = new ObjectWriter(); + + @Override + protected void onReplacing(Object inserting, Object replacement) { + // Is this a normal Minecraft object? + if (!(inserting instanceof Factory)) { + // If so, copy the content of the old element to the new + try { + writer.copyTo(inserting, replacement, inserting.getClass()); + } catch (Throwable e) { + reporter.reportDetailed(InjectedServerConnection.this, + Report.newBuilder(REPORT_CANNOT_COPY_OLD_TO_NEW).messageParam(inserting).callerParam(inserting, replacement).error(e) + ); + } + } + } + + @Override + protected void onInserting(Object inserting) { + // Ready for some login handler injection? + if (MinecraftReflection.isLoginHandler(inserting)) { + Object replaced = netLoginInjector.onNetLoginCreated(inserting); + + // Only replace if it has changed + if (inserting != replaced) + addMapping(inserting, replaced, true); + } + } + + @Override + protected void onRemoved(Object removing) { + // Clean up? + if (MinecraftReflection.isLoginHandler(removing)) { + netLoginInjector.cleanup(removing); + } + } + }; + } + + /** + * Replace the server handler instance kept by the "keep alive" object. + * @param oldHandler - old server handler. + * @param newHandler - new, proxied server handler. + */ + public void replaceServerHandler(Object oldHandler, Object newHandler) { + if (!hasAttempted) { + injectList(); + } + + if (hasSuccess) { + for (ReplacedArrayList replacedList : replacedLists) { + replacedList.addMapping(oldHandler, newHandler); + } + } + } + + /** + * Revert to the old vanilla server handler, if it has been replaced. + * @param oldHandler - old vanilla server handler. + */ + public void revertServerHandler(Object oldHandler) { + if (hasSuccess) { + for (ReplacedArrayList replacedList : replacedLists) { + replacedList.removeMapping(oldHandler); + } + } + } + + /** + * Undoes everything. + */ + public void cleanupAll() { + if (replacedLists.size() > 0) { + // Repair the underlying lists + for (ReplacedArrayList replacedList : replacedLists) { + replacedList.revertAll(); + } + for (VolatileField field : listFields) { + field.revertValue(); + } + + listFields.clear(); + replacedLists.clear(); + } + } +} 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 129a4bc1..5e008f79 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 @@ -1,154 +1,154 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector.player; - -import java.util.concurrent.ConcurrentMap; - -import org.bukkit.Server; -import org.bukkit.entity.Player; - -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.error.Report; -import com.comphenix.protocol.error.ReportType; -import com.comphenix.protocol.injector.GamePhase; -import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; -import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.google.common.collect.Maps; - -/** - * Injects every NetLoginHandler created by the server. - * - * @author Kristian - */ -class NetLoginInjector { - public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s."); - public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s."); - - private ConcurrentMap injectedLogins = Maps.newConcurrentMap(); - - // Handles every hook - private ProxyPlayerInjectionHandler injectionHandler; - - // Create temporary players - private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory(); - - // The current error reporter - private ErrorReporter reporter; - private Server server; - - public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) { - this.reporter = reporter; - this.server = server; - this.injectionHandler = injectionHandler; - } - - /** - * Invoked when a NetLoginHandler has been created. - * @param inserting - the new NetLoginHandler. - * @return An injected NetLoginHandler, or the original object. - */ - public Object onNetLoginCreated(Object inserting) { - try { - // Make sure we actually need to inject during this phase - if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) - return inserting; - - Player temporary = playerFactory.createTemporaryPlayer(server); - // Note that we bail out if there's an existing player injector - PlayerInjector injector = injectionHandler.injectPlayer( - temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN); - - if (injector != null) { - // Update injector as well - TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector); - injector.updateOnLogin = true; - - // Save the login - injectedLogins.putIfAbsent(inserting, injector); - } - - // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler - return inserting; - - } catch (Throwable e) { - // Minecraft can't handle this, so we'll deal with it here - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER). - messageParam(MinecraftReflection.getNetLoginHandlerName()). - callerParam(inserting, injectionHandler). - error(e) - ); - return inserting; - } - } - - /** - * Invoked when a NetLoginHandler should be reverted. - * @param inserting - the original NetLoginHandler. - * @return An injected NetLoginHandler, or the original object. - */ - public synchronized void cleanup(Object removing) { - PlayerInjector injected = injectedLogins.get(removing); - - if (injected != null) { - try { - 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()); - injectionHandler.uninjectPlayer(player); - - // Update NetworkManager - if (newInjector != null) { - if (injected instanceof NetworkObjectInjector) { - newInjector.setNetworkManager(injected.getNetworkManager(), true); - } - } - - } catch (Throwable e) { - // Don't leak this to Minecraft - reporter.reportDetailed(this, - Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER). - messageParam(MinecraftReflection.getNetLoginHandlerName()). - callerParam(removing). - error(e) - ); - } - } - } - - /** - * Remove all injected hooks. - */ - public void cleanupAll() { - for (PlayerInjector injector : injectedLogins.values()) { - injector.cleanupAll(); - } - - injectedLogins.clear(); - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector.player; + +import java.util.concurrent.ConcurrentMap; + +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.injector.GamePhase; +import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; +import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.google.common.collect.Maps; + +/** + * Injects every NetLoginHandler created by the server. + * + * @author Kristian + */ +class NetLoginInjector { + public static final ReportType REPORT_CANNOT_HOOK_LOGIN_HANDLER = new ReportType("Unable to hook %s."); + public static final ReportType REPORT_CANNOT_CLEANUP_LOGIN_HANDLER = new ReportType("Cannot cleanup %s."); + + private ConcurrentMap injectedLogins = Maps.newConcurrentMap(); + + // Handles every hook + private ProxyPlayerInjectionHandler injectionHandler; + + // Create temporary players + private TemporaryPlayerFactory playerFactory = new TemporaryPlayerFactory(); + + // The current error reporter + private ErrorReporter reporter; + private Server server; + + public NetLoginInjector(ErrorReporter reporter, Server server, ProxyPlayerInjectionHandler injectionHandler) { + this.reporter = reporter; + this.server = server; + this.injectionHandler = injectionHandler; + } + + /** + * Invoked when a NetLoginHandler has been created. + * @param inserting - the new NetLoginHandler. + * @return An injected NetLoginHandler, or the original object. + */ + public Object onNetLoginCreated(Object inserting) { + try { + // Make sure we actually need to inject during this phase + if (!injectionHandler.isInjectionNecessary(GamePhase.LOGIN)) + return inserting; + + Player temporary = playerFactory.createTemporaryPlayer(server); + // Note that we bail out if there's an existing player injector + PlayerInjector injector = injectionHandler.injectPlayer( + temporary, inserting, ConflictStrategy.BAIL_OUT, GamePhase.LOGIN); + + if (injector != null) { + // Update injector as well + TemporaryPlayerFactory.setInjectorInPlayer(temporary, injector); + injector.updateOnLogin = true; + + // Save the login + injectedLogins.putIfAbsent(inserting, injector); + } + + // NetServerInjector can never work (currently), so we don't need to replace the NetLoginHandler + return inserting; + + } catch (Throwable e) { + // Minecraft can't handle this, so we'll deal with it here + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_HOOK_LOGIN_HANDLER). + messageParam(MinecraftReflection.getNetLoginHandlerName()). + callerParam(inserting, injectionHandler). + error(e) + ); + return inserting; + } + } + + /** + * Invoked when a NetLoginHandler should be reverted. + * @param inserting - the original NetLoginHandler. + * @return An injected NetLoginHandler, or the original object. + */ + public synchronized void cleanup(Object removing) { + PlayerInjector injected = injectedLogins.get(removing); + + if (injected != null) { + try { + 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()); + injectionHandler.uninjectPlayer(player); + + // Update NetworkManager + if (newInjector != null) { + if (injected instanceof NetworkObjectInjector) { + newInjector.setNetworkManager(injected.getNetworkManager(), true); + } + } + + } catch (Throwable e) { + // Don't leak this to Minecraft + reporter.reportDetailed(this, + Report.newBuilder(REPORT_CANNOT_CLEANUP_LOGIN_HANDLER). + messageParam(MinecraftReflection.getNetLoginHandlerName()). + callerParam(removing). + error(e) + ); + } + } + } + + /** + * Remove all injected hooks. + */ + public void cleanupAll() { + for (PlayerInjector injector : injectedLogins.values()) { + injector.cleanupAll(); + } + + injectedLogins.clear(); + } +} 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 85669c69..bb8f4a2b 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 @@ -161,9 +161,4 @@ public interface PlayerInjectionHandler { * Close any lingering proxy injections. */ public abstract void close(); - - /** - * Perform any action that must be delayed until the world(s) has loaded. - */ - public abstract void postWorldLoaded(); } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java index 15570caf..584cb8af 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/player/ProxyPlayerInjectionHandler.java @@ -144,14 +144,6 @@ class ProxyPlayerInjectionHandler implements PlayerInjectionHandler { this.serverInjection = new InjectedServerConnection(reporter, inputStreamLookup, server, netLoginInjector); serverInjection.injectList(); } - - @Override - public void postWorldLoaded() { - // This will actually create a socket and a seperate thread ... - if (inputStreamLookup != null) { - inputStreamLookup.postWorldLoaded(); - } - } /** * Retrieves how the server packets are read. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java index 5fde4cdc..0af83561 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/AbstractInputStreamLookup.java @@ -1,87 +1,82 @@ -package com.comphenix.protocol.injector.server; - -import java.io.InputStream; -import java.net.Socket; -import java.net.SocketAddress; -import org.bukkit.Server; -import org.bukkit.entity.Player; - -import com.comphenix.protocol.error.ErrorReporter; - -public abstract class AbstractInputStreamLookup { - // Error reporter - protected final ErrorReporter reporter; - - // Reference to the server itself - protected final Server server; - - protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) { - this.reporter = reporter; - this.server = server; - } - - /** - * Inject the given server thread or dedicated connection. - * @param container - class that contains a ServerSocket field. - */ - public abstract void inject(Object container); - - /** - * Invoked when the world has loaded. - */ - public abstract void postWorldLoaded(); - - /** - * Retrieve the associated socket injector for a player. - * @param input - the indentifying filtered input stream. - * @return The socket injector we have associated with this player. - */ - public abstract SocketInjector waitSocketInjector(InputStream input); - - /** - * Retrieve an injector by its socket. - * @param socket - the socket. - * @return The socket injector. - */ - public abstract SocketInjector waitSocketInjector(Socket socket); - - /** - * Retrieve a injector by its address. - * @param address - the address of the socket. - * @return The socket injector, or NULL if not found. - */ - public abstract SocketInjector waitSocketInjector(SocketAddress address); - - /** - * Attempt to get a socket injector without blocking the thread. - * @param address - the address to lookup. - * @return The socket injector, or NULL if not found. - */ - public abstract SocketInjector peekSocketInjector(SocketAddress address); - - /** - * Associate a given socket address to the provided socket injector. - * @param address - the socket address to associate. - * @param injector - the injector. - */ - public abstract void setSocketInjector(SocketAddress address, SocketInjector injector); - - /** - * If a player can hold a reference to its parent injector, this method will update that reference. - * @param previous - the previous injector. - * @param current - the new injector. - */ - protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) { - Player player = previous.getPlayer(); - - // Default implementation - if (player instanceof InjectorContainer) { - TemporaryPlayerFactory.setInjectorInPlayer(player, current); - } - } - - /** - * Invoked when the injection should be undone. - */ - public abstract void cleanupAll(); +package com.comphenix.protocol.injector.server; + +import java.io.InputStream; +import java.net.Socket; +import java.net.SocketAddress; +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import com.comphenix.protocol.error.ErrorReporter; + +public abstract class AbstractInputStreamLookup { + // Error reporter + protected final ErrorReporter reporter; + + // Reference to the server itself + protected final Server server; + + protected AbstractInputStreamLookup(ErrorReporter reporter, Server server) { + this.reporter = reporter; + this.server = server; + } + + /** + * Inject the given server thread or dedicated connection. + * @param container - class that contains a ServerSocket field. + */ + public abstract void inject(Object container); + + /** + * Retrieve the associated socket injector for a player. + * @param input - the indentifying filtered input stream. + * @return The socket injector we have associated with this player. + */ + public abstract SocketInjector waitSocketInjector(InputStream input); + + /** + * Retrieve an injector by its socket. + * @param socket - the socket. + * @return The socket injector. + */ + public abstract SocketInjector waitSocketInjector(Socket socket); + + /** + * Retrieve a injector by its address. + * @param address - the address of the socket. + * @return The socket injector, or NULL if not found. + */ + public abstract SocketInjector waitSocketInjector(SocketAddress address); + + /** + * Attempt to get a socket injector without blocking the thread. + * @param address - the address to lookup. + * @return The socket injector, or NULL if not found. + */ + public abstract SocketInjector peekSocketInjector(SocketAddress address); + + /** + * Associate a given socket address to the provided socket injector. + * @param address - the socket address to associate. + * @param injector - the injector. + */ + public abstract void setSocketInjector(SocketAddress address, SocketInjector injector); + + /** + * If a player can hold a reference to its parent injector, this method will update that reference. + * @param previous - the previous injector. + * @param current - the new injector. + */ + protected void onPreviousSocketOverwritten(SocketInjector previous, SocketInjector current) { + Player player = previous.getPlayer(); + + // Default implementation + if (player instanceof InjectorContainer) { + TemporaryPlayerFactory.setInjectorInPlayer(player, current); + } + } + + /** + * Invoked when the injection should be undone. + */ + public abstract void cleanupAll(); } \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java index 8e72db01..e4f55330 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/BukkitSocketInjector.java @@ -10,36 +10,14 @@ import java.util.List; import org.bukkit.entity.Player; public class BukkitSocketInjector implements SocketInjector { - /** - * Represents a single send packet command. - * @author Kristian - */ - static class SendPacketCommand { - private final Object packet; - private final boolean filtered; - - public SendPacketCommand(Object packet, boolean filtered) { - this.packet = packet; - this.filtered = filtered; - } - - public Object getPacket() { - return packet; - } - - public boolean isFiltered() { - return filtered; - } - } - private Player player; // Queue of server packets - private List syncronizedQueue = Collections.synchronizedList(new ArrayList()); + private List syncronizedQueue = Collections.synchronizedList(new ArrayList()); /** * Represents a temporary socket injector. - * @param temporaryPlayer - + * @param temporaryPlayer - a temporary player. */ public BukkitSocketInjector(Player player) { if (player == null) @@ -65,7 +43,7 @@ public class BukkitSocketInjector implements SocketInjector { @Override public void sendServerPacket(Object packet, boolean filtered) throws InvocationTargetException { - SendPacketCommand command = new SendPacketCommand(packet, filtered); + QueuedSendPacket command = new QueuedSendPacket(packet, filtered); // Queue until we can find something better syncronizedQueue.add(command); @@ -86,7 +64,7 @@ public class BukkitSocketInjector implements SocketInjector { // Transmit all queued packets to a different injector. try { synchronized(syncronizedQueue) { - for (SendPacketCommand command : syncronizedQueue) { + for (QueuedSendPacket command : syncronizedQueue) { delegate.sendServerPacket(command.getPacket(), command.isFiltered()); } syncronizedQueue.clear(); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java index 37f5d6b6..564687d0 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/InputStreamReflectLookup.java @@ -1,191 +1,186 @@ -package com.comphenix.protocol.injector.server; - -import java.io.FilterInputStream; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.net.Socket; -import java.net.SocketAddress; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; - -import org.bukkit.Server; - -import com.comphenix.protocol.concurrency.BlockingHashMap; -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.reflect.FieldAccessException; -import com.comphenix.protocol.reflect.FieldUtils; -import com.comphenix.protocol.reflect.FuzzyReflection; -import com.google.common.collect.MapMaker; - -class InputStreamReflectLookup extends AbstractInputStreamLookup { - // Used to access the inner input stream of a filtered input stream - private static Field filteredInputField; - - // The default lookup timeout - private static final long DEFAULT_TIMEOUT = 2000; // ms - - // Using weak keys and values ensures that we will not hold up garbage collection - protected BlockingHashMap addressLookup = new BlockingHashMap(); - protected ConcurrentMap inputLookup = new MapMaker().weakValues().makeMap(); - - // The timeout - private final long injectorTimeout; - - public InputStreamReflectLookup(ErrorReporter reporter, Server server) { - this(reporter, server, DEFAULT_TIMEOUT); - } - - /** - * Initialize a reflect lookup with a given default injector timeout. - *

- * This timeout defines the maximum amount of time to wait until an injector has been discovered. - * @param reporter - the error reporter. - * @param server - the current Bukkit server. - * @param injectorTimeout - the injector timeout. - */ - public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) { - super(reporter, server); - this.injectorTimeout = injectorTimeout; - } - - @Override - public void inject(Object container) { - // Do nothing - } - - @Override - public void postWorldLoaded() { - // Nothing again - } - - @Override - public SocketInjector peekSocketInjector(SocketAddress address) { - try { - return addressLookup.get(address, 0, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // Whatever - return null; - } - } - - @Override - public SocketInjector waitSocketInjector(SocketAddress address) { - try { - // Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to - // periodically wake up waiting readers and writers. We have to wait for the dedicated server thread - // to catch up, so we'll swallow these interrupts. - // - // TODO: Consider if we should raise the thread priority of the dedicated server listener thread. - return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true); - } catch (InterruptedException e) { - // This cannot be! - throw new IllegalStateException("Impossible exception occured!", e); - } - } - - @Override - public SocketInjector waitSocketInjector(Socket socket) { - return waitSocketInjector(socket.getRemoteSocketAddress()); - } - - @Override - public SocketInjector waitSocketInjector(InputStream input) { - try { - SocketAddress address = waitSocketAddress(input); - - // Guard against NPE - if (address != null) - return waitSocketInjector(address); - else - return null; - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot find or access socket field for " + input, e); - } - } - - /** - * Use reflection to get the underlying socket address from an input stream. - * @param stream - the socket stream to lookup. - * @return The underlying socket address, or NULL if not found. - * @throws IllegalAccessException Unable to access socket field. - */ - private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException { - // Extra check, just in case - if (stream instanceof FilterInputStream) - return waitSocketAddress(getInputStream((FilterInputStream) stream)); - - SocketAddress result = inputLookup.get(stream); - - if (result == null) { - Socket socket = lookupSocket(stream); - - // Save it - result = socket.getRemoteSocketAddress(); - inputLookup.put(stream, result); - } - return result; - } - - /** - * Retrieve the underlying input stream that is associated with a given filter input stream. - * @param filtered - the filter input stream. - * @return The underlying input stream that is being filtered. - * @throws FieldAccessException Unable to access input stream. - */ - protected static InputStream getInputStream(FilterInputStream filtered) { - if (filteredInputField == null) - filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true). - getFieldByType("in", InputStream.class); - - InputStream current = filtered; - - try { - // Iterate until we find the real input stream - while (current instanceof FilterInputStream) { - current = (InputStream) FieldUtils.readField(filteredInputField, current, true); - } - return current; - } catch (IllegalAccessException e) { - throw new FieldAccessException("Cannot access filtered input field.", e); - } - } - - @Override - public void setSocketInjector(SocketAddress address, SocketInjector injector) { - if (address == null) - throw new IllegalArgumentException("address cannot be NULL"); - if (injector == null) - throw new IllegalArgumentException("injector cannot be NULL."); - - SocketInjector previous = addressLookup.put(address, injector); - - // Any previous temporary players will also be associated - if (previous != null) { - // Update the reference to any previous injector - onPreviousSocketOverwritten(previous, injector); - } - } - - @Override - public void cleanupAll() { - // Do nothing - } - - /** - * Lookup the underlying socket of a stream through reflection. - * @param stream - the socket stream. - * @return The underlying socket. - * @throws IllegalAccessException If reflection failed. - */ - private static Socket lookupSocket(InputStream stream) throws IllegalAccessException { - if (stream instanceof FilterInputStream) { - return lookupSocket(getInputStream((FilterInputStream) stream)); - } else { - // Just do it - Field socketField = FuzzyReflection.fromObject(stream, true). - getFieldByType("socket", Socket.class); - - return (Socket) FieldUtils.readField(socketField, stream, true); - } - } -} +package com.comphenix.protocol.injector.server; + +import java.io.FilterInputStream; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.net.Socket; +import java.net.SocketAddress; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +import org.bukkit.Server; + +import com.comphenix.protocol.concurrency.BlockingHashMap; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.reflect.FieldAccessException; +import com.comphenix.protocol.reflect.FieldUtils; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.google.common.collect.MapMaker; + +class InputStreamReflectLookup extends AbstractInputStreamLookup { + // Used to access the inner input stream of a filtered input stream + private static Field filteredInputField; + + // The default lookup timeout + private static final long DEFAULT_TIMEOUT = 2000; // ms + + // Using weak keys and values ensures that we will not hold up garbage collection + protected BlockingHashMap addressLookup = new BlockingHashMap(); + protected ConcurrentMap inputLookup = new MapMaker().weakValues().makeMap(); + + // The timeout + private final long injectorTimeout; + + public InputStreamReflectLookup(ErrorReporter reporter, Server server) { + this(reporter, server, DEFAULT_TIMEOUT); + } + + /** + * Initialize a reflect lookup with a given default injector timeout. + *

+ * This timeout defines the maximum amount of time to wait until an injector has been discovered. + * @param reporter - the error reporter. + * @param server - the current Bukkit server. + * @param injectorTimeout - the injector timeout. + */ + public InputStreamReflectLookup(ErrorReporter reporter, Server server, long injectorTimeout) { + super(reporter, server); + this.injectorTimeout = injectorTimeout; + } + + @Override + public void inject(Object container) { + // Do nothing + } + + @Override + public SocketInjector peekSocketInjector(SocketAddress address) { + try { + return addressLookup.get(address, 0, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // Whatever + return null; + } + } + + @Override + public SocketInjector waitSocketInjector(SocketAddress address) { + try { + // Note that we actually SWALLOW interrupts here - this is because Minecraft uses interrupts to + // periodically wake up waiting readers and writers. We have to wait for the dedicated server thread + // to catch up, so we'll swallow these interrupts. + // + // TODO: Consider if we should raise the thread priority of the dedicated server listener thread. + return addressLookup.get(address, injectorTimeout, TimeUnit.MILLISECONDS, true); + } catch (InterruptedException e) { + // This cannot be! + throw new IllegalStateException("Impossible exception occured!", e); + } + } + + @Override + public SocketInjector waitSocketInjector(Socket socket) { + return waitSocketInjector(socket.getRemoteSocketAddress()); + } + + @Override + public SocketInjector waitSocketInjector(InputStream input) { + try { + SocketAddress address = waitSocketAddress(input); + + // Guard against NPE + if (address != null) + return waitSocketInjector(address); + else + return null; + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot find or access socket field for " + input, e); + } + } + + /** + * Use reflection to get the underlying socket address from an input stream. + * @param stream - the socket stream to lookup. + * @return The underlying socket address, or NULL if not found. + * @throws IllegalAccessException Unable to access socket field. + */ + private SocketAddress waitSocketAddress(InputStream stream) throws IllegalAccessException { + // Extra check, just in case + if (stream instanceof FilterInputStream) + return waitSocketAddress(getInputStream((FilterInputStream) stream)); + + SocketAddress result = inputLookup.get(stream); + + if (result == null) { + Socket socket = lookupSocket(stream); + + // Save it + result = socket.getRemoteSocketAddress(); + inputLookup.put(stream, result); + } + return result; + } + + /** + * Retrieve the underlying input stream that is associated with a given filter input stream. + * @param filtered - the filter input stream. + * @return The underlying input stream that is being filtered. + * @throws FieldAccessException Unable to access input stream. + */ + protected static InputStream getInputStream(FilterInputStream filtered) { + if (filteredInputField == null) + filteredInputField = FuzzyReflection.fromClass(FilterInputStream.class, true). + getFieldByType("in", InputStream.class); + + InputStream current = filtered; + + try { + // Iterate until we find the real input stream + while (current instanceof FilterInputStream) { + current = (InputStream) FieldUtils.readField(filteredInputField, current, true); + } + return current; + } catch (IllegalAccessException e) { + throw new FieldAccessException("Cannot access filtered input field.", e); + } + } + + @Override + public void setSocketInjector(SocketAddress address, SocketInjector injector) { + if (address == null) + throw new IllegalArgumentException("address cannot be NULL"); + if (injector == null) + throw new IllegalArgumentException("injector cannot be NULL."); + + SocketInjector previous = addressLookup.put(address, injector); + + // Any previous temporary players will also be associated + if (previous != null) { + // Update the reference to any previous injector + onPreviousSocketOverwritten(previous, injector); + } + } + + @Override + public void cleanupAll() { + // Do nothing + } + + /** + * Lookup the underlying socket of a stream through reflection. + * @param stream - the socket stream. + * @return The underlying socket. + * @throws IllegalAccessException If reflection failed. + */ + private static Socket lookupSocket(InputStream stream) throws IllegalAccessException { + if (stream instanceof FilterInputStream) { + return lookupSocket(getInputStream((FilterInputStream) stream)); + } else { + // Just do it + Field socketField = FuzzyReflection.fromObject(stream, true). + getFieldByType("socket", Socket.class); + + return (Socket) FieldUtils.readField(socketField, stream, true); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java new file mode 100644 index 00000000..e12f47a8 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/QueuedSendPacket.java @@ -0,0 +1,31 @@ +package com.comphenix.protocol.injector.server; + +/** + * Represents a single send packet command. + * @author Kristian + */ +class QueuedSendPacket { + private final Object packet; + private final boolean filtered; + + public QueuedSendPacket(Object packet, boolean filtered) { + this.packet = packet; + this.filtered = filtered; + } + + /** + * Retrieve the underlying packet that will be sent. + * @return The underlying packet. + */ + public Object getPacket() { + return packet; + } + + /** + * Determine if the packet should be intercepted by packet listeners. + * @return TRUE if it should, FALSE otherwise. + */ + public boolean isFiltered() { + return filtered; + } +} \ No newline at end of file diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java index 8c2e0e05..62db7f2a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/server/TemporaryPlayerFactory.java @@ -1,197 +1,197 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.injector.server; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -import net.sf.cglib.proxy.Callback; -import net.sf.cglib.proxy.CallbackFilter; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; -import net.sf.cglib.proxy.NoOp; - -import org.bukkit.Server; -import org.bukkit.entity.Player; - -import com.comphenix.protocol.injector.PacketConstructor; -import com.comphenix.protocol.reflect.FieldAccessException; - -/** - * Create fake player instances that represents pre-authenticated clients. - */ -public 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" }); - } - - /** - * Retrieve the injector from a given player if it contains one. - * @param player - the player that may contain a reference to a player injector. - * @return The referenced player injector, or NULL if none can be found. - */ - public static SocketInjector getInjectorFromPlayer(Player player) { - if (player instanceof InjectorContainer) { - return ((InjectorContainer) player).getInjector(); - } - return null; - } - - /** - * Set the player injector, if possible. - * @param player - the player to update. - * @param injector - the injector to store. - */ - public static void setInjectorInPlayer(Player player, SocketInjector injector) { - ((InjectorContainer) player).setInjector(injector); - } - - /** - * Construct a temporary player that supports a subset of every player command. - *

- * Supported methods include: - *

    - *
  • getPlayer()
  • - *
  • getAddress()
  • - *
  • getServer()
  • - *
  • chat(String)
  • - *
  • sendMessage(String)
  • - *
  • sendMessage(String[])
  • - *
  • kickPlayer(String)
  • - *
- *

- * Note that a temporary player has not yet been assigned a name, and thus cannot be - * uniquely identified. Use the address instead. - * @param injector - the player injector used. - * @param server - the current server. - * @return A temporary player instance. - */ - public Player createTemporaryPlayer(final Server server) { - - // Default implementation - Callback implementation = new MethodInterceptor() { - @Override - public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { - - String methodName = method.getName(); - SocketInjector injector = ((InjectorContainer) obj).getInjector(); - - if (injector == null) - throw new IllegalStateException("Unable to find injector."); - - // Use the socket to get the address - if (methodName.equalsIgnoreCase("isOnline")) - return injector.getSocket() != null && injector.getSocket().isConnected(); - if (methodName.equalsIgnoreCase("getName")) - return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]"; - if (methodName.equalsIgnoreCase("getPlayer")) - return injector.getUpdatedPlayer(); - if (methodName.equalsIgnoreCase("getAddress")) - return injector.getAddress(); - if (methodName.equalsIgnoreCase("getServer")) - return server; - - try { - // Handle send message methods - if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) { - Object argument = args[0]; - - // Dynamic overloading - if (argument instanceof String) { - return sendMessage(injector, (String) argument); - } else if (argument instanceof String[]) { - for (String message : (String[]) argument) { - sendMessage(injector, message); - } - return null; - } - } - } catch (InvocationTargetException e) { - throw e.getCause(); - } - - // Also, handle kicking - if (methodName.equalsIgnoreCase("kickPlayer")) { - injector.disconnect((String) args[0]); - return null; - } - - // Ignore all other methods - throw new UnsupportedOperationException( - "The method " + method.getName() + " is not supported for temporary players."); - } - }; - - // 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(InjectorContainer.class)) - return 0; - else - return 1; - } - }; - } - - // CGLib is amazing - Enhancer ex = new Enhancer(); - ex.setSuperclass(InjectorContainer.class); - ex.setInterfaces(new Class[] { Player.class }); - ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation }); - ex.setCallbackFilter(callbackFilter); - - return (Player) ex.create(); - } - - /** - * Construct a temporary player with the given associated socket injector. - * @param server - the parent server. - * @param injector - the referenced socket injector. - * @return The temporary player. - */ - public Player createTemporaryPlayer(Server server, SocketInjector injector) { - Player temporary = createTemporaryPlayer(server); - - ((InjectorContainer) temporary).setInjector(injector); - return temporary; - } - - /** - * Send a message to the given client. - * @param injector - the injector representing the client. - * @param message - a message. - * @return Always NULL. - * @throws InvocationTargetException If the message couldn't be sent. - * @throws FieldAccessException If we were unable to construct the message packet. - */ - private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { - injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false); - return null; - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.injector.server; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import net.sf.cglib.proxy.Callback; +import net.sf.cglib.proxy.CallbackFilter; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.MethodInterceptor; +import net.sf.cglib.proxy.MethodProxy; +import net.sf.cglib.proxy.NoOp; + +import org.bukkit.Server; +import org.bukkit.entity.Player; + +import com.comphenix.protocol.injector.PacketConstructor; +import com.comphenix.protocol.reflect.FieldAccessException; + +/** + * Create fake player instances that represents pre-authenticated clients. + */ +public 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" }); + } + + /** + * Retrieve the injector from a given player if it contains one. + * @param player - the player that may contain a reference to a player injector. + * @return The referenced player injector, or NULL if none can be found. + */ + public static SocketInjector getInjectorFromPlayer(Player player) { + if (player instanceof InjectorContainer) { + return ((InjectorContainer) player).getInjector(); + } + return null; + } + + /** + * Set the player injector, if possible. + * @param player - the player to update. + * @param injector - the injector to store. + */ + public static void setInjectorInPlayer(Player player, SocketInjector injector) { + ((InjectorContainer) player).setInjector(injector); + } + + /** + * Construct a temporary player that supports a subset of every player command. + *

+ * Supported methods include: + *

    + *
  • getPlayer()
  • + *
  • getAddress()
  • + *
  • getServer()
  • + *
  • chat(String)
  • + *
  • sendMessage(String)
  • + *
  • sendMessage(String[])
  • + *
  • kickPlayer(String)
  • + *
+ *

+ * Note that a temporary player has not yet been assigned a name, and thus cannot be + * uniquely identified. Use the address instead. + * @param injector - the player injector used. + * @param server - the current server. + * @return A temporary player instance. + */ + public Player createTemporaryPlayer(final Server server) { + + // Default implementation + Callback implementation = new MethodInterceptor() { + @Override + public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { + + String methodName = method.getName(); + SocketInjector injector = ((InjectorContainer) obj).getInjector(); + + if (injector == null) + throw new IllegalStateException("Unable to find injector."); + + // Use the socket to get the address + if (methodName.equalsIgnoreCase("isOnline")) + return injector.getSocket() != null && injector.getSocket().isConnected(); + if (methodName.equalsIgnoreCase("getName")) + return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]"; + if (methodName.equalsIgnoreCase("getPlayer")) + return injector.getUpdatedPlayer(); + if (methodName.equalsIgnoreCase("getAddress")) + return injector.getAddress(); + if (methodName.equalsIgnoreCase("getServer")) + return server; + + try { + // Handle send message methods + if (methodName.equalsIgnoreCase("chat") || methodName.equalsIgnoreCase("sendMessage")) { + Object argument = args[0]; + + // Dynamic overloading + if (argument instanceof String) { + return sendMessage(injector, (String) argument); + } else if (argument instanceof String[]) { + for (String message : (String[]) argument) { + sendMessage(injector, message); + } + return null; + } + } + } catch (InvocationTargetException e) { + throw e.getCause(); + } + + // Also, handle kicking + if (methodName.equalsIgnoreCase("kickPlayer")) { + injector.disconnect((String) args[0]); + return null; + } + + // Ignore all other methods + throw new UnsupportedOperationException( + "The method " + method.getName() + " is not supported for temporary players."); + } + }; + + // 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(InjectorContainer.class)) + return 0; + else + return 1; + } + }; + } + + // CGLib is amazing + Enhancer ex = new Enhancer(); + ex.setSuperclass(InjectorContainer.class); + ex.setInterfaces(new Class[] { Player.class }); + ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation }); + ex.setCallbackFilter(callbackFilter); + + return (Player) ex.create(); + } + + /** + * Construct a temporary player with the given associated socket injector. + * @param server - the parent server. + * @param injector - the referenced socket injector. + * @return The temporary player. + */ + public Player createTemporaryPlayer(Server server, SocketInjector injector) { + Player temporary = createTemporaryPlayer(server); + + ((InjectorContainer) temporary).setInjector(injector); + return temporary; + } + + /** + * Send a message to the given client. + * @param injector - the injector representing the client. + * @param message - a message. + * @return Always NULL. + * @throws InvocationTargetException If the message couldn't be sent. + * @throws FieldAccessException If we were unable to construct the message packet. + */ + private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { + injector.sendServerPacket(chatPacket.createPacket(message).getHandle(), false); + return null; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java index 8df7f70c..dd64e3ad 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/spigot/DummyPlayerHandler.java @@ -114,12 +114,7 @@ class DummyPlayerHandler implements PlayerInjectionHandler { public void checkListener(Set listeners) { // Yes, really } - - @Override - public void postWorldLoaded() { - // Do nothing - } - + @Override public void updatePlayer(Player player) { // Do nothing From 6847283fb36b36b2109ee95a093a63913c948a85 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 6 Jul 2013 00:25:59 +0200 Subject: [PATCH 11/20] Added the Comphenix Maven repository. --- ProtocolLib/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index ddbd5aca..0d7b1758 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -29,6 +29,11 @@ bukkit-rep http://repo.bukkit.org/content/groups/public + + comphenix-releases + Comphenix Maven Releases + http://repo.comphenix.net/content/repositories/releases/ + From a4f81e5e9fdd20ad96ac3026c57a3decdb34263d Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 6 Jul 2013 07:51:02 +0200 Subject: [PATCH 12/20] Update asynchronous manager and handle static senders in reports. --- .../comphenix/protocol/ProtocolLibrary.java | 2 +- .../error/DelegatedErrorReporter.java | 160 +-- .../protocol/error/DetailedErrorReporter.java | 998 +++++++++--------- .../comphenix/protocol/error/ReportType.java | 35 +- .../injector/DelayedPacketManager.java | 4 + .../injector/PacketFilterManager.java | 6 +- 6 files changed, 623 insertions(+), 582 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 4677a5f8..07330826 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -217,7 +217,7 @@ public class ProtocolLibrary extends JavaPlugin { @Override protected Report filterReport(Object sender, Report report, boolean detailed) { - String canonicalName = ReportType.getReportName(sender.getClass(), report.getType()); + String canonicalName = ReportType.getReportName(sender, report.getType()); String reportName = Iterables.getLast(Splitter.on("#").split(canonicalName)).toUpperCase(); if (config != null && config.getModificationCount() != lastModCount) { diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java index 7584f5d0..6d855320 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DelegatedErrorReporter.java @@ -1,80 +1,80 @@ -package com.comphenix.protocol.error; - -import org.bukkit.plugin.Plugin; - -import com.comphenix.protocol.error.Report.ReportBuilder; - -/** - * Construct an error reporter that delegates to another error reporter. - * @author Kristian - */ -public class DelegatedErrorReporter implements ErrorReporter { - private final ErrorReporter delegated; - - /** - * Construct a new error reporter that forwards all reports to a given reporter. - * @param delegated - the delegated reporter. - */ - public DelegatedErrorReporter(ErrorReporter delegated) { - this.delegated = delegated; - } - - /** - * Retrieve the underlying error reporter. - * @return Underlying error reporter. - */ - public ErrorReporter getDelegated() { - return delegated; - } - - @Override - public void reportMinimal(Plugin sender, String methodName, Throwable error) { - delegated.reportMinimal(sender, methodName, error); - } - - @Override - public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { - delegated.reportMinimal(sender, methodName, error, parameters); - } - - @Override - public void reportWarning(Object sender, Report report) { - Report transformed = filterReport(sender, report, false); - - if (transformed != null) { - delegated.reportWarning(sender, transformed); - } - } - - @Override - public void reportDetailed(Object sender, Report report) { - Report transformed = filterReport(sender, report, true); - - if (transformed != null) { - delegated.reportDetailed(sender, transformed); - } - } - - /** - * Invoked before an error report is passed on to the underlying error reporter. - *

- * To cancel a report, return NULL. - * @param sender - the sender component. - * @param report - the error report. - * @param detailed - whether or not the report will be displayed in detail. - * @return The report to pass on, or NULL to cancel it. - */ - protected Report filterReport(Object sender, Report report, boolean detailed) { - return report; - } - - @Override - public void reportWarning(Object sender, ReportBuilder reportBuilder) { - reportWarning(sender, reportBuilder.build()); - } - - @Override - public void reportDetailed(Object sender, ReportBuilder reportBuilder) { - reportDetailed(sender, reportBuilder.build()); - } -} +package com.comphenix.protocol.error; + +import org.bukkit.plugin.Plugin; + +import com.comphenix.protocol.error.Report.ReportBuilder; + +/** + * Construct an error reporter that delegates to another error reporter. + * @author Kristian + */ +public class DelegatedErrorReporter implements ErrorReporter { + private final ErrorReporter delegated; + + /** + * Construct a new error reporter that forwards all reports to a given reporter. + * @param delegated - the delegated reporter. + */ + public DelegatedErrorReporter(ErrorReporter delegated) { + this.delegated = delegated; + } + + /** + * Retrieve the underlying error reporter. + * @return Underlying error reporter. + */ + public ErrorReporter getDelegated() { + return delegated; + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error) { + delegated.reportMinimal(sender, methodName, error); + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { + delegated.reportMinimal(sender, methodName, error, parameters); + } + + @Override + public void reportWarning(Object sender, Report report) { + Report transformed = filterReport(sender, report, false); + + if (transformed != null) { + delegated.reportWarning(sender, transformed); + } + } + + @Override + public void reportDetailed(Object sender, Report report) { + Report transformed = filterReport(sender, report, true); + + if (transformed != null) { + delegated.reportDetailed(sender, transformed); + } + } + + /** + * Invoked before an error report is passed on to the underlying error reporter. + *

+ * To cancel a report, return NULL. + * @param sender - the sender instance or class. + * @param report - the error report. + * @param detailed - whether or not the report will be displayed in detail. + * @return The report to pass on, or NULL to cancel it. + */ + protected Report filterReport(Object sender, Report report, boolean detailed) { + return report; + } + + @Override + public void reportWarning(Object sender, ReportBuilder reportBuilder) { + reportWarning(sender, reportBuilder.build()); + } + + @Override + public void reportDetailed(Object sender, ReportBuilder reportBuilder) { + reportDetailed(sender, reportBuilder.build()); + } +} 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 6953d437..9020da58 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/DetailedErrorReporter.java @@ -1,499 +1,499 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.error; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.lang.ref.WeakReference; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; -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.error.Report.ReportBuilder; -import com.comphenix.protocol.events.PacketAdapter; -import com.comphenix.protocol.reflect.PrettyPrinter; -import com.google.common.primitives.Primitives; - -/** - * Internal class used to handle exceptions. - * - * @author Kristian - */ -public class DetailedErrorReporter implements ErrorReporter { - /** - * Report format for printing the current exception count. - */ - public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!"); - - 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/"; - - // 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; - - // Prevent spam per plugin too - private ConcurrentMap warningCount = new ConcurrentHashMap(); - - protected String prefix; - protected String supportURL; - - protected AtomicInteger internalErrorCount = new AtomicInteger(); - - protected int maxErrorCount; - protected Logger logger; - - protected WeakReference pluginReference; - protected String pluginName; - - // 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(Plugin plugin) { - this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL); - } - - /** - * Create a central error reporting system. - * @param plugin - the plugin owner. - * @param prefix - default line prefix. - * @param supportURL - URL to report the error. - */ - public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) { - this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger()); - } - - /** - * Create a central error reporting system. - * @param plugin - the plugin owner. - * @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(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) { - if (plugin == null) - throw new IllegalArgumentException("Plugin cannot be NULL."); - - this.pluginReference = new WeakReference(plugin); - this.pluginName = plugin.getName(); - this.prefix = prefix; - this.supportURL = supportURL; - this.maxErrorCount = maxErrorCount; - this.logger = logger; - } - - // Attempt to get the logger. - private static Logger getBukkitLogger() { - try { - return Bukkit.getLogger(); - } catch (Throwable e) { - return Logger.getLogger("Minecraft"); - } - } - - @Override - public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { - if (reportMinimalNoSpam(sender, methodName, error)) { - // Print parameters, if they are given - if (parameters != null && parameters.length > 0) { - logger.log(Level.SEVERE, printParameters(parameters)); - } - } - } - - @Override - public void reportMinimal(Plugin sender, String methodName, Throwable error) { - reportMinimalNoSpam(sender, methodName, error); - } - - /** - * Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports. - * @param sender - the component that observed this exception. - * @param methodName - the method name. - * @param error - the error itself. - * @return TRUE if the error was printed, FALSE if it was suppressed. - */ - public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) { - String pluginName = PacketAdapter.getPluginName(sender); - AtomicInteger counter = warningCount.get(pluginName); - - // Thread safe pattern - if (counter == null) { - AtomicInteger created = new AtomicInteger(); - counter = warningCount.putIfAbsent(pluginName, created); - - if (counter == null) { - counter = created; - } - } - - final int errorCount = counter.incrementAndGet(); - - // See if we should print the full error - if (errorCount < getMaxErrorCount()) { - logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " + - methodName + " for " + pluginName, error); - return true; - - } else { - // Nope - only print the error count occationally - if (isPowerOfTwo(errorCount)) { - logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " + - methodName + " for " + pluginName, error); - } - return false; - } - } - - /** - * Determine if a given number is a power of two. - *

- * That is, if there exists an N such that 2^N = number. - * @param number - the number to check. - * @return TRUE if the given number is a power of two, FALSE otherwise. - */ - private boolean isPowerOfTwo(int number) { - return (number & (number - 1)) == 0; - } - - @Override - public void reportWarning(Object sender, ReportBuilder reportBuilder) { - if (reportBuilder == null) - throw new IllegalArgumentException("reportBuilder cannot be NULL."); - - reportWarning(sender, reportBuilder.build()); - } - - @Override - public void reportWarning(Object sender, Report report) { - String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage(); - - // Print the main warning - if (report.getException() != null) { - logger.log(Level.WARNING, message, report.getException()); - } else { - logger.log(Level.WARNING, message); - } - - // Parameters? - if (report.hasCallerParameters()) { - // Write it - logger.log(Level.WARNING, printParameters(report.getCallerParameters())); - } - } - - /** - * Retrieve the name of a sender class. - * @param sender - sender object. - * @return The name of the sender's class. - */ - private String getSenderName(Object sender) { - if (sender != null) - return sender.getClass().getSimpleName(); - else - return "NULL"; - } - - @Override - public void reportDetailed(Object sender, ReportBuilder reportBuilder) { - reportDetailed(sender, reportBuilder.build()); - } - - @Override - public void reportDetailed(Object sender, Report report) { - final Plugin plugin = pluginReference.get(); - final int errorCount = internalErrorCount.incrementAndGet(); - - // Do not overtly spam the server! - if (errorCount > getMaxErrorCount()) { - // Only allow the error count at rare occations - if (isPowerOfTwo(errorCount)) { - // Permit it - but print the number of exceptions first - reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build()); - } else { - // NEVER SPAM THE CONSOLE - return; - } - } - - StringWriter text = new StringWriter(); - PrintWriter writer = new PrintWriter(text); - - // Helpful message - writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage()); - 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 (report.getException() != null) { - report.getException().printStackTrace(writer); - } - - // Data dump! - writer.println(" ===== DUMP ====="); - - // Relevant parameters - if (report.hasCallerParameters()) { - printParameters(writer, report.getCallerParameters()); - } - - // 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)); - - // And plugin - if (plugin != null) { - writer.println("Version:"); - writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX)); - } - - // Add the server version too - 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.", report.getReportMessage(), report.getException(), sender), - ERROR_PERMISSION - ); - } - } - - // Make sure it is reported - logger.severe(addPrefix(text.toString(), prefix)); - } - - private String printParameters(Object... parameters) { - StringWriter writer = new StringWriter(); - - // Print and retrieve the string buffer - printParameters(new PrintWriter(writer), parameters); - return writer.toString(); - } - - private void printParameters(PrintWriter writer, Object[] parameters) { - 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)); - } - } - - /** - * 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); - } - - /** - * Retrieve a string representation of the given object. - * @param value - object to convert. - * @return String representation. - */ - 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 - apacheCommonsMissing = true; - } - - // Use our custom object printer instead - try { - return PrettyPrinter.printObject(value, value.getClass(), Object.class); - } catch (IllegalAccessException e) { - return "[Error: " + e.getMessage() + "]"; - } - } - } - - /** - * 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()); - } - - /** - * Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}. - * @return Number of errors printed. - */ - public int getErrorCount() { - return internalErrorCount.get(); - } - - /** - * Set the number of errors printed. - * @param errorCount - new number of errors printed. - */ - public void setErrorCount(int errorCount) { - internalErrorCount.set(errorCount); - } - - /** - * Retrieve the maximum number of errors we can print before we begin suppressing errors. - * @return Maximum number of errors. - */ - public int getMaxErrorCount() { - return maxErrorCount; - } - - /** - * Set the maximum number of errors we can print before we begin suppressing errors. - * @param maxErrorCount - new max count. - */ - public void setMaxErrorCount(int maxErrorCount) { - this.maxErrorCount = maxErrorCount; - } - - /** - * Adds the given global parameter. It will be included in every error report. - *

- * Both key and value must be non-null. - * @param key - name of parameter. - * @param value - the global parameter itself. - */ - public void addGlobalParameter(String key, Object value) { - if (key == null) - throw new IllegalArgumentException("key cannot be NULL."); - if (value == null) - throw new IllegalArgumentException("value cannot be NULL."); - - globalParameters.put(key, value); - } - - /** - * Retrieve a global parameter by its key. - * @param key - key of the parameter to retrieve. - * @return The value of the global parameter, or NULL if not found. - */ - public Object getGlobalParameter(String key) { - if (key == null) - throw new IllegalArgumentException("key cannot be NULL."); - - return globalParameters.get(key); - } - - /** - * Reset all global parameters. - */ - public void clearGlobalParameters() { - globalParameters.clear(); - } - - /** - * Retrieve a set of every registered global parameter. - * @return Set of all registered global parameters. - */ - public Set globalParameters() { - return globalParameters.keySet(); - } - - /** - * Retrieve the support URL that will be added to all detailed reports. - * @return Support URL. - */ - public String getSupportURL() { - return supportURL; - } - - /** - * Set the support URL that will be added to all detailed reports. - * @param supportURL - the new support URL. - */ - public void setSupportURL(String supportURL) { - this.supportURL = supportURL; - } - - /** - * Retrieve the prefix to apply to every line in the error reports. - * @return Error report prefix. - */ - public String getPrefix() { - return prefix; - } - - /** - * Set the prefix to apply to every line in the error reports. - * @param prefix - new prefix. - */ - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - /** - * Retrieve the current logger that is used to print all reports. - * @return The current logger. - */ - public Logger getLogger() { - return logger; - } - - /** - * Set the current logger that is used to print all reports. - * @param logger - new logger. - */ - public void setLogger(Logger logger) { - this.logger = logger; - } -} +/* + * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. + * Copyright (C) 2012 Kristian S. Stangeland + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program; + * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA + */ + +package com.comphenix.protocol.error; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +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.error.Report.ReportBuilder; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.reflect.PrettyPrinter; +import com.google.common.primitives.Primitives; + +/** + * Internal class used to handle exceptions. + * + * @author Kristian + */ +public class DetailedErrorReporter implements ErrorReporter { + /** + * Report format for printing the current exception count. + */ + public static final ReportType REPORT_EXCEPTION_COUNT = new ReportType("Internal exception count: %s!"); + + 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/"; + + // 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; + + // Prevent spam per plugin too + private ConcurrentMap warningCount = new ConcurrentHashMap(); + + protected String prefix; + protected String supportURL; + + protected AtomicInteger internalErrorCount = new AtomicInteger(); + + protected int maxErrorCount; + protected Logger logger; + + protected WeakReference pluginReference; + protected String pluginName; + + // 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(Plugin plugin) { + this(plugin, DEFAULT_PREFIX, DEFAULT_SUPPORT_URL); + } + + /** + * Create a central error reporting system. + * @param plugin - the plugin owner. + * @param prefix - default line prefix. + * @param supportURL - URL to report the error. + */ + public DetailedErrorReporter(Plugin plugin, String prefix, String supportURL) { + this(plugin, prefix, supportURL, DEFAULT_MAX_ERROR_COUNT, getBukkitLogger()); + } + + /** + * Create a central error reporting system. + * @param plugin - the plugin owner. + * @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(Plugin plugin, String prefix, String supportURL, int maxErrorCount, Logger logger) { + if (plugin == null) + throw new IllegalArgumentException("Plugin cannot be NULL."); + + this.pluginReference = new WeakReference(plugin); + this.pluginName = plugin.getName(); + this.prefix = prefix; + this.supportURL = supportURL; + this.maxErrorCount = maxErrorCount; + this.logger = logger; + } + + // Attempt to get the logger. + private static Logger getBukkitLogger() { + try { + return Bukkit.getLogger(); + } catch (Throwable e) { + return Logger.getLogger("Minecraft"); + } + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error, Object... parameters) { + if (reportMinimalNoSpam(sender, methodName, error)) { + // Print parameters, if they are given + if (parameters != null && parameters.length > 0) { + logger.log(Level.SEVERE, printParameters(parameters)); + } + } + } + + @Override + public void reportMinimal(Plugin sender, String methodName, Throwable error) { + reportMinimalNoSpam(sender, methodName, error); + } + + /** + * Report a problem with a given method and plugin, ensuring that we don't exceed the maximum number of error reports. + * @param sender - the component that observed this exception. + * @param methodName - the method name. + * @param error - the error itself. + * @return TRUE if the error was printed, FALSE if it was suppressed. + */ + public boolean reportMinimalNoSpam(Plugin sender, String methodName, Throwable error) { + String pluginName = PacketAdapter.getPluginName(sender); + AtomicInteger counter = warningCount.get(pluginName); + + // Thread safe pattern + if (counter == null) { + AtomicInteger created = new AtomicInteger(); + counter = warningCount.putIfAbsent(pluginName, created); + + if (counter == null) { + counter = created; + } + } + + final int errorCount = counter.incrementAndGet(); + + // See if we should print the full error + if (errorCount < getMaxErrorCount()) { + logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception occured in " + + methodName + " for " + pluginName, error); + return true; + + } else { + // Nope - only print the error count occationally + if (isPowerOfTwo(errorCount)) { + logger.log(Level.SEVERE, "[" + pluginName + "] Unhandled exception number " + errorCount + " occured in " + + methodName + " for " + pluginName, error); + } + return false; + } + } + + /** + * Determine if a given number is a power of two. + *

+ * That is, if there exists an N such that 2^N = number. + * @param number - the number to check. + * @return TRUE if the given number is a power of two, FALSE otherwise. + */ + private boolean isPowerOfTwo(int number) { + return (number & (number - 1)) == 0; + } + + @Override + public void reportWarning(Object sender, ReportBuilder reportBuilder) { + if (reportBuilder == null) + throw new IllegalArgumentException("reportBuilder cannot be NULL."); + + reportWarning(sender, reportBuilder.build()); + } + + @Override + public void reportWarning(Object sender, Report report) { + String message = "[" + pluginName + "] [" + getSenderName(sender) + "] " + report.getReportMessage(); + + // Print the main warning + if (report.getException() != null) { + logger.log(Level.WARNING, message, report.getException()); + } else { + logger.log(Level.WARNING, message); + } + + // Parameters? + if (report.hasCallerParameters()) { + // Write it + logger.log(Level.WARNING, printParameters(report.getCallerParameters())); + } + } + + /** + * Retrieve the name of a sender class. + * @param sender - sender object. + * @return The name of the sender's class. + */ + private String getSenderName(Object sender) { + if (sender != null) + return ReportType.getSenderClass(sender).getSimpleName(); + else + return "NULL"; + } + + @Override + public void reportDetailed(Object sender, ReportBuilder reportBuilder) { + reportDetailed(sender, reportBuilder.build()); + } + + @Override + public void reportDetailed(Object sender, Report report) { + final Plugin plugin = pluginReference.get(); + final int errorCount = internalErrorCount.incrementAndGet(); + + // Do not overtly spam the server! + if (errorCount > getMaxErrorCount()) { + // Only allow the error count at rare occations + if (isPowerOfTwo(errorCount)) { + // Permit it - but print the number of exceptions first + reportWarning(this, Report.newBuilder(REPORT_EXCEPTION_COUNT).messageParam(errorCount).build()); + } else { + // NEVER SPAM THE CONSOLE + return; + } + } + + StringWriter text = new StringWriter(); + PrintWriter writer = new PrintWriter(text); + + // Helpful message + writer.println("[" + pluginName + "] INTERNAL ERROR: " + report.getReportMessage()); + 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 (report.getException() != null) { + report.getException().printStackTrace(writer); + } + + // Data dump! + writer.println(" ===== DUMP ====="); + + // Relevant parameters + if (report.hasCallerParameters()) { + printParameters(writer, report.getCallerParameters()); + } + + // 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)); + + // And plugin + if (plugin != null) { + writer.println("Version:"); + writer.println(addPrefix(plugin.toString(), SECOND_LEVEL_PREFIX)); + } + + // Add the server version too + 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.", report.getReportMessage(), report.getException(), sender), + ERROR_PERMISSION + ); + } + } + + // Make sure it is reported + logger.severe(addPrefix(text.toString(), prefix)); + } + + private String printParameters(Object... parameters) { + StringWriter writer = new StringWriter(); + + // Print and retrieve the string buffer + printParameters(new PrintWriter(writer), parameters); + return writer.toString(); + } + + private void printParameters(PrintWriter writer, Object[] parameters) { + 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)); + } + } + + /** + * 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); + } + + /** + * Retrieve a string representation of the given object. + * @param value - object to convert. + * @return String representation. + */ + protected String getStringDescription(Object value) { + // We can't only rely on toString. + if (value == null) { + return "[NULL]"; + } if (isSimpleType(value) || value instanceof Class) { + return value.toString(); + } else { + try { + if (!apacheCommonsMissing) + return (ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null)); + } catch (Throwable ex) { + // Apache is probably missing + apacheCommonsMissing = true; + } + + // Use our custom object printer instead + try { + return PrettyPrinter.printObject(value, value.getClass(), Object.class); + } catch (IllegalAccessException e) { + return "[Error: " + e.getMessage() + "]"; + } + } + } + + /** + * 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()); + } + + /** + * Retrieve the current number of errors printed through {@link #reportDetailed(Object, Report)}. + * @return Number of errors printed. + */ + public int getErrorCount() { + return internalErrorCount.get(); + } + + /** + * Set the number of errors printed. + * @param errorCount - new number of errors printed. + */ + public void setErrorCount(int errorCount) { + internalErrorCount.set(errorCount); + } + + /** + * Retrieve the maximum number of errors we can print before we begin suppressing errors. + * @return Maximum number of errors. + */ + public int getMaxErrorCount() { + return maxErrorCount; + } + + /** + * Set the maximum number of errors we can print before we begin suppressing errors. + * @param maxErrorCount - new max count. + */ + public void setMaxErrorCount(int maxErrorCount) { + this.maxErrorCount = maxErrorCount; + } + + /** + * Adds the given global parameter. It will be included in every error report. + *

+ * Both key and value must be non-null. + * @param key - name of parameter. + * @param value - the global parameter itself. + */ + public void addGlobalParameter(String key, Object value) { + if (key == null) + throw new IllegalArgumentException("key cannot be NULL."); + if (value == null) + throw new IllegalArgumentException("value cannot be NULL."); + + globalParameters.put(key, value); + } + + /** + * Retrieve a global parameter by its key. + * @param key - key of the parameter to retrieve. + * @return The value of the global parameter, or NULL if not found. + */ + public Object getGlobalParameter(String key) { + if (key == null) + throw new IllegalArgumentException("key cannot be NULL."); + + return globalParameters.get(key); + } + + /** + * Reset all global parameters. + */ + public void clearGlobalParameters() { + globalParameters.clear(); + } + + /** + * Retrieve a set of every registered global parameter. + * @return Set of all registered global parameters. + */ + public Set globalParameters() { + return globalParameters.keySet(); + } + + /** + * Retrieve the support URL that will be added to all detailed reports. + * @return Support URL. + */ + public String getSupportURL() { + return supportURL; + } + + /** + * Set the support URL that will be added to all detailed reports. + * @param supportURL - the new support URL. + */ + public void setSupportURL(String supportURL) { + this.supportURL = supportURL; + } + + /** + * Retrieve the prefix to apply to every line in the error reports. + * @return Error report prefix. + */ + public String getPrefix() { + return prefix; + } + + /** + * Set the prefix to apply to every line in the error reports. + * @param prefix - new prefix. + */ + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + /** + * Retrieve the current logger that is used to print all reports. + * @return The current logger. + */ + public Logger getLogger() { + return logger; + } + + /** + * Set the current logger that is used to print all reports. + * @param logger - new logger. + */ + public void setLogger(Logger logger) { + this.logger = logger; + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java index e795677c..8e98a5a9 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/error/ReportType.java @@ -46,6 +46,39 @@ public class ReportType { return errorFormat; } + /** + * Retrieve the class of the given sender. + *

+ * If the sender is already a Class, we return it. + * @param sender - the sender to look up. + * @return The class of the sender. + */ + public static Class getSenderClass(Object sender) { + if (sender == null) + throw new IllegalArgumentException("sender cannot be NUll."); + else if (sender instanceof Class) + return (Class) sender; + else + return sender.getClass(); + } + + /** + * Retrieve the full canonical name of a given report type. + *

+ * Note that the sender may be a class (for static callers), in which + * case it will be used directly instead of its getClass() method. + *

+ * It is thus not advisable for class classes to report reports. + * @param sender - the sender, or its class. + * @param type - the report type. + * @return The full canonical name. + */ + public static String getReportName(Object sender, ReportType type) { + if (sender == null) + throw new IllegalArgumentException("sender cannot be NUll."); + return getReportName(getSenderClass(sender), type); + } + /** * Retrieve the full canonical name of a given report type. *

@@ -54,7 +87,7 @@ public class ReportType { * @param type - the report instance. * @return The full canonical name. */ - public static String getReportName(Class sender, ReportType type) { + private static String getReportName(Class sender, ReportType type) { if (sender == null) throw new IllegalArgumentException("sender cannot be NUll."); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java index eaef213f..006f650b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/DelayedPacketManager.java @@ -362,6 +362,10 @@ public class DelayedPacketManager implements ProtocolManager, InternalManager { return asyncManager; } + /** + * Update the asynchronous manager. This must be set. + * @param asyncManager - the asynchronous manager. + */ public void setAsynchronousManager(AsynchronousManager asyncManager) { this.asyncManager = asyncManager; } 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 c40fd81d..b0267334 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -268,6 +268,10 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // We need to delay this until we know if Netty is enabled final DelayedPacketManager delayed = new DelayedPacketManager(reporter); + // They must reference each other + delayed.setAsynchronousManager(asyncManager); + asyncManager.setManager(delayed); + Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), new FutureCallback() { @Override public void onSuccess(WorldInitEvent event) { @@ -297,7 +301,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok @Override public void onFailure(Throwable error) { - reporter.reportWarning(this, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); + reporter.reportWarning(PacketFilterManager.class, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); } }); From 47a538270902cb44eb94d67bf4a4b8bc4805e721 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 6 Jul 2013 08:06:16 +0200 Subject: [PATCH 13/20] Properly handle reloads on Spigot. The code is getting uglier and uglier ... --- .../injector/PacketFilterManager.java | 72 ++++++++++++------- 1 file changed, 46 insertions(+), 26 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 b0267334..a95309ed 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -22,6 +22,7 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -271,39 +272,58 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // They must reference each other delayed.setAsynchronousManager(asyncManager); asyncManager.setManager(delayed); - - Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), new FutureCallback() { + + final Callable registerSpigot = new Callable() { @Override - public void onSuccess(WorldInitEvent event) { - // Nevermind - if (delayed.isClosed()) - return; + public Object call() throws Exception { + // Now we are probably able to check for Netty + InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null); + Object connection = inspector.getServerConnection(); + + // Use netty if we have a non-standard ServerConnection class + boolean useNetty = !MinecraftReflection.isMinecraftObject(connection); - try { - // Now we are probably able to check for Netty - InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null); - Object connection = inspector.getServerConnection(); + // Switch to the standard manager + delayed.setDelegate(new PacketFilterManager( + classLoader, server, library, asyncManager, mcVersion, unhookTask, reporter, useNetty) + ); + + // Reference this manager directly + asyncManager.setManager(delayed.getDelegate()); + return null; + } + }; - // Use netty if we have a non-standard ServerConnection class - boolean useNetty = !MinecraftReflection.isMinecraftObject(connection); + // If the server hasn't loaded yet - wait + if (server.getWorlds().size() == 0) { + Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), new FutureCallback() { + @Override + public void onSuccess(WorldInitEvent event) { + // Nevermind + if (delayed.isClosed()) + return; - // Switch to the standard manager - delayed.setDelegate(new PacketFilterManager( - classLoader, server, library, asyncManager, mcVersion, unhookTask, reporter, useNetty) - ); - // Reference this manager directly - asyncManager.setManager(delayed.getDelegate()); - - } catch (Exception e) { - onFailure(e); + try { + registerSpigot.call(); + } catch (Exception e) { + onFailure(e); + } } - } + + @Override + public void onFailure(Throwable error) { + reporter.reportWarning(PacketFilterManager.class, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); + } + }); - @Override - public void onFailure(Throwable error) { - reporter.reportWarning(PacketFilterManager.class, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); + } else { + // Do it now + try { + registerSpigot.call(); + } catch (Exception e) { + e.printStackTrace(); } - }); + } // Let plugins use this version instead return delayed; From 00ff832e078289ca81f4a1c2c401366317580207 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sat, 6 Jul 2013 16:32:42 +0200 Subject: [PATCH 14/20] Adding the two new packets for 1.6.1. --- ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java b/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java index b4337dca..bd5b91fe 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java @@ -95,6 +95,7 @@ public final class Packets { public static final int MOB_EFFECT = 41; public static final int REMOVE_MOB_EFFECT = 42; public static final int SET_EXPERIENCE = 43; + public static final int UDATE_ATTRIBUTES = 44; public static final int MAP_CHUNK = 51; public static final int MULTI_BLOCK_CHANGE = 52; public static final int BLOCK_CHANGE = 53; @@ -199,6 +200,7 @@ public final class Packets { public static final int BLOCK_ITEM_SWITCH = 16; public static final int ARM_ANIMATION = 18; public static final int ENTITY_ACTION = 19; + public static final int PLAYER_INPUT = 27; public static final int CLOSE_WINDOW = 101; public static final int WINDOW_CLICK = 102; public static final int TRANSACTION = 106; From fb7f80b64665483715103f54199d92ecd051f1f8 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 7 Jul 2013 11:26:10 +0200 Subject: [PATCH 15/20] Fix typo before it's too late. --- ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java b/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java index bd5b91fe..bbbad165 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/Packets.java @@ -95,7 +95,7 @@ public final class Packets { public static final int MOB_EFFECT = 41; public static final int REMOVE_MOB_EFFECT = 42; public static final int SET_EXPERIENCE = 43; - public static final int UDATE_ATTRIBUTES = 44; + public static final int UPDATE_ATTRIBUTES = 44; public static final int MAP_CHUNK = 51; public static final int MULTI_BLOCK_CHANGE = 52; public static final int BLOCK_CHANGE = 53; From 6fe7fe46f3e21dd914857bb0e63f4db3173ffd29 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Sun, 7 Jul 2013 14:03:07 +0200 Subject: [PATCH 16/20] Use a builder pattern instead of a constructor with 8 parameters. Also make use of the fact that Spigot may have initialized its server connection (in the latest version). --- .../comphenix/protocol/ProtocolLibrary.java | 10 +- .../injector/PacketFilterBuilder.java | 255 ++++++++++++++++++ .../injector/PacketFilterManager.java | 123 ++------- .../player/InjectedServerConnection.java | 22 ++ 4 files changed, 301 insertions(+), 109 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java index 07330826..29dfebaf 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/ProtocolLibrary.java @@ -173,8 +173,14 @@ public class ProtocolLibrary extends JavaPlugin { updater = new Updater(this, logger, "protocollib", getFile(), "protocol.info"); unhookTask = new DelayedSingleTask(this); - protocolManager = PacketFilterManager.createManager( - getClassLoader(), getServer(), this, version, unhookTask, reporter); + protocolManager = PacketFilterManager.newBuilder(). + classLoader(getClassLoader()). + server(getServer()). + library(this). + minecraftVersion(version). + unhookTask(unhookTask). + reporter(reporter). + build(); // Setup error reporter detailedReporter.addGlobalParameter("manager", protocolManager); diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java new file mode 100644 index 00000000..1edad3c8 --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterBuilder.java @@ -0,0 +1,255 @@ +package com.comphenix.protocol.injector; + +import javax.annotation.Nonnull; + +import org.bukkit.Server; +import org.bukkit.event.world.WorldInitEvent; +import org.bukkit.plugin.Plugin; + +import com.comphenix.executors.BukkitFutures; +import com.comphenix.protocol.async.AsyncFilterManager; +import com.comphenix.protocol.error.ErrorReporter; +import com.comphenix.protocol.error.Report; +import com.comphenix.protocol.error.ReportType; +import com.comphenix.protocol.injector.player.InjectedServerConnection; +import com.comphenix.protocol.injector.spigot.SpigotPacketInjector; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; + +public class PacketFilterBuilder { + public static final ReportType REPORT_TEMPORARY_EVENT_ERROR = new ReportType("Unable to register or handle temporary event."); + + private ClassLoader classLoader; + private Server server; + private Plugin library; + private MinecraftVersion mcVersion; + private DelayedSingleTask unhookTask; + private ErrorReporter reporter; + + // Whether or not we need to enable Netty + private AsyncFilterManager asyncManager; + private boolean nettyEnabled; + + /** + * Update the current class loader. + * @param classLoader - current class loader. + * @return This builder, for chaining. + */ + public PacketFilterBuilder classLoader(@Nonnull ClassLoader classLoader) { + if (classLoader == null) + throw new IllegalArgumentException("classLoader cannot be NULL."); + this.classLoader = classLoader; + return this; + } + + /** + * Set the current server. + * @param server - current server. + * @return This builder, for chaining. + */ + public PacketFilterBuilder server(@Nonnull Server server) { + if (server == null) + throw new IllegalArgumentException("server cannot be NULL."); + this.server = server; + return this; + } + + /** + * Set a reference to the plugin instance of ProtocolLib. + * @param library - plugin instance. + * @return This builder, for chaining. + */ + public PacketFilterBuilder library(@Nonnull Plugin library) { + if (library == null) + throw new IllegalArgumentException("library cannot be NULL."); + this.library = library; + return this; + } + + /** + * Set the current Minecraft version. + * @param mcVersion - Minecraft version. + * @return This builder, for chaining. + */ + public PacketFilterBuilder minecraftVersion(@Nonnull MinecraftVersion mcVersion) { + if (mcVersion == null) + throw new IllegalArgumentException("minecraftVersion cannot be NULL."); + this.mcVersion = mcVersion; + return this; + } + + /** + * Set the task used to delay unhooking when ProtocolLib is no in use. + * @param unhookTask - the unhook task. + * @return This builder, for chaining. + */ + public PacketFilterBuilder unhookTask(@Nonnull DelayedSingleTask unhookTask) { + if (unhookTask == null) + throw new IllegalArgumentException("unhookTask cannot be NULL."); + this.unhookTask = unhookTask; + return this; + } + + /** + * Set the error reporter. + * @param reporter - new error reporter. + * @return This builder, for chaining. + */ + public PacketFilterBuilder reporter(@Nonnull ErrorReporter reporter) { + if (reporter == null) + throw new IllegalArgumentException("reporter cannot be NULL."); + this.reporter = reporter; + return this; + } + + /** + * Determine if we should prepare to hook Netty in Spigot. + *

+ * This is calculated in the {@link #build()} method. + * @return TRUE if we should, FALSE otherwise. + */ + public boolean isNettyEnabled() { + return nettyEnabled; + } + + /** + * Retrieve the class loader set in this builder. + * @return The class loader. + */ + public ClassLoader getClassLoader() { + return classLoader; + } + + /** + * Retrieve the current CraftBukkit server. + * @return Current server. + */ + public Server getServer() { + return server; + } + + /** + * Retrieve a reference to the current ProtocolLib instance. + * @return ProtocolLib. + */ + public Plugin getLibrary() { + return library; + } + + /** + * Retrieve the current Minecraft version. + * @return Current version. + */ + public MinecraftVersion getMinecraftVersion() { + return mcVersion; + } + + /** + * Retrieve the task that is used to delay unhooking when ProtocolLib is no in use. + * @return The unhook task. + */ + public DelayedSingleTask getUnhookTask() { + return unhookTask; + } + + /** + * Retrieve the error reporter. + * @return Error reporter. + */ + public ErrorReporter getReporter() { + return reporter; + } + + /** + * Retrieve the asynchronous manager. + *

+ * This is first constructed the {@link #build()} method. + * @return The asynchronous manager. + */ + public AsyncFilterManager getAsyncManager() { + return asyncManager; + } + + /** + * Create a new packet filter manager. + * @return A new packet filter manager. + */ + public InternalManager build() { + if (reporter == null) + throw new IllegalArgumentException("reporter cannot be NULL."); + if (classLoader == null) + throw new IllegalArgumentException("classLoader cannot be NULL."); + + asyncManager = new AsyncFilterManager(reporter, server.getScheduler()); + nettyEnabled = false; + + // Spigot + if (SpigotPacketInjector.canUseSpigotListener()) { + // If the server hasn't loaded yet - wait + if (InjectedServerConnection.getServerConnection(reporter, server) == null) { + // We need to delay this until we know if Netty is enabled + final DelayedPacketManager delayed = new DelayedPacketManager(reporter); + + // They must reference each other + delayed.setAsynchronousManager(asyncManager); + asyncManager.setManager(delayed); + + Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), + new FutureCallback() { + @Override + public void onSuccess(WorldInitEvent event) { + // Nevermind + if (delayed.isClosed()) + return; + + try { + registerSpigot(delayed); + } catch (Exception e) { + onFailure(e); + } + } + + @Override + public void onFailure(Throwable error) { + reporter.reportWarning(PacketFilterBuilder.this, Report + .newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); + } + }); + + System.out.println("Delaying due to Spigot"); + + // Let plugins use this version instead + return delayed; + } else { + nettyEnabled = !MinecraftReflection.isMinecraftObject( + InjectedServerConnection.getServerConnection(reporter, server)); + } + } + + // Otherwise - construct the packet filter manager right away + return buildInternal(); + } + + private void registerSpigot(DelayedPacketManager delayed) { + // Use netty if we have a non-standard ServerConnection class + nettyEnabled = !MinecraftReflection.isMinecraftObject( + InjectedServerConnection.getServerConnection(reporter, server)); + + // Switch to the standard manager + delayed.setDelegate(buildInternal()); + } + + /** + * Construct a new packet filter manager without checking for Netty. + * @return A new packet filter manager. + */ + private PacketFilterManager buildInternal() { + PacketFilterManager manager = new PacketFilterManager(this); + + // It's a cyclic reference, but it's too late to fix now + asyncManager.setManager(manager); + return manager; + } +} 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 a95309ed..2848cfe5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -22,7 +22,6 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -43,11 +42,9 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.PluginDisableEvent; -import org.bukkit.event.world.WorldInitEvent; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginManager; -import com.comphenix.executors.BukkitFutures; import com.comphenix.protocol.AsynchronousManager; import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.async.AsyncFilterManager; @@ -59,7 +56,6 @@ import com.comphenix.protocol.events.*; import com.comphenix.protocol.injector.packet.PacketInjector; import com.comphenix.protocol.injector.packet.PacketInjectorBuilder; import com.comphenix.protocol.injector.packet.PacketRegistry; -import com.comphenix.protocol.injector.player.InjectedServerConnection; import com.comphenix.protocol.injector.player.PlayerInjectionHandler; import com.comphenix.protocol.injector.player.PlayerInjectorBuilder; import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy; @@ -67,12 +63,9 @@ import com.comphenix.protocol.injector.spigot.SpigotPacketInjector; import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; public final class PacketFilterManager implements ProtocolManager, ListenerInvoker, InternalManager { @@ -94,8 +87,6 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok public static final ReportType REPORT_CANNOT_UNREGISTER_PLUGIN = new ReportType("Unable to handle disabled plugin."); public static final ReportType REPORT_PLUGIN_VERIFIER_ERROR = new ReportType("Verifier error: %s"); - public static final ReportType REPORT_TEMPORARY_EVENT_ERROR = new ReportType("Unable to register or handle temporary event."); - /** * Sets the inject hook type. Different types allow for maximum compatibility. * @author Kristian @@ -182,17 +173,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok /** * Only create instances of this class if protocol lib is disabled. */ - public PacketFilterManager( - ClassLoader classLoader, Server server, Plugin library, - AsyncFilterManager asyncManager, MinecraftVersion mcVersion, - final DelayedSingleTask unhookTask, - ErrorReporter reporter, boolean nettyEnabled) { - - if (reporter == null) - throw new IllegalArgumentException("reporter cannot be NULL."); - if (classLoader == null) - throw new IllegalArgumentException("classLoader cannot be NULL."); - + public PacketFilterManager(PacketFilterBuilder builder) { // Used to determine if injection is needed Predicate isInjectionNecessary = new Predicate() { @Override @@ -213,16 +194,16 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok this.sendingListeners = new SortedPacketListenerList(); // References - this.unhookTask = unhookTask; - this.server = server; - this.classLoader = classLoader; - this.reporter = reporter; + this.unhookTask = builder.getUnhookTask(); + this.server = builder.getServer(); + this.classLoader = builder.getClassLoader(); + this.reporter = builder.getReporter(); // The plugin verifier - this.pluginVerifier = new PluginVerifier(library); + this.pluginVerifier = new PluginVerifier(builder.getLibrary()); // Use the correct injection type - if (nettyEnabled) { + if (builder.isNettyEnabled()) { spigotInjector = new SpigotPacketInjector(classLoader, reporter, this, server); this.playerInjection = spigotInjector.getPlayerHandler(); this.packetInjector = spigotInjector.getPacketInjector(); @@ -236,7 +217,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok classLoader(classLoader). packetListeners(packetListeners). injectionFilter(isInjectionNecessary). - version(mcVersion). + version(builder.getMinecraftVersion()). buildHandler(); this.packetInjector = PacketInjectorBuilder.newBuilder(). @@ -246,7 +227,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok playerInjection(playerInjection). buildInjector(); } - this.asyncFilterManager = asyncManager; + this.asyncFilterManager = builder.getAsyncManager(); // Attempt to load the list of server and client packets try { @@ -256,87 +237,15 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok reporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_LOAD_PACKET_LIST).error(e)); } } - - public static InternalManager createManager( - final ClassLoader classLoader, final Server server, final Plugin library, - final MinecraftVersion mcVersion, final DelayedSingleTask unhookTask, - final ErrorReporter reporter) { - - final AsyncFilterManager asyncManager = new AsyncFilterManager(reporter, server.getScheduler()); - - // Spigot - if (SpigotPacketInjector.canUseSpigotListener()) { - // We need to delay this until we know if Netty is enabled - final DelayedPacketManager delayed = new DelayedPacketManager(reporter); - - // They must reference each other - delayed.setAsynchronousManager(asyncManager); - asyncManager.setManager(delayed); - final Callable registerSpigot = new Callable() { - @Override - public Object call() throws Exception { - // Now we are probably able to check for Netty - InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null); - Object connection = inspector.getServerConnection(); - - // Use netty if we have a non-standard ServerConnection class - boolean useNetty = !MinecraftReflection.isMinecraftObject(connection); - - // Switch to the standard manager - delayed.setDelegate(new PacketFilterManager( - classLoader, server, library, asyncManager, mcVersion, unhookTask, reporter, useNetty) - ); - - // Reference this manager directly - asyncManager.setManager(delayed.getDelegate()); - return null; - } - }; - - // If the server hasn't loaded yet - wait - if (server.getWorlds().size() == 0) { - Futures.addCallback(BukkitFutures.nextEvent(library, WorldInitEvent.class), new FutureCallback() { - @Override - public void onSuccess(WorldInitEvent event) { - // Nevermind - if (delayed.isClosed()) - return; - - try { - registerSpigot.call(); - } catch (Exception e) { - onFailure(e); - } - } - - @Override - public void onFailure(Throwable error) { - reporter.reportWarning(PacketFilterManager.class, Report.newBuilder(REPORT_TEMPORARY_EVENT_ERROR).error(error)); - } - }); - - } else { - // Do it now - try { - registerSpigot.call(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - // Let plugins use this version instead - return delayed; - } else { - // The standard manager - PacketFilterManager manager = new PacketFilterManager( - classLoader, server, library, asyncManager, mcVersion, unhookTask, reporter, false); - - asyncManager.setManager(manager); - return manager; - } + /** + * Construct a new packet filter builder. + * @return New builder. + */ + public static PacketFilterBuilder newBuilder() { + return new PacketFilterBuilder(); } - + @Override public AsynchronousManager getAsynchronousManager() { return asyncFilterManager; 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 b114feaf..7ae571c2 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 @@ -31,6 +31,7 @@ import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.injector.server.AbstractInputStreamLookup; +import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.ObjectWriter; @@ -98,6 +99,27 @@ public class InjectedServerConnection { this.netLoginInjector = netLoginInjector; } + /** + * Retrieve the current server connection. + * @param reporter - error reproter. + * @param server - the current server. + * @return The current server connection, or NULL if it hasn't been initialized yet. + * @throws FieldAccessException Reflection error. + */ + public static Object getServerConnection(ErrorReporter reporter, Server server) { + try { + // Now we are probably able to check for Netty + InjectedServerConnection inspector = new InjectedServerConnection(reporter, null, server, null); + return inspector.getServerConnection(); + } catch (IllegalAccessException e) { + throw new FieldAccessException("Reflection error.", e); + } catch (IllegalArgumentException e) { + throw new FieldAccessException("Corrupt data.", e); + } catch (InvocationTargetException e) { + throw new FieldAccessException("Minecraft error.", e); + } + } + /** * Initial reflective detective work. Will be automatically called by most methods in this class. */ From a4eb219a9a55834c6df3f435ce862bf7703e2e7e Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 10 Jul 2013 22:45:20 +0200 Subject: [PATCH 17/20] Update unit tests to CraftBukkit 1.6.2 --- ProtocolLib/.project | 4 ++-- ProtocolLib/pom.xml | 2 +- .../com/comphenix/protocol/utility/MinecraftReflection.java | 5 +++++ .../java/com/comphenix/protocol/BukkitInitialization.java | 6 +++--- .../com/comphenix/protocol/events/PacketContainerTest.java | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/ProtocolLib/.project b/ProtocolLib/.project index a1960c9e..d36fcbe8 100644 --- a/ProtocolLib/.project +++ b/ProtocolLib/.project @@ -11,12 +11,12 @@ - org.eclipse.m2e.core.maven2Builder + net.sourceforge.metrics.builder - net.sourceforge.metrics.builder + org.eclipse.m2e.core.maven2Builder diff --git a/ProtocolLib/pom.xml b/ProtocolLib/pom.xml index 0d7b1758..42862be5 100644 --- a/ProtocolLib/pom.xml +++ b/ProtocolLib/pom.xml @@ -214,7 +214,7 @@ org.bukkit craftbukkit - 1.5.1-R0.2-SNAPSHOT + 1.6.2-R0.1-SNAPSHOT provided diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index cd6af1d2..ef1a0151 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -219,6 +219,11 @@ public class MinecraftReflection { MINECRAFT_FULL_PACKAGE = minecraftPackage; CRAFTBUKKIT_PACKAGE = craftBukkitPackage; + // Make sure it exists + if (getMinecraftServerClass() == null) { + throw new IllegalArgumentException("Cannot find MinecraftServer for package " + minecraftPackage); + } + // Standard matcher setDynamicPackageMatcher(MINECRAFT_OBJECT); } diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java b/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java index 39b6b55d..988ac692 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/BukkitInitialization.java @@ -4,10 +4,10 @@ import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import net.minecraft.server.v1_5_R2.StatisticList; +import net.minecraft.server.v1_6_R2.StatisticList; // Will have to be updated for every version though -import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -63,6 +63,6 @@ public class BukkitInitialization { */ public static void initializePackage() { // Initialize reflection - MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_5_R2", "org.bukkit.craftbukkit.v1_5_R2"); + MinecraftReflection.setMinecraftPackage("net.minecraft.server.v1_6_R2", "org.bukkit.craftbukkit.v1_6_R2"); } } diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index d305adf4..5d3c75f2 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -22,7 +22,7 @@ import java.lang.reflect.Array; import java.util.List; // Will have to be updated for every version though -import org.bukkit.craftbukkit.v1_5_R2.inventory.CraftItemFactory; +import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory; import org.bukkit.Material; import org.bukkit.WorldType; From c590e4a825dbb5efe8251ad820fa2c26913e2362 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 10 Jul 2013 23:33:25 +0200 Subject: [PATCH 18/20] Update the stream serializer for Minecraft 1.6.2 --- .../protocol/utility/StreamSerializer.java | 26 +++++++++----- .../utility/StreamSerializerTest.java | 36 +++++++++++++++++++ 2 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java index 2a441149..a1d6dda5 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/StreamSerializer.java @@ -2,7 +2,9 @@ package com.comphenix.protocol.utility; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.DataInput; import java.io.DataInputStream; +import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.Method; @@ -13,6 +15,7 @@ import org.bukkit.inventory.ItemStack; import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; /** * Utility methods for reading and writing Minecraft objects to streams. @@ -37,11 +40,14 @@ public class StreamSerializer { public ItemStack deserializeItemStack(@Nonnull DataInputStream input) throws IOException { if (input == null) throw new IllegalArgumentException("Input stream cannot be NULL."); - if (readItemMethod == null) - readItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). - getMethodByParameters("readPacket", - MinecraftReflection.getItemStackClass(), - new Class[] {DataInputStream.class}); + if (readItemMethod == null) { + readItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( + FuzzyMethodContract.newBuilder(). + parameterCount(1). + parameterDerivedOf(DataInput.class). + returnDerivedOf(MinecraftReflection.getItemStackClass()). + build()); + } try { Object nmsItem = readItemMethod.invoke(null, input); @@ -88,10 +94,12 @@ public class StreamSerializer { Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack); if (writeItemMethod == null) - writeItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()). - getMethodByParameters("writePacket", new Class[] { - MinecraftReflection.getItemStackClass(), - DataOutputStream.class }); + writeItemMethod = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass()).getMethod( + FuzzyMethodContract.newBuilder(). + parameterCount(2). + parameterDerivedOf(MinecraftReflection.getItemStackClass(), 0). + parameterDerivedOf(DataOutput.class, 1). + build()); try { writeItemMethod.invoke(null, nmsItem, output); } catch (Exception e) { diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java new file mode 100644 index 00000000..0895023b --- /dev/null +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/utility/StreamSerializerTest.java @@ -0,0 +1,36 @@ +package com.comphenix.protocol.utility; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory; +import org.bukkit.inventory.ItemStack; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; + +import com.comphenix.protocol.BukkitInitialization; + +@RunWith(org.powermock.modules.junit4.PowerMockRunner.class) +@PrepareForTest(CraftItemFactory.class) +public class StreamSerializerTest { + @BeforeClass + public static void initializeBukkit() throws IllegalAccessException { + BukkitInitialization.initializeItemMeta(); + } + + @Test + public void testSerializer() throws IOException { + ItemStack before = new ItemStack(Material.GOLD_AXE); + + StreamSerializer serializer = new StreamSerializer(); + String data = serializer.serializeItemStack(before); + ItemStack after = serializer.deserializeItemStack(data); + + assertEquals(before.getType(), after.getType()); + assertEquals(before.getAmount(), after.getAmount()); + } +} From 1000378b7819f34aa6bbfb5020d4d8e6bd8a378a Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Wed, 10 Jul 2013 23:41:49 +0200 Subject: [PATCH 19/20] Update packet container serialization for Minecraft 1.6.2. --- .../comphenix/protocol/events/PacketContainer.java | 14 +++++++++++--- .../protocol/events/PacketContainerTest.java | 12 ++++++++++++ 2 files changed, 23 insertions(+), 3 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 46d5e7f2..144a0a97 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -17,7 +17,9 @@ package com.comphenix.protocol.events; +import java.io.DataInput; import java.io.DataInputStream; +import java.io.DataOutput; import java.io.DataOutputStream; import java.io.IOException; import java.io.ObjectInputStream; @@ -49,6 +51,7 @@ import com.comphenix.protocol.reflect.cloning.CollectionCloner; import com.comphenix.protocol.reflect.cloning.FieldCloner; import com.comphenix.protocol.reflect.cloning.ImmutableDetector; import com.comphenix.protocol.reflect.cloning.AggregateCloner.BuilderParameters; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.reflect.instances.DefaultInstances; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.StreamSerializer; @@ -456,7 +459,7 @@ public class PacketContainer implements Serializable { try { // Call the write-method - getMethodLazily(writeMethods, handle.getClass(), "write", DataOutputStream.class). + getMethodLazily(writeMethods, handle.getClass(), "write", DataOutput.class). invoke(handle, new DataOutputStream(output)); } catch (IllegalArgumentException e) { @@ -483,7 +486,7 @@ public class PacketContainer implements Serializable { // Call the read method try { - getMethodLazily(readMethods, handle.getClass(), "read", DataInputStream.class). + getMethodLazily(readMethods, handle.getClass(), "read", DataInput.class). invoke(handle, new DataInputStream(input)); } catch (IllegalArgumentException e) { @@ -513,7 +516,12 @@ public class PacketContainer implements Serializable { // Atomic operation if (method == null) { - Method initialized = FuzzyReflection.fromClass(handleClass).getMethodByParameters(methodName, parameterClass); + Method initialized = FuzzyReflection.fromClass(handleClass).getMethod( + FuzzyMethodContract.newBuilder(). + parameterCount(1). + parameterDerivedOf(parameterClass). + returnTypeVoid(). + build()); method = lookup.putIfAbsent(handleClass, initialized); // Use our version if we succeeded diff --git a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 5d3c75f2..917b160a 100644 --- a/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/ProtocolLib/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.*; import java.lang.reflect.Array; import java.util.List; +import org.apache.commons.lang.SerializationUtils; // Will have to be updated for every version though import org.bukkit.craftbukkit.v1_6_R2.inventory.CraftItemFactory; @@ -305,6 +306,17 @@ public class PacketContainerTest { watchableAccessor.write(0, list); assertEquals(list, watchableAccessor.read(0)); } + + @Test + public void testSerialization() { + PacketContainer chat = new PacketContainer(3); + chat.getStrings().write(0, "Test"); + + PacketContainer copy = (PacketContainer) SerializationUtils.clone(chat); + + assertEquals(3, copy.getID()); + assertEquals("Test", copy.getStrings().read(0)); + } @Test public void testDeepClone() { From f2125623f685d45b8f75ba14ccd21e41f8429b65 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 11 Jul 2013 00:04:42 +0200 Subject: [PATCH 20/20] Correct the fallback method for retrieving WatchableObjects. --- .../com/comphenix/protocol/utility/MinecraftReflection.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index ef1a0151..40fe3d58 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -18,7 +18,7 @@ package com.comphenix.protocol.utility; import java.io.DataInputStream; -import java.io.DataOutputStream; +import java.io.DataOutput; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -797,7 +797,7 @@ public class MinecraftReflection { Method selected = FuzzyReflection.fromClass(getDataWatcherClass(), true). getMethod(FuzzyMethodContract.newBuilder(). requireModifier(Modifier.STATIC). - parameterSuperOf(DataOutputStream.class, 0). + parameterDerivedOf(DataOutput.class, 0). parameterMatches(getMinecraftObjectMatcher(), 1). build());