Using javassist add compatibility for Spigot builds with protocol compatibility (eg. 1.9.1 & 1.9.2)

Also fix issue with protocol passthrough
*whew*
This commit is contained in:
Myles 2016-05-10 14:37:31 +01:00
parent c7ee816aba
commit 00cc545795
9 changed files with 165 additions and 7 deletions

View File

@ -20,6 +20,7 @@ import us.myles.ViaVersion.api.command.ViaVersionCommand;
import us.myles.ViaVersion.api.data.UserConnection; import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.api.protocol.ProtocolRegistry; import us.myles.ViaVersion.api.protocol.ProtocolRegistry;
import us.myles.ViaVersion.boss.ViaBossBar; import us.myles.ViaVersion.boss.ViaBossBar;
import us.myles.ViaVersion.classgenerator.ClassGenerator;
import us.myles.ViaVersion.commands.ViaCommandHandler; import us.myles.ViaVersion.commands.ViaCommandHandler;
import us.myles.ViaVersion.handlers.ViaVersionInitializer; import us.myles.ViaVersion.handlers.ViaVersionInitializer;
import us.myles.ViaVersion.protocols.base.ProtocolInfo; import us.myles.ViaVersion.protocols.base.ProtocolInfo;
@ -48,6 +49,7 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI, ViaVe
private List<Pair<Field, Object>> injectedLists = new ArrayList<>(); private List<Pair<Field, Object>> injectedLists = new ArrayList<>();
private ViaCommandHandler commandHandler; private ViaCommandHandler commandHandler;
private boolean debug = false; private boolean debug = false;
private boolean compatSpigotBuild = false;
@Override @Override
public void onLoad() { 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(); injectPacketHandler();
} }
@ -348,6 +358,11 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI, ViaVe
return commandHandler; return commandHandler;
} }
@Override
public boolean isCompatSpigotBuild() {
return compatSpigotBuild;
}
public boolean isCheckForUpdates() { public boolean isCheckForUpdates() {
return getConfig().getBoolean("checkforupdates", true); return getConfig().getBoolean("checkforupdates", true);
} }

View File

@ -174,7 +174,7 @@ public class PacketWrapper {
readableObjects.clear(); readableObjects.clear();
// If the buffer has readable bytes, copy them. // If the buffer has readable bytes, copy them.
if(inputBuffer.readableBytes() > 0){ 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); PipelineUtil.getContextBefore("decompress", user().getChannel().pipeline()).fireChannelRead(output);
} }
} }
@Override
public String toString() {
return "PacketWrapper{" +
"packetValues=" + packetValues +
", readableObjects=" + readableObjects +
", id=" + id +
'}';
}
} }

View File

@ -101,4 +101,12 @@ public interface ViaVersionAPI {
* @return command handler * @return command handler
*/ */
ViaVersionCommand getCommandHandler(); 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();
} }

View File

@ -1,12 +1,9 @@
package us.myles.ViaVersion.api.data; package us.myles.ViaVersion.api.data;
import com.google.gson.Gson;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import lombok.Data; import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -4,6 +4,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.MessageToByteEncoder;
import us.myles.ViaVersion.api.PacketWrapper; import us.myles.ViaVersion.api.PacketWrapper;
import us.myles.ViaVersion.api.ViaVersion;
import us.myles.ViaVersion.api.data.UserConnection; import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.api.type.Type; import us.myles.ViaVersion.api.type.Type;
import us.myles.ViaVersion.exception.CancelException; 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.protocols.base.ProtocolInfo;
import us.myles.ViaVersion.util.PipelineUtil; import us.myles.ViaVersion.util.PipelineUtil;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
public class ViaEncodeHandler extends MessageToByteEncoder { public class ViaEncodeHandler extends MessageToByteEncoder {
@ -25,6 +27,11 @@ public class ViaEncodeHandler extends MessageToByteEncoder {
@Override @Override
protected void encode(final ChannelHandlerContext ctx, Object o, final ByteBuf bytebuf) throws Exception { 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 // handle the packet type
if (!(o instanceof ByteBuf)) { if (!(o instanceof ByteBuf)) {
// call minecraft encoder // call minecraft encoder

View File

@ -7,6 +7,8 @@ import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.MessageToByteEncoder;
import us.myles.ViaVersion.api.data.UserConnection; import us.myles.ViaVersion.api.data.UserConnection;
import us.myles.ViaVersion.api.protocol.ProtocolPipeline; import us.myles.ViaVersion.api.protocol.ProtocolPipeline;
import us.myles.ViaVersion.classgenerator.ClassGenerator;
import us.myles.ViaVersion.classgenerator.HandlerConstructor;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -36,9 +38,11 @@ public class ViaVersionInitializer extends ChannelInitializer<SocketChannel> {
new ProtocolPipeline(info); new ProtocolPipeline(info);
// Add originals // Add originals
this.method.invoke(this.original, socketChannel); this.method.invoke(this.original, socketChannel);
HandlerConstructor constructor = ClassGenerator.getConstructor();
// Add our transformers // Add our transformers
ViaEncodeHandler encoder = new ViaEncodeHandler(info, (MessageToByteEncoder) socketChannel.pipeline().get("encoder")); MessageToByteEncoder encoder = constructor.newEncodeHandler(info, (MessageToByteEncoder) socketChannel.pipeline().get("encoder"));
ViaDecodeHandler decoder = new ViaDecodeHandler(info, (ByteToMessageDecoder) socketChannel.pipeline().get("decoder")); ByteToMessageDecoder decoder = constructor.newDecodeHandler(info, (ByteToMessageDecoder) socketChannel.pipeline().get("decoder"));
ViaPacketHandler chunkHandler = new ViaPacketHandler(info); ViaPacketHandler chunkHandler = new ViaPacketHandler(info);
socketChannel.pipeline().replace("encoder", "encoder", encoder); socketChannel.pipeline().replace("encoder", "encoder", encoder);