From 9e397b10b5e2de74a289d04ee9894691904f8b70 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Tue, 24 Jul 2018 17:58:20 -0400 Subject: [PATCH] Progress towards a server connection pipeline. --- .../minimum/minecraft/velocity/Velocity.java | 35 +++--------- .../velocity/protocol/StateRegistry.java | 3 ++ .../protocol/netty/LegacyPingEncoder.java | 7 +++ .../netty/MinecraftPipelineUtils.java | 15 ++++++ .../netty/MinecraftVarintLengthEncoder.java | 6 +++ .../protocol/packets/ServerLoginSuccess.java | 49 +++++++++++++++++ .../velocity/proxy/ConnectedPlayer.java | 34 ++++++++++++ .../proxy/InboundMinecraftConnection.java | 8 ++- .../proxy/MinecraftClientSessionHandler.java | 6 +++ .../proxy/MinecraftSessionHandler.java | 4 ++ .../velocity/proxy/VelocityServer.java | 54 +++++++++++++++++++ .../proxy/handler/LoginSessionHandler.java | 21 +++++--- .../proxy/handler/PlaySessionHandler.java | 17 ++++++ .../proxy/server/ServerConnection.java | 19 +++++++ 14 files changed, 243 insertions(+), 35 deletions(-) create mode 100644 src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftPipelineUtils.java create mode 100644 src/main/java/io/minimum/minecraft/velocity/protocol/packets/ServerLoginSuccess.java create mode 100644 src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java create mode 100644 src/main/java/io/minimum/minecraft/velocity/proxy/VelocityServer.java create mode 100644 src/main/java/io/minimum/minecraft/velocity/proxy/handler/PlaySessionHandler.java create mode 100644 src/main/java/io/minimum/minecraft/velocity/proxy/server/ServerConnection.java diff --git a/src/main/java/io/minimum/minecraft/velocity/Velocity.java b/src/main/java/io/minimum/minecraft/velocity/Velocity.java index 06ff283a2..b6e87b456 100644 --- a/src/main/java/io/minimum/minecraft/velocity/Velocity.java +++ b/src/main/java/io/minimum/minecraft/velocity/Velocity.java @@ -1,34 +1,15 @@ package io.minimum.minecraft.velocity; -import io.minimum.minecraft.velocity.protocol.ProtocolConstants; -import io.minimum.minecraft.velocity.protocol.netty.*; -import io.minimum.minecraft.velocity.proxy.InboundMinecraftConnection; -import io.minimum.minecraft.velocity.proxy.MinecraftClientSessionHandler; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.minimum.minecraft.velocity.proxy.VelocityServer; public class Velocity { public static void main(String... args) throws InterruptedException { - new ServerBootstrap() - .channel(NioServerSocketChannel.class) - .group(new NioEventLoopGroup()) - .childHandler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.attr(InboundMinecraftConnection.CONNECTION).set(new InboundMinecraftConnection(ch)); - ch.pipeline().addLast("legacy-ping-decode", new LegacyPingDecoder()); - ch.pipeline().addLast("frame-decoder", new MinecraftVarintFrameDecoder()); - ch.pipeline().addLast("legacy-ping-encode", new LegacyPingEncoder()); - ch.pipeline().addLast("frame-encoder", new MinecraftVarintLengthEncoder()); - ch.pipeline().addLast("minecraft-decoder", new MinecraftDecoder(ProtocolConstants.Direction.TO_SERVER)); - ch.pipeline().addLast("minecraft-encoder", new MinecraftEncoder(ProtocolConstants.Direction.TO_CLIENT)); - ch.pipeline().addLast("handler", new MinecraftClientSessionHandler()); - } - }) - .bind(26671) - .await(); + VelocityServer server = new VelocityServer(); + server.initialize(); + + while (true) { + // temporary until jline is added. + Thread.sleep(999999); + } } } 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 b1ebabe5c..7b7e657e1 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/StateRegistry.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/StateRegistry.java @@ -27,7 +27,10 @@ public enum StateRegistry { LOGIN { { TO_SERVER.register(0x00, ServerLogin.class, ServerLogin::new); + 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); } }; diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/LegacyPingEncoder.java b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/LegacyPingEncoder.java index 313750ecd..1fffabdba 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/LegacyPingEncoder.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/LegacyPingEncoder.java @@ -2,16 +2,23 @@ package io.minimum.minecraft.velocity.protocol.netty; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; +import io.minimum.minecraft.velocity.protocol.packets.LegacyPing; import io.minimum.minecraft.velocity.protocol.packets.LegacyPingResponse; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; import java.nio.charset.StandardCharsets; import java.util.List; +@ChannelHandler.Sharable public class LegacyPingEncoder extends MessageToByteEncoder { + public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder(); + + private LegacyPingEncoder() {} + @Override protected void encode(ChannelHandlerContext ctx, LegacyPingResponse msg, ByteBuf out) throws Exception { out.writeByte(0xff); diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftPipelineUtils.java b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftPipelineUtils.java new file mode 100644 index 000000000..4b5c16b26 --- /dev/null +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftPipelineUtils.java @@ -0,0 +1,15 @@ +package io.minimum.minecraft.velocity.protocol.netty; + +import io.minimum.minecraft.velocity.protocol.ProtocolConstants; +import io.netty.channel.Channel; + +public class MinecraftPipelineUtils { + public static void strapPipeline(Channel ch) { + ch.pipeline().addLast("legacy-ping-decode", new LegacyPingDecoder()); + ch.pipeline().addLast("frame-decoder", new MinecraftVarintFrameDecoder()); + ch.pipeline().addLast("legacy-ping-encode", LegacyPingEncoder.INSTANCE); + ch.pipeline().addLast("frame-encoder", MinecraftVarintLengthEncoder.INSTANCE); + ch.pipeline().addLast("minecraft-decoder", new MinecraftDecoder(ProtocolConstants.Direction.TO_SERVER)); + ch.pipeline().addLast("minecraft-encoder", new MinecraftEncoder(ProtocolConstants.Direction.TO_CLIENT)); + } +} diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftVarintLengthEncoder.java b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftVarintLengthEncoder.java index 4d9e8a4c2..8505e4d97 100644 --- a/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftVarintLengthEncoder.java +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/netty/MinecraftVarintLengthEncoder.java @@ -2,10 +2,16 @@ package io.minimum.minecraft.velocity.protocol.netty; import io.minimum.minecraft.velocity.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; +@ChannelHandler.Sharable public class MinecraftVarintLengthEncoder extends MessageToByteEncoder { + public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder(); + + private MinecraftVarintLengthEncoder() { } + @Override protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { ProtocolUtils.writeVarInt(out, msg.readableBytes()); diff --git a/src/main/java/io/minimum/minecraft/velocity/protocol/packets/ServerLoginSuccess.java b/src/main/java/io/minimum/minecraft/velocity/protocol/packets/ServerLoginSuccess.java new file mode 100644 index 000000000..fff806a24 --- /dev/null +++ b/src/main/java/io/minimum/minecraft/velocity/protocol/packets/ServerLoginSuccess.java @@ -0,0 +1,49 @@ +package io.minimum.minecraft.velocity.protocol.packets; + +import io.minimum.minecraft.velocity.protocol.MinecraftPacket; +import io.minimum.minecraft.velocity.protocol.ProtocolConstants; +import io.minimum.minecraft.velocity.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +import java.util.UUID; + +public class ServerLoginSuccess implements MinecraftPacket { + private UUID uuid; + private String username; + + public UUID getUuid() { + return uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + @Override + public String toString() { + return "ServerLoginSuccess{" + + "uuid=" + uuid + + ", username='" + username + '\'' + + '}'; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + uuid = UUID.fromString(ProtocolUtils.readString(buf, 36)); + username = ProtocolUtils.readString(buf, 16); + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeString(buf, uuid.toString()); + ProtocolUtils.writeString(buf, username); + } +} diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java b/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java new file mode 100644 index 000000000..1c4d63b69 --- /dev/null +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/ConnectedPlayer.java @@ -0,0 +1,34 @@ +package io.minimum.minecraft.velocity.proxy; + +import io.minimum.minecraft.velocity.proxy.server.ServerConnection; + +import java.util.UUID; + +public class ConnectedPlayer { + private final String username; + private final UUID uniqueId; + private final InboundMinecraftConnection connection; + private ServerConnection connectedServer; + + public ConnectedPlayer(String username, UUID uniqueId, InboundMinecraftConnection connection) { + this.username = username; + this.uniqueId = uniqueId; + this.connection = connection; + } + + public String getUsername() { + return username; + } + + public UUID getUniqueId() { + return uniqueId; + } + + public InboundMinecraftConnection getConnection() { + return connection; + } + + public ServerConnection getConnectedServer() { + return connectedServer; + } +} 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 c7a4493fa..05a6e60d0 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/InboundMinecraftConnection.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/InboundMinecraftConnection.java @@ -66,10 +66,12 @@ public class InboundMinecraftConnection { this.setStatus(StateRegistry.STATUS); this.sessionHandler = new StatusSessionHandler(this); break; - default: + case 2: this.setStatus(StateRegistry.LOGIN); this.sessionHandler = new LoginSessionHandler(this); break; + default: + throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus()); } } @@ -83,4 +85,8 @@ public class InboundMinecraftConnection { channel.pipeline().get(MinecraftEncoder.class).setState(state); channel.pipeline().get(MinecraftDecoder.class).setState(state); } + + public void teardown() { + closed = true; + } } diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/MinecraftClientSessionHandler.java b/src/main/java/io/minimum/minecraft/velocity/proxy/MinecraftClientSessionHandler.java index 414f4808c..5ca14a851 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/MinecraftClientSessionHandler.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/MinecraftClientSessionHandler.java @@ -52,6 +52,12 @@ public class MinecraftClientSessionHandler extends ChannelInboundHandlerAdapter } } + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + InboundMinecraftConnection connection = ctx.channel().attr(InboundMinecraftConnection.CONNECTION).get(); + connection.teardown(); + } + @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/MinecraftSessionHandler.java b/src/main/java/io/minimum/minecraft/velocity/proxy/MinecraftSessionHandler.java index fe8dd42b3..2f6f7a907 100644 --- a/src/main/java/io/minimum/minecraft/velocity/proxy/MinecraftSessionHandler.java +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/MinecraftSessionHandler.java @@ -9,4 +9,8 @@ public interface MinecraftSessionHandler { default void handleUnknown(ByteBuf buf) { // No-op: we'll release the buffer later. } + + default void connectionClosed() { + + } } diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/VelocityServer.java b/src/main/java/io/minimum/minecraft/velocity/proxy/VelocityServer.java new file mode 100644 index 000000000..944f38fdf --- /dev/null +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/VelocityServer.java @@ -0,0 +1,54 @@ +package io.minimum.minecraft.velocity.proxy; + +import io.minimum.minecraft.velocity.protocol.ProtocolConstants; +import io.minimum.minecraft.velocity.protocol.netty.*; +import io.minimum.minecraft.velocity.proxy.server.ServerConnection; +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +public class VelocityServer { + private EventLoopGroup bossGroup; + private EventLoopGroup childGroup; + + public VelocityServer() { + + } + + public void initialize() { + bossGroup = new NioEventLoopGroup(); + childGroup = new NioEventLoopGroup(); + new ServerBootstrap() + .channel(NioServerSocketChannel.class) + .group(bossGroup, childGroup) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(Channel ch) throws Exception { + ch.attr(InboundMinecraftConnection.CONNECTION).set(new InboundMinecraftConnection(ch)); + MinecraftPipelineUtils.strapPipeline(ch); + ch.pipeline().addLast("handler", new MinecraftClientSessionHandler()); + } + }) + .bind(26671) + .addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + System.out.println("Listening on " + future.channel().localAddress()); + } else { + System.out.println("Can't bind to " + future.channel().localAddress()); + future.cause().printStackTrace(); + } + } + }); + } + + Bootstrap initializeGenericBootstrap() { + return new Bootstrap() + .channel(NioSocketChannel.class) + .group(childGroup); + } +} 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 57b4aa1f2..2bd70f046 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 @@ -2,12 +2,13 @@ package io.minimum.minecraft.velocity.proxy.handler; import com.google.common.base.Preconditions; import io.minimum.minecraft.velocity.protocol.MinecraftPacket; -import io.minimum.minecraft.velocity.protocol.packets.Disconnect; import io.minimum.minecraft.velocity.protocol.packets.ServerLogin; +import io.minimum.minecraft.velocity.protocol.packets.ServerLoginSuccess; import io.minimum.minecraft.velocity.proxy.InboundMinecraftConnection; import io.minimum.minecraft.velocity.proxy.MinecraftSessionHandler; -import net.kyori.text.TextComponent; -import net.kyori.text.serializer.ComponentSerializers; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; public class LoginSessionHandler implements MinecraftSessionHandler { private final InboundMinecraftConnection connection; @@ -20,9 +21,15 @@ public class LoginSessionHandler implements MinecraftSessionHandler { public void handle(MinecraftPacket packet) { Preconditions.checkArgument(packet instanceof ServerLogin, "Expected a ServerLogin packet, not " + packet.getClass().getName()); - // Disconnect with test message - Disconnect disconnect = new Disconnect(); - disconnect.setReason(ComponentSerializers.JSON.serialize(TextComponent.of("Hi there!"))); - connection.closeWith(disconnect); + String username = ((ServerLogin) packet).getUsername(); + ServerLoginSuccess success = new ServerLoginSuccess(); + success.setUsername(username); + success.setUuid(generateOfflinePlayerUuid(username)); + + connection.write(success); + } + + private static UUID generateOfflinePlayerUuid(String username) { + return UUID.nameUUIDFromBytes(username.getBytes(StandardCharsets.UTF_8)); } } diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/handler/PlaySessionHandler.java b/src/main/java/io/minimum/minecraft/velocity/proxy/handler/PlaySessionHandler.java new file mode 100644 index 000000000..ca71a76e6 --- /dev/null +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/handler/PlaySessionHandler.java @@ -0,0 +1,17 @@ +package io.minimum.minecraft.velocity.proxy.handler; + +import io.minimum.minecraft.velocity.protocol.MinecraftPacket; +import io.minimum.minecraft.velocity.proxy.MinecraftSessionHandler; +import io.netty.buffer.ByteBuf; + +public class PlaySessionHandler implements MinecraftSessionHandler { + @Override + public void handle(MinecraftPacket packet) { + + } + + @Override + public void handleUnknown(ByteBuf buf) { + + } +} diff --git a/src/main/java/io/minimum/minecraft/velocity/proxy/server/ServerConnection.java b/src/main/java/io/minimum/minecraft/velocity/proxy/server/ServerConnection.java new file mode 100644 index 000000000..5c93ebb06 --- /dev/null +++ b/src/main/java/io/minimum/minecraft/velocity/proxy/server/ServerConnection.java @@ -0,0 +1,19 @@ +package io.minimum.minecraft.velocity.proxy.server; + +import io.minimum.minecraft.velocity.protocol.StateRegistry; +import io.minimum.minecraft.velocity.proxy.ConnectedPlayer; +import io.netty.channel.Channel; + +public class ServerConnection { + private final Channel remoteServer; + private final ConnectedPlayer proxyPlayer; + private StateRegistry registry; + + public ServerConnection(Channel remoteServer, ConnectedPlayer proxyPlayer) { + this.remoteServer = remoteServer; + this.proxyPlayer = proxyPlayer; + this.registry = StateRegistry.HANDSHAKE; + } + + +}