This is a huge commit, adds ProtocolLib support. (To the best extent I can):

Channels now work as proxies for the minecraft encoder and decoder, this allows better compatibility when transforming packets.
ConnectionInfo now holds an activate state to deactivate.
Now only 2 handlers
We now use info.getChannel().pipeline().writeAndFlush to ensure we catch it ourselves.
Fix EntityMetadata from last commit so it sends empty metadata instead of cancelling whole packet.
Warn if they reload and don't reinject.
This commit is contained in:
Myles 2016-03-02 15:21:07 +00:00
parent b5b0117fa9
commit 318db73f3c
12 changed files with 176 additions and 183 deletions

View File

@ -9,11 +9,12 @@ import java.util.UUID;
public class ConnectionInfo { public class ConnectionInfo {
private final SocketChannel channel; private final SocketChannel channel;
private int protocol = 0;
private State state = State.HANDSHAKE;
private int compression = 0;
private Object lastPacket; private Object lastPacket;
private java.util.UUID UUID; private java.util.UUID UUID;
private State state = State.HANDSHAKE;
private int protocol = 0;
private int compression = 0;
private boolean active = true;
public ConnectionInfo(SocketChannel socketChannel) { public ConnectionInfo(SocketChannel socketChannel) {
this.channel = socketChannel; this.channel = socketChannel;
@ -66,4 +67,12 @@ public class ConnectionInfo {
public SocketChannel getChannel() { public SocketChannel getChannel() {
return channel; return channel;
} }
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
} }

View File

@ -34,14 +34,17 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
@Override @Override
public void onEnable() { public void onEnable() {
ViaVersion.setInstance(this); ViaVersion.setInstance(this);
System.out.println("ViaVersion enabled, injecting. (Allows 1.8 to be accessed via 1.9)"); if(System.getProperty("ViaVersion") != null){
getLogger().severe("ViaVersion is already loaded, we don't support reloads. Please reboot if you wish to update.");
return;
}
getLogger().info("ViaVersion enabled, injecting. (Allows 1.8 to be accessed via 1.9)");
try { try {
injectPacketHandler(); injectPacketHandler();
System.setProperty("ViaVersion", getDescription().getVersion());
} catch (Exception e) { } catch (Exception e) {
if(Bukkit.getPluginManager().getPlugin("ProtocolLib") != null){ getLogger().severe("Unable to inject handlers, are you on 1.8? ");
System.out.println("This plugin is not compatible with protocol lib.");
}
System.out.println("Unable to inject handlers, are you on 1.8? ");
e.printStackTrace(); e.printStackTrace();
} }
Bukkit.getPluginManager().registerEvents(new Listener() { Bukkit.getPluginManager().registerEvents(new Listener() {
@ -59,7 +62,7 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaVersionAPI {
List<ChannelFuture> futures = ReflectionUtil.get(connection, "g", List.class); List<ChannelFuture> futures = ReflectionUtil.get(connection, "g", List.class);
if (futures.size() == 0) { if (futures.size() == 0) {
throw new Exception("Could not find server to inject (late bind?)"); throw new Exception("Could not find server to inject (Please ensure late-bind in your spigot.yml is false)");
} }
for (ChannelFuture future : futures) { for (ChannelFuture future : futures) {

View File

@ -0,0 +1,48 @@
package us.myles.ViaVersion.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.ByteToMessageDecoder;
import us.myles.ViaVersion.CancelException;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.util.PacketUtil;
import us.myles.ViaVersion.transformers.IncomingTransformer;
import java.util.List;
public class ViaDecodeHandler extends ByteToMessageDecoder {
private final IncomingTransformer incomingTransformer;
private final ByteToMessageDecoder minecraftDecoder;
private final ConnectionInfo info;
public ViaDecodeHandler(ConnectionInfo info, ByteToMessageDecoder minecraftDecoder) {
this.info = info;
this.minecraftDecoder = minecraftDecoder;
this.incomingTransformer = new IncomingTransformer(info);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf bytebuf, List<Object> list) throws Exception {
// use transformers
if(bytebuf.readableBytes() > 0) {
if(info.isActive()) {
int id = PacketUtil.readVarInt(bytebuf);
// Transform
ByteBuf newPacket = ctx.alloc().buffer();
try {
incomingTransformer.transform(id, bytebuf, newPacket);
bytebuf = newPacket;
} catch (CancelException e) {
return;
}
}
// call minecraft decoder
list.addAll(PacketUtil.callDecode(this.minecraftDecoder, ctx, bytebuf));
}
}
}

View File

@ -0,0 +1,72 @@
package us.myles.ViaVersion.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import us.myles.ViaVersion.CancelException;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.transformers.OutgoingTransformer;
import us.myles.ViaVersion.util.PacketUtil;
import us.myles.ViaVersion.util.ReflectionUtil;
import java.lang.reflect.Constructor;
public class ViaEncodeHandler extends MessageToByteEncoder {
private final ConnectionInfo info;
private final MessageToByteEncoder minecraftEncoder;
private final OutgoingTransformer outgoingTransformer;
public ViaEncodeHandler(ConnectionInfo info, MessageToByteEncoder minecraftEncoder) {
this.info = info;
this.minecraftEncoder = minecraftEncoder;
this.outgoingTransformer = new OutgoingTransformer(info);
}
@Override
protected void encode(ChannelHandlerContext ctx, Object o, ByteBuf bytebuf) throws Exception {
// handle the packet type
if (o == null) return;
if (!(o instanceof ByteBuf)) {
info.setLastPacket(o);
/* This transformer is more for fixing issues which we find hard at packet level :) */
if (o.getClass().getName().endsWith("PacketPlayOutMapChunkBulk")) {
int[] locX = ReflectionUtil.get(o, "a", int[].class);
int[] locZ = ReflectionUtil.get(o, "b", int[].class);
Object world = ReflectionUtil.get(o, "world", ReflectionUtil.nms("World"));
Class<?> mapChunk = ReflectionUtil.nms("PacketPlayOutMapChunk");
Constructor constructor = mapChunk.getDeclaredConstructor(ReflectionUtil.nms("Chunk"), boolean.class, int.class);
for (int i = 0; i < locX.length; i++) {
int x = locX[i];
int z = locZ[i];
// world invoke function
Object chunk = ReflectionUtil.nms("World").getDeclaredMethod("getChunkAt", int.class, int.class).invoke(world, x, z);
Object packet = constructor.newInstance(chunk, true, 65535);
ctx.pipeline().writeAndFlush(packet);
}
bytebuf.clear();
return;
}
// call minecraft encoder
PacketUtil.callEncode(this.minecraftEncoder, ctx, o, bytebuf);
}
if (bytebuf.readableBytes() == 0) {
return;
}
if(info.isActive()) {
int id = PacketUtil.readVarInt(bytebuf);
// Transform
ByteBuf oldPacket = bytebuf.copy();
bytebuf.clear();
try {
outgoingTransformer.transform(id, oldPacket, bytebuf);
} catch (CancelException e) {
return;
} finally {
oldPacket.release();
}
}
}
}

View File

@ -1,51 +0,0 @@
package us.myles.ViaVersion.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import us.myles.ViaVersion.CancelException;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.util.PacketUtil;
import us.myles.ViaVersion.transformers.IncomingTransformer;
@ChannelHandler.Sharable
public class ViaInboundHandler extends ChannelInboundHandlerAdapter {
private final IncomingTransformer incomingTransformer;
public ViaInboundHandler(ConnectionInfo info) {
this.incomingTransformer = new IncomingTransformer(info);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
boolean compression = ctx.pipeline().get("compress") != null;
if (msg instanceof ByteBuf) {
ByteBuf bytebuf = (ByteBuf) msg;
if (compression) {
// decompress :)
bytebuf = PacketUtil.decompress(ctx, bytebuf);
}
int id = PacketUtil.readVarInt(bytebuf);
// Transform
ByteBuf newPacket = ctx.alloc().buffer();
try {
incomingTransformer.transform(id, bytebuf, newPacket);
} catch (CancelException e) {
return;
} finally {
bytebuf.release();
}
if (compression) {
// recompress :)
newPacket = PacketUtil.compress(ctx, newPacket);
}
msg = newPacket;
}
super.channelRead(ctx, msg);
}
}

View File

@ -1,53 +0,0 @@
package us.myles.ViaVersion.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import us.myles.ViaVersion.CancelException;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.util.PacketUtil;
import us.myles.ViaVersion.transformers.OutgoingTransformer;
@ChannelHandler.Sharable
public class ViaOutboundHandler extends ChannelOutboundHandlerAdapter {
private final OutgoingTransformer outgoingTransformer;
private final ConnectionInfo info;
public ViaOutboundHandler(ConnectionInfo info) {
this.info = info;
this.outgoingTransformer = new OutgoingTransformer(info);
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise channelPromise) throws Exception {
try {
if (channelPromise.isDone()) return; // don't break any <3s
boolean compression = ctx.pipeline().get("compress") != null;
if (msg instanceof ByteBuf) {
ByteBuf bytebuf = (ByteBuf) msg;
if (compression) {
// decompress :)
bytebuf = PacketUtil.decompress(ctx, bytebuf);
}
int id = PacketUtil.readVarInt(bytebuf);
// Transform
ByteBuf newPacket = ctx.alloc().buffer();
try {
outgoingTransformer.transform(id, bytebuf, newPacket);
} catch (CancelException e) {
return;
} finally {
bytebuf.release();
}
if (compression) {
// recompress :)
newPacket = PacketUtil.compress(ctx, newPacket);
}
msg = newPacket;
}
super.write(ctx, msg, channelPromise);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -1,43 +0,0 @@
package us.myles.ViaVersion.handlers;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import us.myles.ViaVersion.ConnectionInfo;
import us.myles.ViaVersion.util.ReflectionUtil;
import java.lang.reflect.Constructor;
@ChannelHandler.Sharable
public class ViaOutboundPacketHandler extends ChannelOutboundHandlerAdapter {
private final ConnectionInfo info;
public ViaOutboundPacketHandler(ConnectionInfo info) {
this.info = info;
}
@Override
public void write(ChannelHandlerContext channelHandlerContext, Object o, ChannelPromise channelPromise) throws Exception {
if (!(o instanceof ByteBuf)) {
info.setLastPacket(o);
/* This transformer is more for fixing issues which we find hard at byte level :) */
if (o.getClass().getName().endsWith("PacketPlayOutMapChunkBulk")) {
int[] locX = ReflectionUtil.get(o, "a", int[].class);
int[] locZ = ReflectionUtil.get(o, "b", int[].class);
Object world = ReflectionUtil.get(o, "world", ReflectionUtil.nms("World"));
Class<?> mapChunk = ReflectionUtil.nms("PacketPlayOutMapChunk");
Constructor constructor = mapChunk.getDeclaredConstructor(ReflectionUtil.nms("Chunk"), boolean.class, int.class);
for (int i = 0; i < locX.length; i++) {
int x = locX[i];
int z = locZ[i];
// world invoke function
Object chunk = ReflectionUtil.nms("World").getDeclaredMethod("getChunkAt", int.class, int.class).invoke(world, x, z);
Object packet = constructor.newInstance(chunk, true, 65535);
channelHandlerContext.write(packet);
}
return;
}
}
super.write(channelHandlerContext, o, channelPromise);
}
}

View File

@ -3,6 +3,8 @@ package us.myles.ViaVersion.handlers;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import us.myles.ViaVersion.ConnectionInfo; import us.myles.ViaVersion.ConnectionInfo;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -27,12 +29,10 @@ public class ViaVersionInitializer extends ChannelInitializer<SocketChannel> {
// Add originals // Add originals
this.method.invoke(this.oldInit, socketChannel); this.method.invoke(this.oldInit, socketChannel);
// Add our transformers // Add our transformers
ViaInboundHandler inbound = new ViaInboundHandler(info); ViaEncodeHandler encoder = new ViaEncodeHandler(info, (MessageToByteEncoder) socketChannel.pipeline().get("encoder"));
ViaOutboundHandler outbound = new ViaOutboundHandler(info); ViaDecodeHandler decoder = new ViaDecodeHandler(info, (ByteToMessageDecoder) socketChannel.pipeline().get("decoder"));
ViaOutboundPacketHandler outbound2 = new ViaOutboundPacketHandler(info);
socketChannel.pipeline().addBefore("decoder", "via_incoming", inbound);
socketChannel.pipeline().addBefore("packet_handler", "via_outgoing2", outbound2);
socketChannel.pipeline().addBefore("encoder", "via_outgoing", outbound);
socketChannel.pipeline().replace("encoder", "encoder", encoder);
socketChannel.pipeline().replace("decoder", "decoder", decoder);
} }
} }

View File

@ -45,9 +45,7 @@ public class IncomingTransformer {
if (protVer <= 102) { if (protVer <= 102) {
// not 1.9, remove pipes // not 1.9, remove pipes
info.getChannel().pipeline().remove("via_incoming"); info.setActive(false);
info.getChannel().pipeline().remove("via_outgoing");
info.getChannel().pipeline().remove("via_outgoing2");
} }
String serverAddress = PacketUtil.readString(input); String serverAddress = PacketUtil.readString(input);
PacketUtil.writeString(serverAddress, output); PacketUtil.writeString(serverAddress, output);
@ -105,7 +103,7 @@ public class IncomingTransformer {
try { try {
Class<?> setSlot = ReflectionUtil.nms("PacketPlayOutSetSlot"); Class<?> setSlot = ReflectionUtil.nms("PacketPlayOutSetSlot");
Object setSlotPacket = setSlot.getConstructors()[1].newInstance(windowID, slot, null); Object setSlotPacket = setSlot.getConstructors()[1].newInstance(windowID, slot, null);
info.getChannel().writeAndFlush(setSlotPacket); // slot is empty info.getChannel().pipeline().writeAndFlush(setSlotPacket); // slot is empty
slot = -999; // we're evil, they'll throw item on the ground slot = -999; // we're evil, they'll throw item on the ground
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
e.printStackTrace(); e.printStackTrace();

View File

@ -290,7 +290,6 @@ public class OutgoingTransformer {
int id = PacketUtil.readVarInt(input); int id = PacketUtil.readVarInt(input);
clientEntityTypes.put(id, EntityType.EXPERIENCE_ORB); clientEntityTypes.put(id, EntityType.EXPERIENCE_ORB);
PacketUtil.writeVarInt(id, output); PacketUtil.writeVarInt(id, output);
double x = input.readInt(); double x = input.readInt();
output.writeDouble(x / 32D); output.writeDouble(x / 32D);
double y = input.readInt(); double y = input.readInt();
@ -309,7 +308,6 @@ public class OutgoingTransformer {
PacketUtil.writeVarInt(id, output); PacketUtil.writeVarInt(id, output);
PacketUtil.writeUUID(getUUID(id), output); PacketUtil.writeUUID(getUUID(id), output);
String title = PacketUtil.readString(input); String title = PacketUtil.readString(input);
PacketUtil.writeString(title, output); PacketUtil.writeString(title, output);
@ -533,7 +531,8 @@ public class OutgoingTransformer {
EntityType type = clientEntityTypes.get(entityID); EntityType type = clientEntityTypes.get(entityID);
if (type == null) { if (type == null) {
System.out.println("Unable to get entity for ID: " + entityID); System.out.println("Unable to get entity for ID: " + entityID);
throw new CancelException(); output.writeByte(255);
return;
} }
if (dw != null) { if (dw != null) {
short id = -1; short id = -1;

View File

@ -42,29 +42,38 @@ public class PacketUtil {
} }
} }
public static ByteBuf decompress(ChannelHandlerContext ctx, ByteBuf msg) { public static List<Object> callDecode(ByteToMessageDecoder decoder, ChannelHandlerContext ctx, Object input) {
ByteToMessageDecoder x = (ByteToMessageDecoder) ctx.pipeline().get("decompress");
List<Object> output = new ArrayList<Object>(); List<Object> output = new ArrayList<Object>();
try { try {
PacketUtil.DECODE_METHOD.invoke(x, ctx, msg, output); PacketUtil.DECODE_METHOD.invoke(decoder, ctx, input, output);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
e.printStackTrace(); e.printStackTrace();
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
e.printStackTrace(); e.printStackTrace();
} }
return output;
}
public static void callEncode(MessageToByteEncoder encoder, ChannelHandlerContext ctx, Object msg, ByteBuf output) {
try {
PacketUtil.ENCODE_METHOD.invoke(encoder, ctx, msg, output);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public static ByteBuf decompress(ChannelHandlerContext ctx, ByteBuf msg) {
ByteToMessageDecoder x = (ByteToMessageDecoder) ctx.pipeline().get("decompress");
List<Object> output = callDecode(x, ctx, msg);
return output.size() == 0 ? null : (ByteBuf) output.get(0); return output.size() == 0 ? null : (ByteBuf) output.get(0);
} }
public static ByteBuf compress(ChannelHandlerContext ctx, ByteBuf msg) { public static ByteBuf compress(ChannelHandlerContext ctx, ByteBuf msg) {
MessageToByteEncoder x = (MessageToByteEncoder) ctx.pipeline().get("compress"); MessageToByteEncoder x = (MessageToByteEncoder) ctx.pipeline().get("compress");
ByteBuf output = ctx.alloc().buffer(); ByteBuf output = ctx.alloc().buffer();
try { callEncode(x, ctx, msg, output);
PacketUtil.ENCODE_METHOD.invoke(x, ctx, msg, output);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return output; return output;
} }
@ -185,9 +194,9 @@ public class PacketUtil {
} }
public static void writeVarIntArray(List<Integer> integers, ByteBuf output) { public static void writeVarIntArray(List<Integer> integers, ByteBuf output) {
writeVarInt(integers.size(),output); writeVarInt(integers.size(), output);
for (Integer i : integers){ for (Integer i : integers) {
writeVarInt(i,output); writeVarInt(i, output);
} }
} }

View File

@ -1,4 +1,6 @@
name: ViaVersion name: ViaVersion
main: us.myles.ViaVersion.ViaVersionPlugin main: us.myles.ViaVersion.ViaVersionPlugin
author: _MylesC author: _MylesC
version: 0.3.7 version: 0.3.7
load: startup
loadbefore: [ProtocolLib, ProxyPipe]