Replace CGLib with ByteBuddy (#984)

- The gclib dependency in the EnchancerFactory has been removed. All classes that used the actual factory part of it have been updated to use bytebuddy instead. This class will have to be removed at some point, but at the moment it is still used for accessing its class loader.
- Renamed EnhancerFactory to ByteBuddyFactory. All ByteBuddy actions should go through this now. Every subclass created here implements the ByteBuddyGenerated interface. This makes it possible to recognize classes generated using ByteBuddy (by default, it doesn't leave such a trace).
- Removed the method DefaultInstances#forEnhancer(Enhancer). This method isn't used anywhere; the last trace of usage of the method I could find was in 2013 (in the NetworkServerInjector). External plugins (I couldn't find any that used it), they should really have their own implementation, given that they already require an instance of an Enchancer. As such, I feel it is safe to remove rather than update it.
This commit is contained in:
PimvanderLoos 2021-01-04 06:24:34 +01:00 committed by GitHub
parent 26274fed52
commit b54dd49426
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 725 additions and 441 deletions

14
pom.xml
View File

@ -51,8 +51,8 @@
<relocations> <relocations>
<relocation> <relocation>
<pattern>net.sf</pattern> <pattern>net.bytebuddy</pattern>
<shadedPattern>com.comphenix.net.sf</shadedPattern> <shadedPattern>com.comphenix.net.bytebuddy</shadedPattern>
</relocation> </relocation>
</relocations> </relocations>
@ -100,6 +100,8 @@
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version> <version>3.0.0-M5</version>
<configuration> <configuration>
<trimStackTrace>false</trimStackTrace>
<useFile>false</useFile>
<systemProperties> <systemProperties>
<property> <property>
<name>projectVersion</name> <name>projectVersion</name>
@ -291,11 +293,11 @@
<version>${spigot.version}</version> <version>${spigot.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/net.bytebuddy/byte-buddy -->
<dependency> <dependency>
<groupId>cglib</groupId> <groupId>net.bytebuddy</groupId>
<artifactId>cglib-nodep</artifactId> <artifactId>byte-buddy</artifactId>
<version>3.2.5</version> <version>1.10.16</version>
<scope>compile</scope>
</dependency> </dependency>
<!-- Testing dependencies --> <!-- Testing dependencies -->

View File

@ -29,13 +29,12 @@ import java.util.WeakHashMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import net.sf.cglib.proxy.Factory;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.utility.ByteBuddyGenerated;
import com.comphenix.protocol.PacketType.Sender; import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.concurrency.PacketTypeSet; import com.comphenix.protocol.concurrency.PacketTypeSet;
import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.ErrorReporter;
@ -464,7 +463,7 @@ class CommandPacket extends CommandBase {
// Get the first Minecraft super class // Get the first Minecraft super class
while (clazz != null && clazz != Object.class && while (clazz != null && clazz != Object.class &&
(!MinecraftReflection.isMinecraftClass(clazz) || (!MinecraftReflection.isMinecraftClass(clazz) ||
Factory.class.isAssignableFrom(clazz))) { ByteBuddyGenerated.class.isAssignableFrom(clazz))) {
clazz = clazz.getSuperclass(); clazz = clazz.getSuperclass();
} }

View File

@ -37,7 +37,7 @@ import com.comphenix.protocol.reflect.compiler.BackgroundCompiler;
import com.comphenix.protocol.updater.Updater; import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType; import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.ChatExtensions; import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.EnhancerFactory; import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.base.Splitter; import com.google.common.base.Splitter;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
@ -144,7 +144,7 @@ public class ProtocolLib extends JavaPlugin {
ProtocolLogger.init(this); ProtocolLogger.init(this);
// Initialize enhancer factory // Initialize enhancer factory
EnhancerFactory.getInstance().setClassLoader(getClassLoader()); ByteBuddyFactory.getInstance().setClassLoader(getClassLoader());
// Add global parameters // Add global parameters
DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this); DetailedErrorReporter detailedReporter = new DetailedErrorReporter(this);

View File

@ -21,20 +21,32 @@ import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function;
import net.sf.cglib.proxy.Enhancer; import net.bytebuddy.description.ByteCodeElement;
import net.sf.cglib.proxy.MethodInterceptor; import net.bytebuddy.description.modifier.Visibility;
import net.sf.cglib.proxy.MethodProxy; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.Pipe;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.entity.EntityType; import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import com.comphenix.protocol.utility.EnhancerFactory; import com.comphenix.protocol.utility.ByteBuddyFactory;
/** /**
* Represents a player object that can be serialized by Java. * Represents a player object that can be serialized by Java.
@ -60,9 +72,8 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
private boolean playedBefore; private boolean playedBefore;
private boolean online; private boolean online;
private boolean whitelisted; private boolean whitelisted;
// Proxy helper private static final Constructor<?> proxyPlayerConstructor = setupProxyPlayerConstructor();
private static Map<String, Method> lookup = new ConcurrentHashMap<String, Method>();
/** /**
* Constructor used by serialization. * Constructor used by serialization.
@ -243,42 +254,78 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
} }
/** /**
* Retrieve a player object that implements OfflinePlayer by refering to this object. * Retrieve a player object that implements OfflinePlayer by referring to this object.
* <p> * <p>
* All other methods cause an exception. * All other methods cause an exception.
* @return Proxy object. * @return Proxy object.
*/ */
public Player getProxyPlayer() { public Player getProxyPlayer() {
try {
// Remember to initialize the method filter return (Player) proxyPlayerConstructor.newInstance(this);
if (lookup.size() == 0) { } catch (IllegalAccessException e) {
// Add all public methods throw new RuntimeException("Cannot access reflection.", e);
for (Method method : OfflinePlayer.class.getMethods()) { } catch (InstantiationException e) {
lookup.put(method.getName(), method); throw new RuntimeException("Cannot instantiate object.", e);
} } catch (InvocationTargetException e) {
throw new RuntimeException("Error in invocation.", e);
} }
}
// MORE CGLIB magic!
Enhancer ex = EnhancerFactory.getInstance().createEnhancer();
ex.setSuperclass(Player.class);
ex.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// There's no overloaded methods, so we don't care
Method offlineMethod = lookup.get(method.getName());
// Ignore all other methods
if (offlineMethod == null) {
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.");
}
// Invoke our on method private static Constructor<? extends Player> setupProxyPlayerConstructor()
return offlineMethod.invoke(SerializedOfflinePlayer.this, args); {
} final Method[] offlinePlayerMethods = OfflinePlayer.class.getMethods();
}); final String[] methodNames = new String[offlinePlayerMethods.length];
for (int idx = 0; idx < offlinePlayerMethods.length; ++idx)
return (Player) ex.create(); methodNames[idx] = offlinePlayerMethods[idx].getName();
final ElementMatcher.Junction<ByteCodeElement> forwardedMethods = ElementMatchers.namedOneOf(methodNames);
try {
final MethodDelegation forwarding = MethodDelegation.withDefaultConfiguration()
.withBinders(Pipe.Binder.install(Function.class))
.to(new Object() {
@RuntimeType
public Object intercept(@Pipe Function<OfflinePlayer, Object> pipe,
@FieldValue("offlinePlayer") OfflinePlayer proxy) {
return pipe.apply(proxy);
}
});
final InvocationHandlerAdapter throwException = InvocationHandlerAdapter.of((obj, method, args) -> {
throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for offline players.");
});
return ByteBuddyFactory.getInstance()
.createSubclass(PlayerUnion.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name(SerializedOfflinePlayer.class.getPackage().getName() + ".PlayerInvocationHandler")
.defineField("offlinePlayer", OfflinePlayer.class, Visibility.PRIVATE)
.defineConstructor(Visibility.PUBLIC)
.withParameters(OfflinePlayer.class)
.intercept(MethodCall.invoke(Object.class.getDeclaredConstructor())
.andThen(FieldAccessor.ofField("offlinePlayer").setsArgumentAt(0)))
.method(forwardedMethods)
.intercept(forwarding)
.method(ElementMatchers.not(forwardedMethods))
.intercept(throwException)
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getDeclaredConstructor(OfflinePlayer.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find Player constructor!", e);
}
}
/**
* This interface extends both OfflinePlayer and Player (in that order) so that the class generated by ByteBuddy
* looks at OfflinePlayer's methods first while still being a Player.
*/
private interface PlayerUnion extends OfflinePlayer, Player
{
} }
} }

View File

@ -30,6 +30,8 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.VolatileField; import com.comphenix.protocol.reflect.VolatileField;
import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.ByteBuddyGenerated;
import com.comphenix.protocol.utility.MinecraftFields; import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftProtocolVersion; import com.comphenix.protocol.utility.MinecraftProtocolVersion;
@ -37,7 +39,9 @@ import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.ObjectReconstructor; import com.comphenix.protocol.utility.ObjectReconstructor;
import com.comphenix.protocol.wrappers.Pair; import com.comphenix.protocol.wrappers.Pair;
import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.*; import io.netty.channel.*;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
@ -45,7 +49,7 @@ import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.AttributeKey; import io.netty.util.AttributeKey;
import io.netty.util.internal.TypeParameterMatcher; import io.netty.util.internal.TypeParameterMatcher;
import net.sf.cglib.proxy.Factory;
import org.apache.commons.lang.Validate; import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -224,7 +228,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
synchronized (networkManager) { synchronized (networkManager) {
if (closed) if (closed)
return false; return false;
if (originalChannel instanceof Factory) if (originalChannel instanceof ByteBuddyFactory)
return false; return false;
if (!originalChannel.isActive()) if (!originalChannel.isActive())
return false; return false;
@ -703,7 +707,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
*/ */
private void disconnect(String message) { private void disconnect(String message) {
// If we're logging in, we can only close the channel // If we're logging in, we can only close the channel
if (playerConnection == null || player instanceof Factory) { if (playerConnection == null || player instanceof ByteBuddyGenerated) {
originalChannel.disconnect(); originalChannel.disconnect();
} else { } else {
// Call the disconnect method // Call the disconnect method
@ -736,7 +740,7 @@ public class ChannelInjector extends ByteToMessageDecoder implements Injector {
// Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active // Attempt to send the packet with NetworkMarker.handle(), or the PlayerConnection if its active
try { try {
if (player instanceof Factory) { if (player instanceof ByteBuddyGenerated) {
MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet); MinecraftMethods.getNetworkManagerHandleMethod().invoke(networkManager, packet);
} else { } else {
MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet); MinecraftMethods.getSendPacketMethod().invoke(getPlayerConnection(), packet);

View File

@ -17,15 +17,25 @@
package com.comphenix.protocol.injector.server; package com.comphenix.protocol.injector.server;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback; import com.comphenix.protocol.utility.ByteBuddyFactory;
import net.sf.cglib.proxy.CallbackFilter; import net.bytebuddy.description.ByteCodeElement;
import net.sf.cglib.proxy.Enhancer; import net.bytebuddy.description.modifier.Visibility;
import net.sf.cglib.proxy.MethodInterceptor; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.sf.cglib.proxy.MethodProxy; import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.sf.cglib.proxy.NoOp; import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -38,8 +48,7 @@ import com.comphenix.protocol.utility.ChatExtensions;
* Create fake player instances that represents pre-authenticated clients. * Create fake player instances that represents pre-authenticated clients.
*/ */
public class TemporaryPlayerFactory { public class TemporaryPlayerFactory {
// Prevent too many class creations private static final Constructor<? extends Player> temporaryPlayerConstructor = setupProxyPlayerConstructor();
private static CallbackFilter callbackFilter;
/** /**
* Retrieve the injector from a given player if it contains one. * Retrieve the injector from a given player if it contains one.
@ -82,21 +91,34 @@ public class TemporaryPlayerFactory {
* @return A temporary player instance. * @return A temporary player instance.
*/ */
public Player createTemporaryPlayer(final Server server) { public Player createTemporaryPlayer(final Server server) {
try {
// Default implementation return temporaryPlayerConstructor.newInstance(server);
Callback implementation = new MethodInterceptor() { } catch (IllegalAccessException e) {
@Override throw new RuntimeException("Cannot access reflection.", e);
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { } catch (InstantiationException e) {
throw new RuntimeException("Cannot instantiate object.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error in invocation.", e);
}
}
private static Constructor<? extends Player> setupProxyPlayerConstructor()
{
final MethodDelegation implementation = MethodDelegation.to(new Object() {
@RuntimeType
public Object delegate(@This Object obj, @Origin Method method, @FieldValue("server") Server server,
@AllArguments Object... args) throws Throwable {
String methodName = method.getName(); String methodName = method.getName();
SocketInjector injector = ((TemporaryPlayer) obj).getInjector(); SocketInjector injector = ((TemporaryPlayer) obj).getInjector();
if (injector == null) if (injector == null)
throw new IllegalStateException("Unable to find injector."); throw new IllegalStateException("Unable to find injector.");
// Use the socket to get the address // Use the socket to get the address
else if (methodName.equals("getPlayer")) else if (methodName.equals("getPlayer"))
return injector.getUpdatedPlayer(); return injector.getUpdatedPlayer();
else if (methodName.equals("getAddress")) else if (methodName.equals("getAddress"))
return injector.getAddress(); return injector.getAddress();
else if (methodName.equals("getServer")) else if (methodName.equals("getServer"))
return server; return server;
@ -104,70 +126,71 @@ public class TemporaryPlayerFactory {
// Handle send message methods // Handle send message methods
if (methodName.equals("chat") || methodName.equals("sendMessage")) { if (methodName.equals("chat") || methodName.equals("sendMessage")) {
try { try {
Object argument = args[0]; Object argument = args[0];
// Dynamic overloading // Dynamic overloading
if (argument instanceof String) { if (argument instanceof String) {
return sendMessage(injector, (String) argument); return sendMessage(injector, (String) argument);
} else if (argument instanceof String[]) { } else if (argument instanceof String[]) {
for (String message : (String[]) argument) { for (String message : (String[]) argument) {
sendMessage(injector, message); sendMessage(injector, message);
}
return null;
} }
return null;
}
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
throw e.getCause(); throw e.getCause();
} }
} }
// Also, handle kicking // Also, handle kicking
if (methodName.equals("kickPlayer")) { if (methodName.equals("kickPlayer")) {
injector.disconnect((String) args[0]); injector.disconnect((String) args[0]);
return null; return null;
} }
// The fallback instance // The fallback instance
Player updated = injector.getUpdatedPlayer(); Player updated = injector.getUpdatedPlayer();
if (updated != obj && updated != null) { if (updated != obj && updated != null) {
return proxy.invoke(updated, args); return method.invoke(updated, args);
} }
// Methods that are supported in the fallback instance // Methods that are supported in the fallback instance
if (methodName.equals("isOnline")) if (methodName.equals("isOnline"))
return injector.getSocket() != null && injector.getSocket().isConnected(); return injector.getSocket() != null && injector.getSocket().isConnected();
else if (methodName.equals("getName")) else if (methodName.equals("getName"))
return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]"; return "UNKNOWN[" + injector.getSocket().getRemoteSocketAddress() + "]";
// Ignore all other methods // Ignore all other methods
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"The method " + method.getName() + " is not supported for temporary players."); "The method " + method.getName() + " is not supported for temporary players.");
} }
}; });
// Shared callback filter final ElementMatcher.Junction<ByteCodeElement> callbackFilter = ElementMatchers.not(
if (callbackFilter == null) { ElementMatchers.isDeclaredBy(Object.class).or(ElementMatchers.isDeclaredBy(TemporaryPlayer.class)));
callbackFilter = new CallbackFilter() {
@Override try {
public int accept(Method method) { final Constructor<?> constructor = ByteBuddyFactory.getInstance()
// Do not override the object method or the superclass methods .createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
if (method.getDeclaringClass().equals(Object.class) || .name(TemporaryPlayerFactory.class.getPackage().getName() + ".TemporaryPlayerInvocationHandler")
method.getDeclaringClass().equals(TemporaryPlayer.class)) .implement(Player.class)
return 0;
else .defineField("server", Server.class, Visibility.PRIVATE)
return 1; .defineConstructor(Visibility.PUBLIC)
} .withParameters(Server.class)
}; .intercept(MethodCall.invoke(TemporaryPlayer.class.getDeclaredConstructor())
.andThen(FieldAccessor.ofField("server").setsArgumentAt(0)))
.method(callbackFilter)
.intercept(implementation)
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getDeclaredConstructor(Server.class);
return (Constructor<? extends Player>) constructor;
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find Temporary Player constructor!", e);
} }
// CGLib is amazing
Enhancer ex = new Enhancer();
ex.setSuperclass(TemporaryPlayer.class);
ex.setInterfaces(new Class[] { Player.class });
ex.setCallbacks(new Callback[] { NoOp.INSTANCE, implementation });
ex.setCallbackFilter(callbackFilter);
return (Player) ex.create();
} }
/** /**
@ -191,7 +214,7 @@ public class TemporaryPlayerFactory {
* @throws InvocationTargetException If the message couldn't be sent. * @throws InvocationTargetException If the message couldn't be sent.
* @throws FieldAccessException If we were unable to construct the message packet. * @throws FieldAccessException If we were unable to construct the message packet.
*/ */
private Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException { private static Object sendMessage(SocketInjector injector, String message) throws InvocationTargetException, FieldAccessException {
for (PacketContainer packet : ChatExtensions.createChatPackets(message)) { for (PacketContainer packet : ChatExtensions.createChatPackets(message)) {
injector.sendServerPacket(packet.getHandle(), null, false); injector.sendServerPacket(packet.getHandle(), null, false);
} }

View File

@ -6,8 +6,11 @@ import java.util.List;
import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod.AsmOpcodes; import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod.AsmOpcodes;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import net.bytebuddy.jar.asm.ClassReader;
import net.sf.cglib.asm.*; import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
public class ClassAnalyser { public class ClassAnalyser {
/** /**
@ -26,11 +29,11 @@ public class ClassAnalyser {
public static AsmOpcodes fromIntOpcode(int opcode) { public static AsmOpcodes fromIntOpcode(int opcode) {
switch (opcode) { switch (opcode) {
case $Opcodes.INVOKEVIRTUAL: return AsmOpcodes.INVOKE_VIRTUAL; case Opcodes.INVOKEVIRTUAL: return AsmOpcodes.INVOKE_VIRTUAL;
case $Opcodes.INVOKESPECIAL: return AsmOpcodes.INVOKE_SPECIAL; case Opcodes.INVOKESPECIAL: return AsmOpcodes.INVOKE_SPECIAL;
case $Opcodes.INVOKESTATIC: return AsmOpcodes.INVOKE_STATIC; case Opcodes.INVOKESTATIC: return AsmOpcodes.INVOKE_STATIC;
case $Opcodes.INVOKEINTERFACE: return AsmOpcodes.INVOKE_INTERFACE; case Opcodes.INVOKEINTERFACE: return AsmOpcodes.INVOKE_INTERFACE;
case $Opcodes.INVOKEDYNAMIC: return AsmOpcodes.INVOKE_DYNAMIC; case Opcodes.INVOKEDYNAMIC: return AsmOpcodes.INVOKE_DYNAMIC;
default: throw new IllegalArgumentException("Unknown opcode: " + opcode); default: throw new IllegalArgumentException("Unknown opcode: " + opcode);
} }
} }
@ -105,18 +108,18 @@ public class ClassAnalyser {
* @throws IOException Cannot access the parent class. * @throws IOException Cannot access the parent class.
*/ */
private List<AsmMethod> getMethodCalls(Class<?> clazz, Method method) throws IOException { private List<AsmMethod> getMethodCalls(Class<?> clazz, Method method) throws IOException {
final $ClassReader reader = new $ClassReader(clazz.getCanonicalName()); final ClassReader reader = new ClassReader(clazz.getCanonicalName());
final List<AsmMethod> output = Lists.newArrayList(); final List<AsmMethod> output = Lists.newArrayList();
// The method we are looking for // The method we are looking for
final String methodName = method.getName(); final String methodName = method.getName();
final String methodDescription = $Type.getMethodDescriptor(method); final String methodDescription = Type.getMethodDescriptor(method);
reader.accept(new $ClassVisitor($Opcodes.ASM5) { reader.accept(new ClassVisitor(Opcodes.ASM5) {
@Override @Override
public $MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (methodName.equals(name) && methodDescription.equals(desc)) { if (methodName.equals(name) && methodDescription.equals(desc)) {
return new $MethodVisitor($Opcodes.ASM5) { return new MethodVisitor(Opcodes.ASM5) {
@Override @Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean flag) { public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean flag) {
output.add(new AsmMethod(AsmOpcodes.fromIntOpcode(opcode), owner, methodName, desc)); output.add(new AsmMethod(AsmOpcodes.fromIntOpcode(opcode), owner, methodName, desc));
@ -126,7 +129,7 @@ public class ClassAnalyser {
return null; return null;
} }
}, $ClassReader.EXPAND_FRAMES); }, ClassReader.EXPAND_FRAMES);
return output; return output;
} }
} }

View File

@ -17,25 +17,25 @@
package com.comphenix.protocol.reflect.compiler; package com.comphenix.protocol.reflect.compiler;
import net.sf.cglib.asm.$MethodVisitor; import net.bytebuddy.jar.asm.MethodVisitor;
import net.sf.cglib.asm.$Opcodes; import net.bytebuddy.jar.asm.Opcodes;
import net.sf.cglib.asm.$Type; import net.bytebuddy.jar.asm.Type;
/** /**
* Used by the compiler to automatically box and unbox values. * Used by the compiler to automatically box and unbox values.
*/ */
class BoxingHelper { class BoxingHelper {
private final static $Type BYTE_$Type = $Type.getObjectType("java/lang/Byte"); private final static Type BYTE_Type = Type.getObjectType("java/lang/Byte");
private final static $Type BOOLEAN_$Type = $Type.getObjectType("java/lang/Boolean"); private final static Type BOOLEAN_Type = Type.getObjectType("java/lang/Boolean");
private final static $Type SHORT_$Type = $Type.getObjectType("java/lang/Short"); private final static Type SHORT_Type = Type.getObjectType("java/lang/Short");
private final static $Type CHARACTER_$Type = $Type.getObjectType("java/lang/Character"); private final static Type CHARACTER_Type = Type.getObjectType("java/lang/Character");
private final static $Type INTEGER_$Type = $Type.getObjectType("java/lang/Integer"); private final static Type INTEGER_Type = Type.getObjectType("java/lang/Integer");
private final static $Type FLOAT_$Type = $Type.getObjectType("java/lang/Float"); private final static Type FLOAT_Type = Type.getObjectType("java/lang/Float");
private final static $Type LONG_$Type = $Type.getObjectType("java/lang/Long"); private final static Type LONG_Type = Type.getObjectType("java/lang/Long");
private final static $Type DOUBLE_$Type = $Type.getObjectType("java/lang/Double"); private final static Type DOUBLE_Type = Type.getObjectType("java/lang/Double");
private final static $Type NUMBER_$Type = $Type.getObjectType("java/lang/Number"); private final static Type NUMBER_Type = Type.getObjectType("java/lang/Number");
private final static $Type OBJECT_$Type = $Type.getObjectType("java/lang/Object"); private final static Type OBJECT_Type = Type.getObjectType("java/lang/Object");
private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()"); private final static MethodDescriptor BOOLEAN_VALUE = MethodDescriptor.getMethod("boolean booleanValue()");
private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()"); private final static MethodDescriptor CHAR_VALUE = MethodDescriptor.getMethod("char charValue()");
@ -44,9 +44,9 @@ class BoxingHelper {
private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()"); private final static MethodDescriptor LONG_VALUE = MethodDescriptor.getMethod("long longValue()");
private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()"); private final static MethodDescriptor DOUBLE_VALUE = MethodDescriptor.getMethod("double doubleValue()");
private $MethodVisitor mv; private MethodVisitor mv;
public BoxingHelper($MethodVisitor mv) { public BoxingHelper(MethodVisitor mv) {
this.mv = mv; this.mv = mv;
} }
@ -54,42 +54,42 @@ class BoxingHelper {
* Generates the instructions to box the top stack value. This value is * Generates the instructions to box the top stack value. This value is
* replaced by its boxed equivalent on top of the stack. * replaced by its boxed equivalent on top of the stack.
* *
* @param type the $Type of the top stack value. * @param type the Type of the top stack value.
*/ */
public void box(final $Type type){ public void box(final Type type){
if(type.getSort() == $Type.OBJECT || type.getSort() == $Type.ARRAY) { if(type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return; return;
} }
if(type == $Type.VOID_TYPE) { if(type == Type.VOID_TYPE) {
push((String) null); push((String) null);
} else { } else {
$Type boxed = type; Type boxed = type;
switch(type.getSort()) { switch(type.getSort()) {
case $Type.BYTE: case Type.BYTE:
boxed = BYTE_$Type; boxed = BYTE_Type;
break; break;
case $Type.BOOLEAN: case Type.BOOLEAN:
boxed = BOOLEAN_$Type; boxed = BOOLEAN_Type;
break; break;
case $Type.SHORT: case Type.SHORT:
boxed = SHORT_$Type; boxed = SHORT_Type;
break; break;
case $Type.CHAR: case Type.CHAR:
boxed = CHARACTER_$Type; boxed = CHARACTER_Type;
break; break;
case $Type.INT: case Type.INT:
boxed = INTEGER_$Type; boxed = INTEGER_Type;
break; break;
case $Type.FLOAT: case Type.FLOAT:
boxed = FLOAT_$Type; boxed = FLOAT_Type;
break; break;
case $Type.LONG: case Type.LONG:
boxed = LONG_$Type; boxed = LONG_Type;
break; break;
case $Type.DOUBLE: case Type.DOUBLE:
boxed = DOUBLE_$Type; boxed = DOUBLE_Type;
break; break;
} }
@ -105,46 +105,46 @@ class BoxingHelper {
swap(); swap();
} }
invokeConstructor(boxed, new MethodDescriptor("<init>", $Type.VOID_TYPE, new $Type[] {type})); invokeConstructor(boxed, new MethodDescriptor("<init>", Type.VOID_TYPE, new Type[] {type}));
} }
} }
/** /**
* Generates the instruction to invoke a constructor. * Generates the instruction to invoke a constructor.
* *
* @param $Type the class in which the constructor is defined. * @param Type the class in which the constructor is defined.
* @param method the constructor to be invoked. * @param method the constructor to be invoked.
*/ */
public void invokeConstructor(final $Type $Type, final MethodDescriptor method){ public void invokeConstructor(final Type Type, final MethodDescriptor method){
invokeInsn($Opcodes.INVOKESPECIAL, $Type, method); invokeInsn(Opcodes.INVOKESPECIAL, Type, method);
} }
/** /**
* Generates a DUP_X1 instruction. * Generates a DUP_X1 instruction.
*/ */
public void dupX1(){ public void dupX1(){
mv.visitInsn($Opcodes.DUP_X1); mv.visitInsn(Opcodes.DUP_X1);
} }
/** /**
* Generates a DUP_X2 instruction. * Generates a DUP_X2 instruction.
*/ */
public void dupX2(){ public void dupX2(){
mv.visitInsn($Opcodes.DUP_X2); mv.visitInsn(Opcodes.DUP_X2);
} }
/** /**
* Generates a POP instruction. * Generates a POP instruction.
*/ */
public void pop(){ public void pop(){
mv.visitInsn($Opcodes.POP); mv.visitInsn(Opcodes.POP);
} }
/** /**
* Generates a SWAP instruction. * Generates a SWAP instruction.
*/ */
public void swap(){ public void swap(){
mv.visitInsn($Opcodes.SWAP); mv.visitInsn(Opcodes.SWAP);
} }
/** /**
@ -163,11 +163,11 @@ class BoxingHelper {
*/ */
public void push(final int value) { public void push(final int value) {
if (value >= -1 && value <= 5) { if (value >= -1 && value <= 5) {
mv.visitInsn($Opcodes.ICONST_0 + value); mv.visitInsn(Opcodes.ICONST_0 + value);
} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
mv.visitIntInsn($Opcodes.BIPUSH, value); mv.visitIntInsn(Opcodes.BIPUSH, value);
} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
mv.visitIntInsn($Opcodes.SIPUSH, value); mv.visitIntInsn(Opcodes.SIPUSH, value);
} else { } else {
mv.visitLdcInsn(new Integer(value)); mv.visitLdcInsn(new Integer(value));
} }
@ -176,10 +176,10 @@ class BoxingHelper {
/** /**
* Generates the instruction to create a new object. * Generates the instruction to create a new object.
* *
* @param $Type the class of the object to be created. * @param Type the class of the object to be created.
*/ */
public void newInstance(final $Type $Type){ public void newInstance(final Type Type){
$TypeInsn($Opcodes.NEW, $Type); TypeInsn(Opcodes.NEW, Type);
} }
/** /**
@ -189,7 +189,7 @@ class BoxingHelper {
*/ */
public void push(final String value) { public void push(final String value) {
if (value == null) { if (value == null) {
mv.visitInsn($Opcodes.ACONST_NULL); mv.visitInsn(Opcodes.ACONST_NULL);
} else { } else {
mv.visitLdcInsn(value); mv.visitLdcInsn(value);
} }
@ -200,35 +200,35 @@ class BoxingHelper {
* replaced by its unboxed equivalent on top of the stack. * replaced by its unboxed equivalent on top of the stack.
* *
* @param type * @param type
* the $Type of the top stack value. * the Type of the top stack value.
*/ */
public void unbox(final $Type type){ public void unbox(final Type type){
$Type t = NUMBER_$Type; Type t = NUMBER_Type;
MethodDescriptor sig = null; MethodDescriptor sig = null;
switch(type.getSort()) { switch(type.getSort()) {
case $Type.VOID: case Type.VOID:
return; return;
case $Type.CHAR: case Type.CHAR:
t = CHARACTER_$Type; t = CHARACTER_Type;
sig = CHAR_VALUE; sig = CHAR_VALUE;
break; break;
case $Type.BOOLEAN: case Type.BOOLEAN:
t = BOOLEAN_$Type; t = BOOLEAN_Type;
sig = BOOLEAN_VALUE; sig = BOOLEAN_VALUE;
break; break;
case $Type.DOUBLE: case Type.DOUBLE:
sig = DOUBLE_VALUE; sig = DOUBLE_VALUE;
break; break;
case $Type.FLOAT: case Type.FLOAT:
sig = FLOAT_VALUE; sig = FLOAT_VALUE;
break; break;
case $Type.LONG: case Type.LONG:
sig = LONG_VALUE; sig = LONG_VALUE;
break; break;
case $Type.INT: case Type.INT:
case $Type.SHORT: case Type.SHORT:
case $Type.BYTE: case Type.BYTE:
sig = INT_VALUE; sig = INT_VALUE;
} }
@ -242,13 +242,13 @@ class BoxingHelper {
/** /**
* Generates the instruction to check that the top stack value is of the * Generates the instruction to check that the top stack value is of the
* given $Type. * given Type.
* *
* @param $Type a class or interface $Type. * @param Type a class or interface Type.
*/ */
public void checkCast(final $Type $Type){ public void checkCast(final Type Type){
if(!$Type.equals(OBJECT_$Type)) { if(!Type.equals(OBJECT_Type)) {
$TypeInsn($Opcodes.CHECKCAST, $Type); TypeInsn(Opcodes.CHECKCAST, Type);
} }
} }
@ -258,35 +258,35 @@ class BoxingHelper {
* @param owner the class in which the method is defined. * @param owner the class in which the method is defined.
* @param method the method to be invoked. * @param method the method to be invoked.
*/ */
public void invokeVirtual(final $Type owner, final MethodDescriptor method){ public void invokeVirtual(final Type owner, final MethodDescriptor method){
invokeInsn($Opcodes.INVOKEVIRTUAL, owner, method); invokeInsn(Opcodes.INVOKEVIRTUAL, owner, method);
} }
/** /**
* Generates an invoke method instruction. * Generates an invoke method instruction.
* *
* @param opcode the instruction's opcode. * @param opcode the instruction's opcode.
* @param $Type the class in which the method is defined. * @param type the class in which the method is defined.
* @param method the method to be invoked. * @param method the method to be invoked.
*/ */
private void invokeInsn(final int opcode, final $Type $Type, final MethodDescriptor method){ private void invokeInsn(final int opcode, final Type type, final MethodDescriptor method){
String owner = $Type.getSort() == $Type.ARRAY ? $Type.getDescriptor() : $Type.getInternalName(); String owner = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor()); mv.visitMethodInsn(opcode, owner, method.getName(), method.getDescriptor());
} }
/** /**
* Generates a $Type dependent instruction. * Generates a Type dependent instruction.
* *
* @param opcode the instruction's opcode. * @param opcode the instruction's opcode.
* @param $Type the instruction's operand. * @param type the instruction's operand.
*/ */
private void $TypeInsn(final int opcode, final $Type $Type){ private void TypeInsn(final int opcode, final Type type){
String desc; String desc;
if($Type.getSort() == $Type.ARRAY) { if(type.getSort() == Type.ARRAY) {
desc = $Type.getDescriptor(); desc = type.getDescriptor();
} else { } else {
desc = $Type.getInternalName(); desc = type.getInternalName();
} }
mv.visitTypeInsn(opcode, desc); mv.visitTypeInsn(opcode, desc);

View File

@ -20,7 +20,7 @@ package com.comphenix.protocol.reflect.compiler;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import net.sf.cglib.asm.$Type; import net.bytebuddy.jar.asm.Type;
/** /**
* Represents a method. * Represents a method.
@ -75,10 +75,10 @@ class MethodDescriptor {
*/ */
public MethodDescriptor( public MethodDescriptor(
final String name, final String name,
final $Type returnType, final Type returnType,
final $Type[] argumentTypes) final Type[] argumentTypes)
{ {
this(name, $Type.getMethodDescriptor(returnType, argumentTypes)); this(name, Type.getMethodDescriptor(returnType, argumentTypes));
} }
/** /**
@ -206,8 +206,8 @@ class MethodDescriptor {
* *
* @return the return type of the method described by this object. * @return the return type of the method described by this object.
*/ */
public $Type getReturnType() { public Type getReturnType() {
return $Type.getReturnType(desc); return Type.getReturnType(desc);
} }
/** /**
@ -215,8 +215,8 @@ class MethodDescriptor {
* *
* @return the argument types of the method described by this object. * @return the argument types of the method described by this object.
*/ */
public $Type[] getArgumentTypes() { public Type[] getArgumentTypes() {
return $Type.getArgumentTypes(desc); return Type.getArgumentTypes(desc);
} }
public String toString() { public String toString() {

View File

@ -25,19 +25,13 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import net.sf.cglib.asm.$ClassWriter;
import net.sf.cglib.asm.$FieldVisitor;
import net.sf.cglib.asm.$Label;
import net.sf.cglib.asm.$MethodVisitor;
import net.sf.cglib.asm.$Opcodes;
import net.sf.cglib.asm.$Type;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.StructureModifier;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.primitives.Primitives; import com.google.common.primitives.Primitives;
import net.bytebuddy.jar.asm.*;
// public class CompiledStructureModifierPacket20<TField> extends CompiledStructureModifier<TField> { // public class CompiledStructureModifierPacket20<TField> extends CompiledStructureModifier<TField> {
// //
@ -276,15 +270,15 @@ public final class StructureCompiler {
*/ */
private <TField> Class<?> generateClass(StructureModifier<TField> source) { private <TField> Class<?> generateClass(StructureModifier<TField> source) {
$ClassWriter cw = new $ClassWriter(0); ClassWriter cw = new ClassWriter(0);
Class<?> targetType = source.getTargetType(); Class<?> targetType = source.getTargetType();
String className = getCompiledName(source); String className = getCompiledName(source);
String targetSignature = $Type.getDescriptor(targetType); String targetSignature = Type.getDescriptor(targetType);
String targetName = targetType.getName().replace('.', '/'); String targetName = targetType.getName().replace('.', '/');
// Define class // Define class
cw.visit($Opcodes.V1_6, $Opcodes.ACC_PUBLIC + $Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className, cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, PACKAGE_NAME + "/" + className,
null, COMPILED_CLASS, null); null, COMPILED_CLASS, null);
createFields(cw, targetSignature); createFields(cw, targetSignature);
@ -352,35 +346,35 @@ public final class StructureCompiler {
return !Modifier.isFinal(field.getModifiers()); return !Modifier.isFinal(field.getModifiers());
} }
private void createFields($ClassWriter cw, String targetSignature) { private void createFields(ClassWriter cw, String targetSignature) {
$FieldVisitor typedField = cw.visitField($Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null); FieldVisitor typedField = cw.visitField(Opcodes.ACC_PRIVATE, "typedTarget", targetSignature, null, null);
typedField.visitEnd(); typedField.visitEnd();
} }
private void createWriteMethod($ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) { private void createWriteMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";"; String methodDescriptor = "(ILjava/lang/Object;)L" + SUPER_CLASS + ";";
String methodSignature = "(ILjava/lang/Object;)L" + SUPER_CLASS + "<Ljava/lang/Object;>;"; String methodSignature = "(ILjava/lang/Object;)L" + SUPER_CLASS + "<Ljava/lang/Object;>;";
$MethodVisitor mv = cw.visitMethod($Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature, MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "writeGenerated", methodDescriptor, methodSignature,
new String[] { FIELD_EXCEPTION_CLASS }); new String[] { FIELD_EXCEPTION_CLASS });
BoxingHelper boxingHelper = new BoxingHelper(mv); BoxingHelper boxingHelper = new BoxingHelper(mv);
String generatedClassName = PACKAGE_NAME + "/" + className; String generatedClassName = PACKAGE_NAME + "/" + className;
mv.visitCode(); mv.visitCode();
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn($Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature); mv.visitFieldInsn(Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature);
mv.visitVarInsn($Opcodes.ASTORE, 3); mv.visitVarInsn(Opcodes.ASTORE, 3);
mv.visitVarInsn($Opcodes.ILOAD, 1); mv.visitVarInsn(Opcodes.ILOAD, 1);
// The last $Label is for the default switch // The last $Label is for the default switch
$Label[] $Labels = new $Label[fields.size()]; Label[] $Labels = new Label[fields.size()];
$Label error$Label = new $Label(); Label error$Label = new Label();
$Label return$Label = new $Label(); Label return$Label = new Label();
// Generate $Labels // Generate $Labels
for (int i = 0; i < fields.size(); i++) { for (int i = 0; i < fields.size(); i++) {
$Labels[i] = new $Label(); $Labels[i] = new Label();
} }
mv.visitTableSwitchInsn(0, $Labels.length - 1, error$Label, $Labels); mv.visitTableSwitchInsn(0, $Labels.length - 1, error$Label, $Labels);
@ -390,82 +384,82 @@ public final class StructureCompiler {
Field field = fields.get(i); Field field = fields.get(i);
Class<?> outputType = field.getType(); Class<?> outputType = field.getType();
Class<?> inputType = Primitives.wrap(outputType); Class<?> inputType = Primitives.wrap(outputType);
String typeDescriptor = $Type.getDescriptor(outputType); String typeDescriptor = Type.getDescriptor(outputType);
String inputPath = inputType.getName().replace('.', '/'); String inputPath = inputType.getName().replace('.', '/');
mv.visitLabel($Labels[i]); mv.visitLabel($Labels[i]);
// Push the compare object // Push the compare object
if (i == 0) if (i == 0)
mv.visitFrame($Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null); mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
else else
mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// Only write to public non-final fields // Only write to public non-final fields
if (isPublic(field) && isNonFinal(field)) { if (isPublic(field) && isNonFinal(field)) {
mv.visitVarInsn($Opcodes.ALOAD, 3); mv.visitVarInsn(Opcodes.ALOAD, 3);
mv.visitVarInsn($Opcodes.ALOAD, 2); mv.visitVarInsn(Opcodes.ALOAD, 2);
if (!outputType.isPrimitive()) if (!outputType.isPrimitive())
mv.visitTypeInsn($Opcodes.CHECKCAST, inputPath); mv.visitTypeInsn(Opcodes.CHECKCAST, inputPath);
else else
boxingHelper.unbox($Type.getType(outputType)); boxingHelper.unbox(Type.getType(outputType));
mv.visitFieldInsn($Opcodes.PUTFIELD, targetName, field.getName(), typeDescriptor); mv.visitFieldInsn(Opcodes.PUTFIELD, targetName, field.getName(), typeDescriptor);
} else { } else {
// Use reflection. We don't have a choice, unfortunately. // Use reflection. We don't have a choice, unfortunately.
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ILOAD, 1); mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitVarInsn($Opcodes.ALOAD, 2); mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, generatedClassName, "writeReflected", "(ILjava/lang/Object;)V"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedClassName, "writeReflected", "(ILjava/lang/Object;)V");
} }
mv.visitJumpInsn($Opcodes.GOTO, return$Label); mv.visitJumpInsn(Opcodes.GOTO, return$Label);
} }
mv.visitLabel(error$Label); mv.visitLabel(error$Label);
mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitTypeInsn($Opcodes.NEW, FIELD_EXCEPTION_CLASS); mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
mv.visitInsn($Opcodes.DUP); mv.visitInsn(Opcodes.DUP);
mv.visitTypeInsn($Opcodes.NEW, "java/lang/StringBuilder"); mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn($Opcodes.DUP); mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn("Invalid index "); mv.visitLdcInsn("Invalid index ");
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V"); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn($Opcodes.ILOAD, 1); mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V"); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn($Opcodes.ATHROW); mv.visitInsn(Opcodes.ATHROW);
mv.visitLabel(return$Label); mv.visitLabel(return$Label);
mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn($Opcodes.ARETURN); mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(5, 4); mv.visitMaxs(5, 4);
mv.visitEnd(); mv.visitEnd();
} }
private void createReadMethod($ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) { private void createReadMethod(ClassWriter cw, String className, List<Field> fields, String targetSignature, String targetName) {
$MethodVisitor mv = cw.visitMethod($Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null, MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PROTECTED, "readGenerated", "(I)Ljava/lang/Object;", null,
new String[] { "com/comphenix/protocol/reflect/FieldAccessException" }); new String[] { "com/comphenix/protocol/reflect/FieldAccessException" });
BoxingHelper boxingHelper = new BoxingHelper(mv); BoxingHelper boxingHelper = new BoxingHelper(mv);
String generatedClassName = PACKAGE_NAME + "/" + className; String generatedClassName = PACKAGE_NAME + "/" + className;
mv.visitCode(); mv.visitCode();
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn($Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature); mv.visitFieldInsn(Opcodes.GETFIELD, generatedClassName, "typedTarget", targetSignature);
mv.visitVarInsn($Opcodes.ASTORE, 2); mv.visitVarInsn(Opcodes.ASTORE, 2);
mv.visitVarInsn($Opcodes.ILOAD, 1); mv.visitVarInsn(Opcodes.ILOAD, 1);
// The last $Label is for the default switch // The last $Label is for the default switch
$Label[] $Labels = new $Label[fields.size()]; Label[] $Labels = new Label[fields.size()];
$Label error$Label = new $Label(); Label error$Label = new Label();
// Generate $Labels // Generate $Labels
for (int i = 0; i < fields.size(); i++) { for (int i = 0; i < fields.size(); i++) {
$Labels[i] = new $Label(); $Labels[i] = new Label();
} }
mv.visitTableSwitchInsn(0, fields.size() - 1, error$Label, $Labels); mv.visitTableSwitchInsn(0, fields.size() - 1, error$Label, $Labels);
@ -474,74 +468,74 @@ public final class StructureCompiler {
Field field = fields.get(i); Field field = fields.get(i);
Class<?> outputType = field.getType(); Class<?> outputType = field.getType();
String typeDescriptor = $Type.getDescriptor(outputType); String typeDescriptor = Type.getDescriptor(outputType);
mv.visitLabel($Labels[i]); mv.visitLabel($Labels[i]);
// Push the compare object // Push the compare object
if (i == 0) if (i == 0)
mv.visitFrame($Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null); mv.visitFrame(Opcodes.F_APPEND, 1, new Object[] { targetName }, 0, null);
else else
mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
// Note that byte code cannot access non-public fields // Note that byte code cannot access non-public fields
if (isPublic(field)) { if (isPublic(field)) {
mv.visitVarInsn($Opcodes.ALOAD, 2); mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitFieldInsn($Opcodes.GETFIELD, targetName, field.getName(), typeDescriptor); mv.visitFieldInsn(Opcodes.GETFIELD, targetName, field.getName(), typeDescriptor);
boxingHelper.box($Type.getType(outputType)); boxingHelper.box(Type.getType(outputType));
} else { } else {
// We have to use reflection for private and protected fields. // We have to use reflection for private and protected fields.
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ILOAD, 1); mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, generatedClassName, "readReflected", "(I)Ljava/lang/Object;"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedClassName, "readReflected", "(I)Ljava/lang/Object;");
} }
mv.visitInsn($Opcodes.ARETURN); mv.visitInsn(Opcodes.ARETURN);
} }
mv.visitLabel(error$Label); mv.visitLabel(error$Label);
mv.visitFrame($Opcodes.F_SAME, 0, null, 0, null); mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitTypeInsn($Opcodes.NEW, FIELD_EXCEPTION_CLASS); mv.visitTypeInsn(Opcodes.NEW, FIELD_EXCEPTION_CLASS);
mv.visitInsn($Opcodes.DUP); mv.visitInsn(Opcodes.DUP);
mv.visitTypeInsn($Opcodes.NEW, "java/lang/StringBuilder"); mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
mv.visitInsn($Opcodes.DUP); mv.visitInsn(Opcodes.DUP);
mv.visitLdcInsn("Invalid index "); mv.visitLdcInsn("Invalid index ");
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V"); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn($Opcodes.ILOAD, 1); mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;");
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V"); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, FIELD_EXCEPTION_CLASS, "<init>", "(Ljava/lang/String;)V");
mv.visitInsn($Opcodes.ATHROW); mv.visitInsn(Opcodes.ATHROW);
mv.visitMaxs(5, 3); mv.visitMaxs(5, 3);
mv.visitEnd(); mv.visitEnd();
} }
private void createConstructor($ClassWriter cw, String className, String targetSignature, String targetName) { private void createConstructor(ClassWriter cw, String className, String targetSignature, String targetName) {
$MethodVisitor mv = cw.visitMethod($Opcodes.ACC_PUBLIC, "<init>", MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V", "(L" + SUPER_CLASS + ";L" + PACKAGE_NAME + "/StructureCompiler;)V",
"(L" + SUPER_CLASS + "<Ljava/lang/Object;>;L" + PACKAGE_NAME + "/StructureCompiler;)V", null); "(L" + SUPER_CLASS + "<Ljava/lang/Object;>;L" + PACKAGE_NAME + "/StructureCompiler;)V", null);
String fullClassName = PACKAGE_NAME + "/" + className; String fullClassName = PACKAGE_NAME + "/" + className;
mv.visitCode(); mv.visitCode();
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn($Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V"); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, COMPILED_CLASS, "<init>", "()V");
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ALOAD, 1); mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, fullClassName, "initialize", "(L" + SUPER_CLASS + ";)V"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, fullClassName, "initialize", "(L" + SUPER_CLASS + ";)V");
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ALOAD, 1); mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitMethodInsn($Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, SUPER_CLASS, "getTarget", "()Ljava/lang/Object;");
mv.visitFieldInsn($Opcodes.PUTFIELD, fullClassName, "target", "Ljava/lang/Object;"); mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "target", "Ljava/lang/Object;");
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn($Opcodes.GETFIELD, fullClassName, "target", "Ljava/lang/Object;"); mv.visitFieldInsn(Opcodes.GETFIELD, fullClassName, "target", "Ljava/lang/Object;");
mv.visitTypeInsn($Opcodes.CHECKCAST, targetName); mv.visitTypeInsn(Opcodes.CHECKCAST, targetName);
mv.visitFieldInsn($Opcodes.PUTFIELD, fullClassName, "typedTarget", targetSignature); mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "typedTarget", targetSignature);
mv.visitVarInsn($Opcodes.ALOAD, 0); mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn($Opcodes.ALOAD, 2); mv.visitVarInsn(Opcodes.ALOAD, 2);
mv.visitFieldInsn($Opcodes.PUTFIELD, fullClassName, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;"); mv.visitFieldInsn(Opcodes.PUTFIELD, fullClassName, "compiler", "L" + PACKAGE_NAME + "/StructureCompiler;");
mv.visitInsn($Opcodes.RETURN); mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 3); mv.visitMaxs(2, 3);
mv.visitEnd(); mv.visitEnd();
} }

View File

@ -25,8 +25,6 @@ import java.util.logging.Level;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import net.sf.cglib.proxy.Enhancer;
import com.comphenix.protocol.ProtocolLogger; import com.comphenix.protocol.ProtocolLogger;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -304,24 +302,6 @@ public class DefaultInstances implements InstanceProvider {
return null; return null;
} }
/**
* Construct default instances using the CGLIB enhancer object instead.
* @param enhancer - a CGLIB enhancer to use.
* @return A default instance generator that uses the CGLIB enhancer.
*/
public DefaultInstances forEnhancer(Enhancer enhancer) {
final Enhancer ex = enhancer;
return new DefaultInstances(this) {
@SuppressWarnings("unchecked")
@Override
protected <T> T createInstance(Class<T> type, Constructor<T> constructor, Class<?>[] types, Object[] params) {
// Use the enhancer instead
return (T) ex.create(types, params);
}
};
}
/** /**
* Used by the default instance provider to create a class from a given constructor. * Used by the default instance provider to create a class from a given constructor.
* The default method uses reflection. * The default method uses reflection.

View File

@ -0,0 +1,65 @@
package com.comphenix.protocol.utility;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
/**
* Represents a shared ByteBuddy factory.
* @author Kristian
*/
public class ByteBuddyFactory {
private static ByteBuddyFactory INSTANCE = new ByteBuddyFactory();
// The current class loader
private ClassLoader loader = ByteBuddyFactory.class.getClassLoader();
public static ByteBuddyFactory getInstance() {
return INSTANCE;
}
/**
* Set the current class loader to use when constructing enhancers.
* @param loader - the class loader
*/
public void setClassLoader(ClassLoader loader) {
this.loader = loader;
}
/**
* Get the current class loader we are using.
* @return The current class loader.
*/
public ClassLoader getClassLoader() {
return loader;
}
/**
* Creates a type builder for a subclass of a given {@link Class}.
* @param clz The class for which to create a subclass.
* @return A type builder for creating a new class extending the provided clz and implementing
* {@link ByteBuddyGenerated}.
*/
public <T> DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional<T> createSubclass(Class<T> clz)
{
return new ByteBuddy()
.subclass(clz)
.implement(ByteBuddyGenerated.class);
}
/**
* Creates a type builder for a subclass of a given {@link Class}.
* @param clz The class for which to create a subclass.
* @param constructorStrategy The constructor strategy to use.
* @return A type builder for creating a new class extending the provided clz and implementing
* {@link ByteBuddyGenerated}.
*/
public <T> DynamicType.Builder.MethodDefinition.ImplementationDefinition.Optional<T> createSubclass(Class<T> clz,
ConstructorStrategy.Default constructorStrategy)
{
return new ByteBuddy()
.subclass(clz, constructorStrategy)
.implement(ByteBuddyGenerated.class);
}
}

View File

@ -0,0 +1,9 @@
package com.comphenix.protocol.utility;
/**
* Represents an object that has been generated using ByteBuddy.
*
* @author Pim
*/
public interface ByteBuddyGenerated {
}

View File

@ -1,44 +0,0 @@
package com.comphenix.protocol.utility;
import net.sf.cglib.proxy.Enhancer;
/**
* Represents a shared enchancer factory.
* @author Kristian
*/
public class EnhancerFactory {
private static EnhancerFactory INSTANCE = new EnhancerFactory();
// The current class loader
private ClassLoader loader = EnhancerFactory.class.getClassLoader();
public static EnhancerFactory getInstance() {
return INSTANCE;
}
/**
* Create a new CGLib enhancer.
* @return The new enhancer.
*/
public Enhancer createEnhancer() {
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(loader);
return enhancer;
}
/**
* Set the current class loader to use when constructing enhancers.
* @param loader - the class loader
*/
public void setClassLoader(ClassLoader loader) {
this.loader = loader;
}
/**
* Get the current class loader we are using.
* @return The current class loader.
*/
public ClassLoader getClassLoader() {
return loader;
}
}

View File

@ -1,10 +1,12 @@
package com.comphenix.protocol.utility; package com.comphenix.protocol.utility;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Callable;
import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketContainer;
@ -14,8 +16,13 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import net.sf.cglib.proxy.Enhancer; import net.bytebuddy.dynamic.DynamicType;
import net.sf.cglib.proxy.MethodInterceptor; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.matcher.ElementMatchers;
/** /**
* Static methods for accessing Minecraft methods. * Static methods for accessing Minecraft methods.
@ -33,6 +40,8 @@ public class MinecraftMethods {
// For packet // For packet
private volatile static Method packetReadByteBuf; private volatile static Method packetReadByteBuf;
private volatile static Method packetWriteByteBuf; private volatile static Method packetWriteByteBuf;
private static Constructor<?> proxyConstructor;
/** /**
* Retrieve the send packet method in PlayerConnection/NetServerHandler. * Retrieve the send packet method in PlayerConnection/NetServerHandler.
@ -166,36 +175,59 @@ public class MinecraftMethods {
initializePacket(); initializePacket();
return packetWriteByteBuf; return packetWriteByteBuf;
} }
private static Constructor<?> setupProxyConstructor()
{
try {
return ByteBuddyFactory.getInstance()
.createSubclass(MinecraftReflection.getPacketDataSerializerClass())
.name(MinecraftMethods.class.getPackage().getName() + ".PacketDecorator")
.method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class)))
.intercept(MethodDelegation.to(new Object() {
@RuntimeType
public Object delegate(@SuperCall Callable<?> zuper, @Origin Method method) throws Exception {
if (method.getName().contains("read")) {
throw new ReadMethodException();
}
if (method.getName().contains("write")) {
throw new WriteMethodException();
}
return zuper.call();
}
}))
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getDeclaredConstructor(MinecraftReflection.getByteBufClass());
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find constructor!", e);
}
}
/** /**
* Initialize the two read() and write() methods. * Initialize the two read() and write() methods.
*/ */
private static void initializePacket() { private static void initializePacket() {
// Initialize the methods // Initialize the methods
if (packetReadByteBuf == null || packetWriteByteBuf == null) { if (packetReadByteBuf == null || packetWriteByteBuf == null) {
// This object will allow us to detect which methods were called if (proxyConstructor == null)
Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer(); proxyConstructor = setupProxyConstructor();
enhancer.setSuperclass(MinecraftReflection.getPacketDataSerializerClass());
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
if (method.getName().contains("read")) {
throw new ReadMethodException();
}
if (method.getName().contains("write")) { final Object javaProxy;
throw new WriteMethodException(); try {
} javaProxy = proxyConstructor.newInstance(Unpooled.buffer());
} catch (IllegalAccessException e) {
throw new RuntimeException("Cannot access reflection.", e);
} catch (InstantiationException e) {
throw new RuntimeException("Cannot instantiate object.", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error in invocation.", e);
}
return proxy.invokeSuper(obj, args); final Object lookPacket = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW).getHandle();
}); final List<Method> candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass())
// Create our proxy object
Object javaProxy = enhancer.create(
new Class<?>[] { MinecraftReflection.getByteBufClass() },
new Object[] { Unpooled.buffer() }
);
Object lookPacket = new PacketContainer(PacketType.Play.Client.CLOSE_WINDOW).getHandle();
List<Method> candidates = FuzzyReflection.fromClass(MinecraftReflection.getPacketClass())
.getMethodListByParameters(Void.TYPE, new Class<?>[] { MinecraftReflection.getPacketDataSerializerClass() }); .getMethodListByParameters(Void.TYPE, new Class<?>[] { MinecraftReflection.getPacketDataSerializerClass() });
// Look through all the methods // Look through all the methods

View File

@ -1,6 +1,8 @@
package com.comphenix.protocol.wrappers.nbt; package com.comphenix.protocol.wrappers.nbt;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -11,18 +13,20 @@ import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.EnhancerFactory; import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import net.sf.cglib.asm.$ClassReader; import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.sf.cglib.asm.$ClassVisitor; import net.bytebuddy.implementation.InvocationHandlerAdapter;
import net.sf.cglib.asm.$MethodVisitor; import net.bytebuddy.jar.asm.ClassReader;
import net.sf.cglib.asm.$Opcodes; import net.bytebuddy.jar.asm.ClassVisitor;
import net.sf.cglib.proxy.Enhancer; import net.bytebuddy.jar.asm.MethodVisitor;
import net.sf.cglib.proxy.MethodInterceptor; import net.bytebuddy.jar.asm.Opcodes;
import net.sf.cglib.proxy.MethodProxy; import net.bytebuddy.matcher.ElementMatchers;
import org.bukkit.block.BlockState; import org.bukkit.block.BlockState;
@ -43,14 +47,12 @@ class TileEntityAccessor<T extends BlockState> {
*/ */
private static final ConcurrentMap<Class<?>, TileEntityAccessor<?>> cachedAccessors = Maps.newConcurrentMap(); private static final ConcurrentMap<Class<?>, TileEntityAccessor<?>> cachedAccessors = Maps.newConcurrentMap();
private static Constructor<?> nbtCompoundParserConstructor;
private FieldAccessor tileEntityField; private FieldAccessor tileEntityField;
private MethodAccessor readCompound; private MethodAccessor readCompound;
private MethodAccessor writeCompound; private MethodAccessor writeCompound;
// For CGLib detection
private boolean writeDetected;
private boolean readDetected;
TileEntityAccessor() { TileEntityAccessor() {
// Do nothing // Do nothing
} }
@ -97,7 +99,7 @@ class TileEntityAccessor<T extends BlockState> {
} catch (IOException ex1) { } catch (IOException ex1) {
try { try {
// Much slower though // Much slower though
findMethodUsingCGLib(state); findMethodUsingByteBuddy(state);
} catch (Exception ex2) { } catch (Exception ex2) {
throw new RuntimeException("Cannot find read/write methods in " + type, ex2); throw new RuntimeException("Cannot find read/write methods in " + type, ex2);
} }
@ -117,26 +119,26 @@ class TileEntityAccessor<T extends BlockState> {
private void findMethodsUsingASM() throws IOException { private void findMethodsUsingASM() throws IOException {
final Class<?> nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); final Class<?> nbtCompoundClass = MinecraftReflection.getNBTCompoundClass();
final Class<?> tileEntityClass = MinecraftReflection.getTileEntityClass(); final Class<?> tileEntityClass = MinecraftReflection.getTileEntityClass();
final $ClassReader reader = new $ClassReader(tileEntityClass.getCanonicalName()); final ClassReader reader = new ClassReader(tileEntityClass.getCanonicalName());
final String tagCompoundName = getJarName(MinecraftReflection.getNBTCompoundClass()); final String tagCompoundName = getJarName(MinecraftReflection.getNBTCompoundClass());
final String expectedDesc = "(L" + tagCompoundName + ";)"; final String expectedDesc = "(L" + tagCompoundName + ";)";
reader.accept(new $ClassVisitor($Opcodes.ASM5) { reader.accept(new ClassVisitor(Opcodes.ASM5) {
@Override @Override
public $MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
final String methodName = name; final String methodName = name;
// Detect read/write calls to NBTTagCompound // Detect read/write calls to NBTTagCompound
if (desc.startsWith(expectedDesc)) { if (desc.startsWith(expectedDesc)) {
return new $MethodVisitor($Opcodes.ASM5) { return new MethodVisitor(Opcodes.ASM5) {
private int readMethods; private int readMethods;
private int writeMethods; private int writeMethods;
@Override @Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean intf) { public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean intf) {
// This must be a virtual call on NBTTagCompound that accepts a String // This must be a virtual call on NBTTagCompound that accepts a String
if (opcode == $Opcodes.INVOKEVIRTUAL if (opcode == Opcodes.INVOKEVIRTUAL
&& tagCompoundName.equals(owner) && tagCompoundName.equals(owner)
&& desc.startsWith("(Ljava/lang/String")) { && desc.startsWith("(Ljava/lang/String")) {
@ -167,47 +169,60 @@ class TileEntityAccessor<T extends BlockState> {
}, 0); }, 0);
} }
private static Constructor<?> setupNBTCompoundParserConstructor()
{
final Class<?> nbtCompoundClass = MinecraftReflection.getNBTCompoundClass();
try {
return ByteBuddyFactory.getInstance()
.createSubclass(nbtCompoundClass)
.name(MinecraftMethods.class.getPackage().getName() + ".NBTInvocationHandler")
.method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class)))
.intercept(InvocationHandlerAdapter.of((obj, method, args) -> {
// If true, we've found a write method. Otherwise, a read method.
if (method.getReturnType().equals(Void.TYPE))
throw new WriteMethodException();
throw new ReadMethodException();
}))
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find NBTCompound constructor.");
}
}
/** /**
* Find the read/write methods in TileEntity. * Find the read/write methods in TileEntity.
* @param blockState - the block state. * @param blockState - the block state.
* @throws IOException If we cannot find these methods. * @throws IOException If we cannot find these methods.
*/ */
private void findMethodUsingCGLib(T blockState) throws IOException { private void findMethodUsingByteBuddy(T blockState) throws IllegalAccessException, InvocationTargetException,
InstantiationException {
if (nbtCompoundParserConstructor == null)
nbtCompoundParserConstructor = setupNBTCompoundParserConstructor();
final Class<?> nbtCompoundClass = MinecraftReflection.getNBTCompoundClass(); final Class<?> nbtCompoundClass = MinecraftReflection.getNBTCompoundClass();
// This is a much slower method, but it is necessary in MCPC final Object compound = nbtCompoundParserConstructor.newInstance();
Enhancer enhancer = EnhancerFactory.getInstance().createEnhancer(); final Object tileEntity = tileEntityField.get(blockState);
enhancer.setSuperclass(nbtCompoundClass);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (method.getReturnType().equals(Void.TYPE)) {
// Write method
writeDetected = true;
} else {
// Read method
readDetected = true;
}
throw new RuntimeException("Stop execution.");
}
});
Object compound = enhancer.create();
Object tileEntity = tileEntityField.get(blockState);
// Look in every read/write like method // Look in every read/write like method
for (Method method : FuzzyReflection.fromObject(tileEntity, true). for (Method method : FuzzyReflection.fromObject(tileEntity, true).
getMethodListByParameters(Void.TYPE, new Class<?>[] { nbtCompoundClass })) { getMethodListByParameters(Void.TYPE, new Class<?>[] { nbtCompoundClass })) {
try { try {
readDetected = false;
writeDetected = false;
method.invoke(tileEntity, compound); method.invoke(tileEntity, compound);
} catch (Exception e) { } catch (InvocationTargetException e) {
// Okay - see if we detected a write or read if (e.getCause() instanceof ReadMethodException) {
if (readDetected)
readCompound = Accessors.getMethodAccessor(method, true); readCompound = Accessors.getMethodAccessor(method, true);
if (writeDetected) } else if (e.getCause() instanceof WriteMethodException) {
writeCompound = Accessors.getMethodAccessor(method, true); writeCompound = Accessors.getMethodAccessor(method, true);
} else {
// throw new RuntimeException("Inner exception.", e);
}
} catch (Exception e) {
throw new RuntimeException("Generic reflection error.", e);
} }
} }
} }
@ -284,4 +299,28 @@ class TileEntityAccessor<T extends BlockState> {
} }
return (TileEntityAccessor<T>) (accessor != EMPTY_ACCESSOR ? accessor : null); return (TileEntityAccessor<T>) (accessor != EMPTY_ACCESSOR ? accessor : null);
} }
/**
* An internal exception used to detect read methods.
* @author Kristian
*/
private static class ReadMethodException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ReadMethodException() {
super("A read method was executed.");
}
}
/**
* An internal exception used to detect write methods.
* @author Kristian
*/
private static class WriteMethodException extends RuntimeException {
private static final long serialVersionUID = 1L;
public WriteMethodException() {
super("A write method was executed.");
}
}
} }

View File

@ -0,0 +1,72 @@
package com.comphenix.protocol.events;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.UUID;
import static org.junit.Assert.*;
import static org.mockito.Mockito.when;
public class SerializedOfflinePlayerTest {
@Mock
static OfflinePlayer offlinePlayer;
private static final String name = "playerName";
private static UUID uuid;
private static final long firstPlayed = 1000L;
private static final long lastPlayed = firstPlayed + 100L;
private static final boolean isOp = false;
private static final boolean playedBefore = true;
private static final boolean whitelisted = true;
private static SerializedOfflinePlayer serializedOfflinePlayer;
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
uuid = UUID.randomUUID();
when(offlinePlayer.getName()).thenReturn(name);
when(offlinePlayer.getUniqueId()).thenReturn(uuid);
when(offlinePlayer.getFirstPlayed()).thenReturn(firstPlayed);
when(offlinePlayer.getLastPlayed()).thenReturn(lastPlayed);
when(offlinePlayer.isOp()).thenReturn(isOp);
when(offlinePlayer.hasPlayedBefore()).thenReturn(playedBefore);
when(offlinePlayer.isWhitelisted()).thenReturn(whitelisted);
serializedOfflinePlayer = new SerializedOfflinePlayer(offlinePlayer);
}
@Test
public void getProxyPlayer() {
Player player = serializedOfflinePlayer.getProxyPlayer();
Assert.assertNotNull(player);
// getDisplayName only works for online players.
assertThrows(UnsupportedOperationException.class, player::getDisplayName);
assertEquals(uuid, player.getUniqueId());
assertEquals(name, player.getName());
assertEquals(firstPlayed, player.getFirstPlayed());
assertEquals(lastPlayed, player.getLastPlayed());
assertEquals(isOp, player.isOp());
assertEquals(playedBefore, player.hasPlayedBefore());
assertEquals(whitelisted, player.isWhitelisted());
}
@Test
public void getSecondProxyPlayer() {
// Make sure that the proxyPlayer generation doesn't work only once.
Player player = serializedOfflinePlayer.getProxyPlayer();
Assert.assertNotNull(player);
assertEquals(uuid, player.getUniqueId());
}
}

View File

@ -0,0 +1,42 @@
package com.comphenix.protocol.injector.server;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.junit.Assert.*;
public class TemporaryPlayerFactoryTest {
private static final TemporaryPlayerFactory temporaryPlayerFactory = new TemporaryPlayerFactory();
@Mock
Server server;
@Mock
SocketInjector socketInjector;
@Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testUnavailableSocketInjector()
{
Player player = temporaryPlayerFactory.createTemporaryPlayer(server);
assertThrows(IllegalStateException.class, player::getPlayer);
}
@Test
public void createTemporaryPlayer() {
Player player = temporaryPlayerFactory.createTemporaryPlayer(server, socketInjector);
assertEquals(server, player.getServer());
// May seem dumb, but this makes sure that the .equals method is still instact.
assertEquals(player, player);
}
}

View File

@ -7,6 +7,8 @@ import org.junit.Test;
import com.comphenix.protocol.BukkitInitialization; import com.comphenix.protocol.BukkitInitialization;
import java.lang.reflect.Field;
public class MinecraftMethodsTest { public class MinecraftMethodsTest {
@BeforeClass @BeforeClass
@ -19,4 +21,19 @@ public class MinecraftMethodsTest {
assertNotNull(MinecraftMethods.getSendPacketMethod()); assertNotNull(MinecraftMethods.getSendPacketMethod());
assertNotNull(MinecraftMethods.getNetworkManagerHandleMethod()); assertNotNull(MinecraftMethods.getNetworkManagerHandleMethod());
} }
private void setNull(final String fieldName) throws NoSuchFieldException, IllegalAccessException {
Field field = MinecraftMethods.class.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(null, null);
}
@Test
public void initializePacket() throws NoSuchFieldException, IllegalAccessException {
setNull("packetReadByteBuf");
setNull("packetWriteByteBuf");
assertNotNull(MinecraftMethods.getPacketWriteByteBufMethod());
assertNotNull(MinecraftMethods.getPacketReadByteBufMethod());
}
} }