From 32772d1e9b224c9d20700cc7ed429829953cf673 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 24 Jul 2018 23:40:20 -0400 Subject: [PATCH] Fix a bunch of small issues, so that the proxy can establish a connection This is still broken and has performance/bandwidth problems, which I will fix very soon. For a first try, it's good enough. --- .../velocity/protocol/ProtocolUtils.java | 42 ++++++++----------- .../velocity/protocol/StateRegistry.java | 2 +- .../protocol/netty/MinecraftDecoder.java | 8 +++- .../velocity/protocol/packets/Handshake.java | 16 +++---- .../velocity/proxy/ConnectedPlayer.java | 7 +++- .../proxy/InboundMinecraftConnection.java | 18 +++++++- .../velocity/proxy/ServerConnection.java | 26 ++++++------ .../proxy/handler/LoginSessionHandler.java | 9 +--- 8 files changed, 70 insertions(+), 58 deletions(-) diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/ProtocolUtils.java b/src/main/java/io/minimum/minecraft/velocity/protocol/ProtocolUtils.java index b96be4691..6f961380a 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/ProtocolUtils.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/ProtocolUtils.java @@ -9,33 +9,27 @@ public enum ProtocolUtils { ; private static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB public static int readVarInt(ByteBuf buf) { - int numRead = 0; - int result = 0; - byte read; - do { - read = buf.readByte(); - int value = (read & 0b01111111); - result |= (value << (7 * numRead)); - - numRead++; - if (numRead > 5) { - throw new RuntimeException("VarInt is too big"); - } - } while ((read & 0b10000000) != 0); - - return result; + int i = 0; + int j = 0; + while (true) { + int k = buf.readByte(); + i |= (k & 0x7F) << j++ * 7; + if (j > 5) throw new RuntimeException("VarInt too big"); + if ((k & 0x80) != 128) break; + } + return i; } public static void writeVarInt(ByteBuf buf, int value) { - do { - byte temp = (byte)(value & 0b01111111); - // Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone - value >>>= 7; - if (value != 0) { - temp |= 0b10000000; + while (true) { + if ((value & 0xFFFFFF80) == 0) { + buf.writeByte(value); + return; } - buf.writeByte(temp); - } while (value != 0); + + buf.writeByte(value & 0x7F | 0x80); + value >>>= 7; + } } public static String readString(ByteBuf buf) { @@ -44,7 +38,7 @@ public enum ProtocolUtils { ; public static String readString(ByteBuf buf, int cap) { int length = readVarInt(buf); - Preconditions.checkArgument(length < cap, "Bad string size (got %s, maximum is %s)", length, cap); + Preconditions.checkArgument(length <= cap, "Bad string size (got %s, maximum is %s)", length, cap); byte[] str = new byte[length]; buf.readBytes(str); return new String(str, StandardCharsets.UTF_8); diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/StateRegistry.java b/src/main/java/io/minimum/minecraft/velocity/protocol/StateRegistry.java index 60f10ee2b..e7c3f4352 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/StateRegistry.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/StateRegistry.java @@ -32,7 +32,7 @@ public enum StateRegistry { TO_CLIENT.register(0x00, Disconnect.class, Disconnect::new); // Encryption Success will follow once Mojang auth/encryption is done - TO_CLIENT.register(0x02, ServerLoginSuccess.class, ServerLogin::new); + TO_CLIENT.register(0x02, ServerLoginSuccess.class, ServerLoginSuccess::new); } }; diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftDecoder.java b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftDecoder.java index 8f7d5340f..07f9e40a8 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftDecoder.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftDecoder.java @@ -5,6 +5,7 @@ import io.minimum.minecraft.velocity.protocol.*; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.CorruptedFrameException; import io.netty.handler.codec.MessageToMessageDecoder; import java.util.List; @@ -35,7 +36,12 @@ public class MinecraftDecoder extends MessageToMessageDecoder { msg.skipBytes(msg.readableBytes()); out.add(new PacketWrapper(null, slice)); } else { - packet.decode(msg, direction, protocolVersion); + try { + packet.decode(msg, direction, protocolVersion); + } catch (Exception e) { + throw new CorruptedFrameException("Error decoding " + packet.getClass() + " Direction " + direction + + " Protocol " + protocolVersion + " State " + state + " ID " + Integer.toHexString(packetId), e); + } out.add(new PacketWrapper(packet, slice)); } } diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/packets/Handshake.java b/src/main/java/io/minimum/minecraft/velocity/protocol/packets/Handshake.java index 563e5909e..dc3ce746c 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/packets/Handshake.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/packets/Handshake.java @@ -55,17 +55,17 @@ public class Handshake implements MinecraftPacket { @Override public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - protocolVersion = ProtocolUtils.readVarInt(buf); - serverAddress = ProtocolUtils.readString(buf, 255); - port = buf.readUnsignedShort(); - nextStatus = ProtocolUtils.readVarInt(buf); + this.protocolVersion = ProtocolUtils.readVarInt(buf); + this.serverAddress = ProtocolUtils.readString(buf, 255); + this.port = buf.readUnsignedShort(); + this.nextStatus = ProtocolUtils.readVarInt(buf); } @Override public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { - ProtocolUtils.writeVarInt(buf, protocolVersion); - ProtocolUtils.writeString(buf, serverAddress); - buf.writeShort(port); - ProtocolUtils.writeVarInt(buf, nextStatus); + ProtocolUtils.writeVarInt(buf, this.protocolVersion); + ProtocolUtils.writeString(buf, this.serverAddress); + buf.writeShort(this.port); + ProtocolUtils.writeVarInt(buf, this.nextStatus); } } diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java b/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java index c7ca0a5a8..5e4e14c94 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java @@ -37,11 +37,14 @@ public class ConnectedPlayer { public void handleConnectionException(Throwable throwable) { String error = "Exception: " + throwable.getClass().getName() + ": " + throwable.getMessage(); + Disconnect disconnect = new Disconnect(); + disconnect.setReason(ComponentSerializers.JSON.serialize(TextComponent.of(error, TextColor.RED))); + handleConnectionException(disconnect); + } + public void handleConnectionException(Disconnect disconnect) { if (connectedServer == null) { // The player isn't yet connected to a server - we should disconnect them. - Disconnect disconnect = new Disconnect(); - disconnect.setReason(ComponentSerializers.JSON.serialize(TextComponent.of(error, TextColor.RED))); connection.closeWith(disconnect); } else { // TODO diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/InboundMinecraftConnection.java b/src/main/java/io/minimum/minecraft/velocity/proxy/InboundMinecraftConnection.java index 07b3e9e1b..2a784ff58 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/InboundMinecraftConnection.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/InboundMinecraftConnection.java @@ -1,10 +1,13 @@ package io.minimum.minecraft.velocity.proxy; import com.google.common.base.Preconditions; +import io.minimum.minecraft.velocity.data.ServerInfo; +import io.minimum.minecraft.velocity.protocol.ProtocolConstants; import io.minimum.minecraft.velocity.protocol.StateRegistry; import io.minimum.minecraft.velocity.protocol.netty.MinecraftDecoder; import io.minimum.minecraft.velocity.protocol.netty.MinecraftEncoder; import io.minimum.minecraft.velocity.protocol.packets.Handshake; +import io.minimum.minecraft.velocity.protocol.packets.ServerLoginSuccess; import io.minimum.minecraft.velocity.proxy.handler.HandshakeSessionHandler; import io.minimum.minecraft.velocity.proxy.handler.LoginSessionHandler; import io.minimum.minecraft.velocity.proxy.handler.StatusSessionHandler; @@ -13,6 +16,7 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.util.AttributeKey; +import java.net.InetSocketAddress; import java.util.Optional; public class InboundMinecraftConnection { @@ -82,7 +86,7 @@ public class InboundMinecraftConnection { Preconditions.checkState(!closed, "Connection is closed."); } - public void setStatus(StateRegistry state) { + private void setStatus(StateRegistry state) { Preconditions.checkNotNull(state, "state"); this.state = state; channel.pipeline().get(MinecraftEncoder.class).setState(state); @@ -100,4 +104,16 @@ public class InboundMinecraftConnection { public Optional getConnectedPlayer() { return Optional.ofNullable(connectedPlayer); } + + public int getProtocolVersion() { + return handshake == null ? ProtocolConstants.MINECRAFT_1_12 : handshake.getProtocolVersion(); + } + + public void initiatePlay(ServerLoginSuccess success) { + setStatus(StateRegistry.PLAY); + ConnectedPlayer player = new ConnectedPlayer(success.getUsername(), success.getUuid(), this); + ServerInfo info = new ServerInfo("test", new InetSocketAddress("127.0.0.1", 25565)); + ServerConnection connection = new ServerConnection(info, player, VelocityServer.getServer()); + connection.connect(); + } } diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/ServerConnection.java b/src/main/java/io/minimum/minecraft/velocity/proxy/ServerConnection.java index c76123737..cec4900ce 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/ServerConnection.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/ServerConnection.java @@ -2,7 +2,6 @@ package io.minimum.minecraft.velocity.proxy; import io.minimum.minecraft.velocity.protocol.MinecraftPacket; import io.minimum.minecraft.velocity.protocol.PacketWrapper; -import io.minimum.minecraft.velocity.protocol.ProtocolConstants; import io.minimum.minecraft.velocity.protocol.StateRegistry; import io.minimum.minecraft.velocity.data.ServerInfo; import io.minimum.minecraft.velocity.protocol.netty.MinecraftDecoder; @@ -13,9 +12,6 @@ import io.minimum.minecraft.velocity.protocol.packets.Handshake; import io.minimum.minecraft.velocity.protocol.packets.ServerLogin; import io.minimum.minecraft.velocity.protocol.packets.ServerLoginSuccess; import io.netty.channel.*; -import net.kyori.text.serializer.ComponentSerializers; - -import java.io.IOException; public class ServerConnection { private Channel channel; @@ -55,7 +51,7 @@ public class ServerConnection { // Initiate a handshake. Handshake handshake = new Handshake(); handshake.setNextStatus(2); // login - handshake.setProtocolVersion(ProtocolConstants.MINECRAFT_1_12); // TODO: Expose client version + handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion()); // TODO: Expose client version handshake.setServerAddress(info.getAddress().getHostString()); handshake.setPort(info.getAddress().getPort()); ctx.writeAndFlush(handshake, ctx.voidPromise()); @@ -77,6 +73,9 @@ public class ServerConnection { case LOGIN: onLogin(ctx, pw); break; + case PLAY: + onPlay(ctx, pw); + break; default: throw new UnsupportedOperationException("Unsupported state " + registry); } @@ -86,16 +85,23 @@ public class ServerConnection { } } + private void onPlay(ChannelHandlerContext ctx, PacketWrapper pw) { + proxyPlayer.getConnection().write(pw.getBuffer().retain()); + } + private void onLogin(ChannelHandlerContext ctx, PacketWrapper wrapper) { + //System.out.println("FROM PROXIED SERVER -> " + wrapper.getPacket() + " / " + ByteBufUtil.hexDump(wrapper.getBuffer())); MinecraftPacket packet = wrapper.getPacket(); if (packet instanceof Disconnect) { Disconnect disconnect = (Disconnect) packet; ctx.close(); - proxyPlayer.handleConnectionException(new IOException("Disconnected from target: " + jsonToPlain(disconnect.getReason()))); + proxyPlayer.handleConnectionException(disconnect); } if (packet instanceof ServerLoginSuccess) { - System.out.println("got it"); + // the player has been logged on. + System.out.println("Player connected to remote server"); + setRegistry(StateRegistry.PLAY); } } } @@ -105,10 +111,4 @@ public class ServerConnection { this.channel.pipeline().get(MinecraftEncoder.class).setState(registry); this.channel.pipeline().get(MinecraftDecoder.class).setState(registry); } - - private static String jsonToPlain(String j) { - return ComponentSerializers.LEGACY.serialize( - ComponentSerializers.JSON.deserialize(j) - ).replaceAll("\\u00A7[a-z0-9]", ""); - } } diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/handler/LoginSessionHandler.java b/src/main/java/io/minimum/minecraft/velocity/proxy/handler/LoginSessionHandler.java index 906995dca..47c9ad2ae 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/handler/LoginSessionHandler.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/handler/LoginSessionHandler.java @@ -3,7 +3,6 @@ package io.minimum.minecraft.velocity.proxy.handler; import com.google.common.base.Preconditions; import io.minimum.minecraft.velocity.data.ServerInfo; import io.minimum.minecraft.velocity.protocol.MinecraftPacket; -import io.minimum.minecraft.velocity.protocol.StateRegistry; import io.minimum.minecraft.velocity.protocol.packets.ServerLogin; import io.minimum.minecraft.velocity.protocol.packets.ServerLoginSuccess; import io.minimum.minecraft.velocity.proxy.*; @@ -30,13 +29,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler { success.setUuid(generateOfflinePlayerUuid(username)); connection.write(success); - connection.setStatus(StateRegistry.PLAY); - - ConnectedPlayer player = new ConnectedPlayer(username, generateOfflinePlayerUuid(username), connection); - - ServerInfo info = new ServerInfo("test", new InetSocketAddress("127.0.0.1", 25565)); - ServerConnection connection = new ServerConnection(info, player, VelocityServer.getServer()); - connection.connect(); + connection.initiatePlay(success); } private static UUID generateOfflinePlayerUuid(String username) {