From 00cc545795bafc2fea3b9590e81ff625376fc293 Mon Sep 17 00:00:00 2001 From: Myles Date: Tue, 10 May 2016 14:37:31 +0100 Subject: [PATCH] Using javassist add compatibility for Spigot builds with protocol compatibility (eg. 1.9.1 & 1.9.2) Also fix issue with protocol passthrough *whew* --- .../us/myles/ViaVersion/ViaVersionPlugin.java | 17 +++- .../myles/ViaVersion/api/PacketWrapper.java | 11 ++- .../myles/ViaVersion/api/ViaVersionAPI.java | 8 ++ .../ViaVersion/api/data/UserConnection.java | 3 - .../BasicHandlerConstructor.java | 20 +++++ .../classgenerator/ClassGenerator.java | 88 +++++++++++++++++++ .../classgenerator/HandlerConstructor.java | 10 +++ .../ViaVersion/handlers/ViaEncodeHandler.java | 7 ++ .../handlers/ViaVersionInitializer.java | 8 +- 9 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/main/java/us/myles/ViaVersion/classgenerator/BasicHandlerConstructor.java create mode 100644 src/main/java/us/myles/ViaVersion/classgenerator/ClassGenerator.java create mode 100644 src/main/java/us/myles/ViaVersion/classgenerator/HandlerConstructor.java diff --git a/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java b/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java index 6363e4f2f..f7bdb2533 100644 --- a/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java +++ b/src/main/java/us/myles/ViaVersion/ViaVersionPlugin.java @@ -20,6 +20,7 @@ import us.myles.ViaVersion.api.command.ViaVersionCommand; import us.myles.ViaVersion.api.data.UserConnection; import us.myles.ViaVersion.api.protocol.ProtocolRegistry; import us.myles.ViaVersion.boss.ViaBossBar; +import us.myles.ViaVersion.classgenerator.ClassGenerator; import us.myles.ViaVersion.commands.ViaCommandHandler; import us.myles.ViaVersion.handlers.ViaVersionInitializer; import us.myles.ViaVersion.protocols.base.ProtocolInfo; @@ -48,6 +49,7 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI, ViaVe private List> injectedLists = new ArrayList<>(); private ViaCommandHandler commandHandler; private boolean debug = false; + private boolean compatSpigotBuild = false; @Override public void onLoad() { @@ -65,8 +67,16 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI, ViaVe } } + // Check if it's a spigot build with a protocol mod + try { + compatSpigotBuild = ReflectionUtil.nms("PacketEncoder").getDeclaredField("version") != null; + } catch (Exception e){ + compatSpigotBuild = false; + } + // Generate classes needed (only works if it's compat) + ClassGenerator.generate(); - getLogger().info("ViaVersion " + getDescription().getVersion() + " is now loaded, injecting."); + getLogger().info("ViaVersion " + getDescription().getVersion() + (compatSpigotBuild ? "compat" : "") + " is now loaded, injecting."); injectPacketHandler(); } @@ -348,6 +358,11 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI, ViaVe return commandHandler; } + @Override + public boolean isCompatSpigotBuild() { + return compatSpigotBuild; + } + public boolean isCheckForUpdates() { return getConfig().getBoolean("checkforupdates", true); } diff --git a/src/main/java/us/myles/ViaVersion/api/PacketWrapper.java b/src/main/java/us/myles/ViaVersion/api/PacketWrapper.java index 5a605d7d6..db94b0a66 100644 --- a/src/main/java/us/myles/ViaVersion/api/PacketWrapper.java +++ b/src/main/java/us/myles/ViaVersion/api/PacketWrapper.java @@ -174,7 +174,7 @@ public class PacketWrapper { readableObjects.clear(); // If the buffer has readable bytes, copy them. if(inputBuffer.readableBytes() > 0){ - read(Type.REMAINING_BYTES); + passthrough(Type.REMAINING_BYTES); } } @@ -318,4 +318,13 @@ public class PacketWrapper { PipelineUtil.getContextBefore("decompress", user().getChannel().pipeline()).fireChannelRead(output); } } + + @Override + public String toString() { + return "PacketWrapper{" + + "packetValues=" + packetValues + + ", readableObjects=" + readableObjects + + ", id=" + id + + '}'; + } } diff --git a/src/main/java/us/myles/ViaVersion/api/ViaVersionAPI.java b/src/main/java/us/myles/ViaVersion/api/ViaVersionAPI.java index 5f69cf8ee..3a5c309a5 100644 --- a/src/main/java/us/myles/ViaVersion/api/ViaVersionAPI.java +++ b/src/main/java/us/myles/ViaVersion/api/ViaVersionAPI.java @@ -101,4 +101,12 @@ public interface ViaVersionAPI { * @return command handler */ ViaVersionCommand getCommandHandler(); + + /** + * Get if this version is a compatibility build for spigot. + * Eg. 1.9.1 / 1.9.2 allow certain versions to connect + * + * @return True if it is + */ + boolean isCompatSpigotBuild(); } diff --git a/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java b/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java index d253cf31f..cb7588114 100644 --- a/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java +++ b/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java @@ -1,12 +1,9 @@ package us.myles.ViaVersion.api.data; -import com.google.gson.Gson; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.socket.SocketChannel; import lombok.Data; -import lombok.Getter; -import lombok.Setter; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; diff --git a/src/main/java/us/myles/ViaVersion/classgenerator/BasicHandlerConstructor.java b/src/main/java/us/myles/ViaVersion/classgenerator/BasicHandlerConstructor.java new file mode 100644 index 000000000..2dfd4d49b --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/classgenerator/BasicHandlerConstructor.java @@ -0,0 +1,20 @@ +package us.myles.ViaVersion.classgenerator; + +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.MessageToByteEncoder; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.classgenerator.HandlerConstructor; +import us.myles.ViaVersion.handlers.ViaDecodeHandler; +import us.myles.ViaVersion.handlers.ViaEncodeHandler; + +public class BasicHandlerConstructor implements HandlerConstructor { + @Override + public ViaEncodeHandler newEncodeHandler(UserConnection info, MessageToByteEncoder minecraftEncoder) { + return new ViaEncodeHandler(info, minecraftEncoder); + } + + @Override + public ViaDecodeHandler newDecodeHandler(UserConnection info, ByteToMessageDecoder minecraftDecoder) { + return new ViaDecodeHandler(info, minecraftDecoder); + } +} diff --git a/src/main/java/us/myles/ViaVersion/classgenerator/ClassGenerator.java b/src/main/java/us/myles/ViaVersion/classgenerator/ClassGenerator.java new file mode 100644 index 000000000..82a331ae5 --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/classgenerator/ClassGenerator.java @@ -0,0 +1,88 @@ +package us.myles.ViaVersion.classgenerator; + +import javassist.*; +import javassist.expr.ConstructorCall; +import javassist.expr.ExprEditor; +import us.myles.ViaVersion.api.ViaVersion; +import us.myles.ViaVersion.handlers.ViaDecodeHandler; +import us.myles.ViaVersion.handlers.ViaEncodeHandler; +import us.myles.ViaVersion.util.ReflectionUtil; + +public class ClassGenerator { + private static HandlerConstructor constructor = new BasicHandlerConstructor(); + + public static HandlerConstructor getConstructor() { + return constructor; + } + + public static void generate() { + if(!ViaVersion.getInstance().isCompatSpigotBuild()) return; // Use Basic Handler as not needed. + + try { + ClassPool pool = ClassPool.getDefault(); + pool.insertClassPath(new LoaderClassPath(ClassGenerator.class.getClassLoader())); + // Generate the classes + transformSuperclass(pool, ViaDecodeHandler.class, ReflectionUtil.nms("PacketDecoder")); + transformSuperclass(pool, ViaEncodeHandler.class, ReflectionUtil.nms("PacketEncoder")); + + // Implement Constructor + CtClass generated = pool.makeClass("us.myles.ViaVersion.classgenerator.generated.GeneratedConstructor"); + CtClass handlerInterface = pool.get(HandlerConstructor.class.getName()); + + generated.setInterfaces(new CtClass[]{handlerInterface}); + // Import required classes + pool.importPackage("us.myles.ViaVersion.classgenerator.generated"); + pool.importPackage("us.myles.ViaVersion.classgenerator"); + pool.importPackage("us.myles.ViaVersion.api.data"); + pool.importPackage("io.netty.handler.codec"); + // Implement Methods + generated.addMethod(CtMethod.make("public MessageToByteEncoder newEncodeHandler(UserConnection info, MessageToByteEncoder minecraftEncoder) {\n" + + " return new ViaEncodeHandler(info, minecraftEncoder);\n" + + " }", generated)); + generated.addMethod(CtMethod.make("public ByteToMessageDecoder newDecodeHandler(UserConnection info, ByteToMessageDecoder minecraftDecoder) {\n" + + " return new ViaDecodeHandler(info, minecraftDecoder);\n" + + " }", generated)); + + constructor = (HandlerConstructor) generated.toClass(HandlerConstructor.class.getClassLoader()).newInstance(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (CannotCompileException e) { + e.printStackTrace(); + } catch (NotFoundException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private static Class transformSuperclass(ClassPool pool, Class input, Class superclass) { + String newName = "us.myles.ViaVersion.classgenerator.generated." + input.getSimpleName(); + + try { + CtClass toExtend = pool.get(superclass.getName()); + CtClass generated = pool.getAndRename(input.getName(), newName); + generated.setSuperclass(toExtend); + // Modify constructor to call super + if(generated.getConstructors().length != 0) { + generated.getConstructors()[0].instrument(new ExprEditor() { + @Override + public void edit(ConstructorCall c) throws CannotCompileException { + if (c.isSuper()) { + // Constructor for both has a stats thing. + c.replace("super(null);"); + } + super.edit(c); + } + }); + } + return generated.toClass(HandlerConstructor.class.getClassLoader()); + } catch (NotFoundException e) { + e.printStackTrace(); + } catch (CannotCompileException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/main/java/us/myles/ViaVersion/classgenerator/HandlerConstructor.java b/src/main/java/us/myles/ViaVersion/classgenerator/HandlerConstructor.java new file mode 100644 index 000000000..fe534238a --- /dev/null +++ b/src/main/java/us/myles/ViaVersion/classgenerator/HandlerConstructor.java @@ -0,0 +1,10 @@ +package us.myles.ViaVersion.classgenerator; + +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.MessageToByteEncoder; +import us.myles.ViaVersion.api.data.UserConnection; + +public interface HandlerConstructor { + public MessageToByteEncoder newEncodeHandler(UserConnection info, MessageToByteEncoder minecraftEncoder); + public ByteToMessageDecoder newDecodeHandler(UserConnection info, ByteToMessageDecoder minecraftDecoder); +} diff --git a/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java b/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java index c86091f80..fd0f5d2e2 100644 --- a/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java +++ b/src/main/java/us/myles/ViaVersion/handlers/ViaEncodeHandler.java @@ -4,6 +4,7 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import us.myles.ViaVersion.api.PacketWrapper; +import us.myles.ViaVersion.api.ViaVersion; import us.myles.ViaVersion.api.data.UserConnection; import us.myles.ViaVersion.api.type.Type; import us.myles.ViaVersion.exception.CancelException; @@ -11,6 +12,7 @@ import us.myles.ViaVersion.packets.Direction; import us.myles.ViaVersion.protocols.base.ProtocolInfo; import us.myles.ViaVersion.util.PipelineUtil; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; public class ViaEncodeHandler extends MessageToByteEncoder { @@ -25,6 +27,11 @@ public class ViaEncodeHandler extends MessageToByteEncoder { @Override protected void encode(final ChannelHandlerContext ctx, Object o, final ByteBuf bytebuf) throws Exception { + if (ViaVersion.getInstance().isCompatSpigotBuild()) { + Field ver = minecraftEncoder.getClass().getDeclaredField("version"); + ver.setAccessible(true); + ver.set(minecraftEncoder, ver.get(this)); + } // handle the packet type if (!(o instanceof ByteBuf)) { // call minecraft encoder diff --git a/src/main/java/us/myles/ViaVersion/handlers/ViaVersionInitializer.java b/src/main/java/us/myles/ViaVersion/handlers/ViaVersionInitializer.java index 019ee4fd3..ccd70da1e 100644 --- a/src/main/java/us/myles/ViaVersion/handlers/ViaVersionInitializer.java +++ b/src/main/java/us/myles/ViaVersion/handlers/ViaVersionInitializer.java @@ -7,6 +7,8 @@ import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.MessageToByteEncoder; import us.myles.ViaVersion.api.data.UserConnection; import us.myles.ViaVersion.api.protocol.ProtocolPipeline; +import us.myles.ViaVersion.classgenerator.ClassGenerator; +import us.myles.ViaVersion.classgenerator.HandlerConstructor; import java.lang.reflect.Method; @@ -36,9 +38,11 @@ public class ViaVersionInitializer extends ChannelInitializer { new ProtocolPipeline(info); // Add originals this.method.invoke(this.original, socketChannel); + + HandlerConstructor constructor = ClassGenerator.getConstructor(); // Add our transformers - ViaEncodeHandler encoder = new ViaEncodeHandler(info, (MessageToByteEncoder) socketChannel.pipeline().get("encoder")); - ViaDecodeHandler decoder = new ViaDecodeHandler(info, (ByteToMessageDecoder) socketChannel.pipeline().get("decoder")); + MessageToByteEncoder encoder = constructor.newEncodeHandler(info, (MessageToByteEncoder) socketChannel.pipeline().get("encoder")); + ByteToMessageDecoder decoder = constructor.newDecodeHandler(info, (ByteToMessageDecoder) socketChannel.pipeline().get("decoder")); ViaPacketHandler chunkHandler = new ViaPacketHandler(info); socketChannel.pipeline().replace("encoder", "encoder", encoder);