Use package com.velocitypowered.proxy

This commit is contained in:
Andrew Steinborn
2018-07-25 19:47:59 -04:00
parent 4a2120f4d8
commit 034039a27d
45 changed files with 152 additions and 149 deletions

View File

@@ -0,0 +1,15 @@
package com.velocitypowered.proxy;
import com.velocitypowered.proxy.connection.VelocityServer;
public class Velocity {
public static void main(String... args) throws InterruptedException {
VelocityServer server = new VelocityServer();
server.initialize();
while (true) {
// temporary until jline is added.
Thread.sleep(999999);
}
}
}

View File

@@ -0,0 +1,158 @@
package com.velocitypowered.proxy.connection;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.PacketWrapper;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.compression.JavaVelocityCompressor;
import com.velocitypowered.proxy.protocol.netty.MinecraftCompressDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftCompressEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.packets.SetCompression;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
/**
* A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft
* protocol mechanics.
*/
public class MinecraftConnection extends ChannelInboundHandlerAdapter {
private final Channel channel;
private boolean closed;
private StateRegistry state;
private MinecraftSessionHandler sessionHandler;
private int protocolVersion;
public MinecraftConnection(Channel channel) {
this.channel = channel;
this.closed = false;
this.state = StateRegistry.HANDSHAKE;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) {
sessionHandler.connected();
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (sessionHandler != null) {
sessionHandler.disconnected();
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof PacketWrapper) {
PacketWrapper pw = (PacketWrapper) msg;
try {
if (sessionHandler != null) {
if (pw.getPacket() == null) {
sessionHandler.handleUnknown(pw.getBuffer());
} else {
sessionHandler.handle(pw.getPacket());
}
}
} finally {
ReferenceCountUtil.release(pw.getBuffer());
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (ctx.channel().isActive()) {
cause.printStackTrace();
if (sessionHandler != null) {
sessionHandler.exception(cause);
}
closed = true;
ctx.close();
}
}
public void write(Object msg) {
ensureOpen();
channel.writeAndFlush(msg, channel.voidPromise());
}
public void closeWith(Object msg) {
ensureOpen();
teardown();
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
}
public void close() {
ensureOpen();
teardown();
channel.close();
}
public void teardown() {
closed = true;
}
public Channel getChannel() {
return channel;
}
public boolean isClosed() {
return closed;
}
public StateRegistry getState() {
return state;
}
public void setState(StateRegistry state) {
this.state = state;
this.channel.pipeline().get(MinecraftEncoder.class).setState(state);
this.channel.pipeline().get(MinecraftDecoder.class).setState(state);
}
public int getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
this.channel.pipeline().get(MinecraftEncoder.class).setProtocolVersion(protocolVersion);
this.channel.pipeline().get(MinecraftDecoder.class).setProtocolVersion(protocolVersion);
}
public MinecraftSessionHandler getSessionHandler() {
return sessionHandler;
}
public void setSessionHandler(MinecraftSessionHandler sessionHandler) {
this.sessionHandler = sessionHandler;
}
private void ensureOpen() {
Preconditions.checkState(!closed, "Connection is closed.");
}
public void setCompressionThreshold(int threshold) {
channel.writeAndFlush(new SetCompression(threshold), channel.voidPromise());
if (threshold == -1) {
channel.pipeline().remove("compress-decoder");
channel.pipeline().remove("compress-encoder");
return;
}
JavaVelocityCompressor compressor = new JavaVelocityCompressor();
MinecraftCompressEncoder encoder = new MinecraftCompressEncoder(threshold, compressor);
MinecraftCompressDecoder decoder = new MinecraftCompressDecoder(threshold, compressor);
channel.pipeline().addBefore("minecraft-decoder", "compress-decoder", decoder);
channel.pipeline().addBefore("minecraft-encoder", "compress-encoder", encoder);
}
}

View File

@@ -0,0 +1,24 @@
package com.velocitypowered.proxy.connection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import io.netty.buffer.ByteBuf;
public interface MinecraftSessionHandler {
void handle(MinecraftPacket packet);
default void handleUnknown(ByteBuf buf) {
// No-op: we'll release the buffer later.
}
default void connected() {
}
default void disconnected() {
}
default void exception(Throwable throwable) {
}
}

View File

@@ -0,0 +1,64 @@
package com.velocitypowered.proxy.connection;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils;
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
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 static VelocityServer server;
private EventLoopGroup bossGroup;
private EventLoopGroup childGroup;
public VelocityServer() {
}
public static VelocityServer getServer() {
return server;
}
public void initialize() {
bossGroup = new NioEventLoopGroup();
childGroup = new NioEventLoopGroup();
server = this;
new ServerBootstrap()
.channel(NioServerSocketChannel.class)
.group(bossGroup, childGroup)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
MinecraftPipelineUtils.strapPipelineForServer(ch);
MinecraftConnection connection = new MinecraftConnection(ch);
connection.setState(StateRegistry.HANDSHAKE);
connection.setSessionHandler(new HandshakeSessionHandler(connection));
ch.pipeline().addLast("handler", connection);
}
})
.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();
}
}
});
}
public Bootstrap initializeGenericBootstrap() {
return new Bootstrap()
.channel(NioSocketChannel.class)
.group(childGroup);
}
}

View File

@@ -0,0 +1,40 @@
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packets.Disconnect;
import com.velocitypowered.proxy.protocol.packets.ServerLoginSuccess;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.packets.SetCompression;
public class LoginSessionHandler implements MinecraftSessionHandler {
private final ServerConnection connection;
public LoginSessionHandler(ServerConnection connection) {
this.connection = connection;
}
@Override
public void handle(MinecraftPacket packet) {
if (packet instanceof Disconnect) {
Disconnect disconnect = (Disconnect) packet;
connection.disconnect();
connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), disconnect);
}
if (packet instanceof SetCompression) {
System.out.println("Enabling compression on server connection, this is inefficient!");
SetCompression sc = (SetCompression) packet;
connection.getChannel().setCompressionThreshold(sc.getThreshold());
}
if (packet instanceof ServerLoginSuccess) {
// the player has been logged on.
System.out.println("Player connected to remote server");
connection.getChannel().setState(StateRegistry.PLAY);
connection.getProxyPlayer().setConnectedServer(connection);
connection.getProxyPlayer().getConnection().setSessionHandler(new com.velocitypowered.proxy.connection.client.PlaySessionHandler(connection.getProxyPlayer()));
connection.getChannel().setSessionHandler(new PlaySessionHandler(connection));
}
}
}

View File

@@ -0,0 +1,45 @@
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packets.Disconnect;
import com.velocitypowered.proxy.protocol.packets.Ping;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf;
import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor;
import net.kyori.text.serializer.ComponentSerializers;
public class PlaySessionHandler implements MinecraftSessionHandler {
private final ServerConnection connection;
public PlaySessionHandler(ServerConnection connection) {
this.connection = connection;
}
@Override
public void handle(MinecraftPacket packet) {
if (packet instanceof Ping) {
// Make sure to reply back to the server so it doesn't think we're gone.
connection.getChannel().write(packet);
connection.getProxyPlayer().getConnection().write(packet);
} else if (packet instanceof Disconnect) {
// The server wants to disconnect us. TODO fallback handling
Disconnect original = (Disconnect) packet;
TextComponent reason = TextComponent.builder()
.content("Disconnected from " + connection.getServerInfo().getName() + ":")
.color(TextColor.RED)
.append(TextComponent.of(" ", TextColor.WHITE))
.append(ComponentSerializers.JSON.deserialize(original.getReason()))
.build();
connection.getProxyPlayer().close(reason);
} else {
// Just forward the packet on. We don't have anything to handle at this time.
connection.getProxyPlayer().getConnection().write(packet);
}
}
@Override
public void handleUnknown(ByteBuf buf) {
connection.getProxyPlayer().getConnection().write(buf.retain());
}
}

View File

@@ -0,0 +1,87 @@
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.proxy.protocol.packets.Handshake;
import com.velocitypowered.proxy.protocol.packets.ServerLogin;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.data.ServerInfo;
import com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils;
import com.velocitypowered.proxy.connection.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import io.netty.channel.*;
public class ServerConnection {
private final ServerInfo serverInfo;
private final ConnectedPlayer proxyPlayer;
private final VelocityServer server;
private MinecraftConnection channel;
public ServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) {
this.serverInfo = target;
this.proxyPlayer = proxyPlayer;
this.server = server;
}
public void connect() {
server.initializeGenericBootstrap()
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
MinecraftPipelineUtils.strapPipelineForProxy(ch);
MinecraftConnection connection = new MinecraftConnection(ch);
connection.setState(StateRegistry.HANDSHAKE);
connection.setSessionHandler(new LoginSessionHandler(ServerConnection.this));
ch.pipeline().addLast("handler", connection);
}
})
.connect(serverInfo.getAddress())
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
channel = future.channel().pipeline().get(MinecraftConnection.class);
// Kick off the connection process
startHandshake();
} else {
proxyPlayer.handleConnectionException(serverInfo, future.cause());
}
}
});
}
private void startHandshake() {
// Initiate a handshake.
Handshake handshake = new Handshake();
handshake.setNextStatus(2); // login
handshake.setProtocolVersion(proxyPlayer.getConnection().getProtocolVersion());
handshake.setServerAddress(serverInfo.getAddress().getHostString());
handshake.setPort(serverInfo.getAddress().getPort());
channel.write(handshake);
channel.setState(StateRegistry.LOGIN);
// Login
ServerLogin login = new ServerLogin();
login.setUsername(proxyPlayer.getUsername());
channel.write(login);
}
public ConnectedPlayer getProxyPlayer() {
return proxyPlayer;
}
public MinecraftConnection getChannel() {
return channel;
}
public ServerInfo getServerInfo() {
return serverInfo;
}
public void disconnect() {
channel.close();
channel = null;
}
}

View File

@@ -0,0 +1,78 @@
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.proxy.protocol.packets.Chat;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.ServerConnection;
import com.velocitypowered.proxy.util.ThrowableUtils;
import com.velocitypowered.proxy.data.ServerInfo;
import com.velocitypowered.proxy.protocol.packets.Disconnect;
import net.kyori.text.TextComponent;
import net.kyori.text.format.TextColor;
import net.kyori.text.serializer.ComponentSerializers;
import java.util.UUID;
public class ConnectedPlayer {
private final String username;
private final UUID uniqueId;
private final MinecraftConnection connection;
private ServerConnection connectedServer;
public ConnectedPlayer(String username, UUID uniqueId, MinecraftConnection connection) {
this.username = username;
this.uniqueId = uniqueId;
this.connection = connection;
}
public String getUsername() {
return username;
}
public UUID getUniqueId() {
return uniqueId;
}
public MinecraftConnection getConnection() {
return connection;
}
public ServerConnection getConnectedServer() {
return connectedServer;
}
public void handleConnectionException(ServerInfo info, Throwable throwable) {
String error = ThrowableUtils.briefDescription(throwable);
Disconnect disconnect = new Disconnect();
disconnect.setReason(ComponentSerializers.JSON.serialize(TextComponent.of(error, TextColor.RED)));
handleConnectionException(info, disconnect);
}
public void handleConnectionException(ServerInfo info, Disconnect disconnect) {
TextComponent component = TextComponent.builder()
.content("Exception connecting to server " + info.getName() + ": ")
.color(TextColor.RED)
.append(ComponentSerializers.JSON.deserialize(disconnect.getReason()))
.build();
if (connectedServer == null) {
// The player isn't yet connected to a server - we should disconnect them.
Disconnect d = new Disconnect();
d.setReason(ComponentSerializers.JSON.serialize(component));
connection.closeWith(d);
} else {
Chat chat = new Chat();
chat.setMessage(ComponentSerializers.JSON.serialize(component));
connection.write(chat);
}
}
public void setConnectedServer(ServerConnection serverConnection) {
this.connectedServer = serverConnection;
}
public void close(TextComponent reason) {
Disconnect disconnect = new Disconnect();
disconnect.setReason(ComponentSerializers.JSON.serialize(reason));
connection.closeWith(disconnect);
}
}

View File

@@ -0,0 +1,40 @@
package com.velocitypowered.proxy.connection.client;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packets.Handshake;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
public class HandshakeSessionHandler implements MinecraftSessionHandler {
private final MinecraftConnection connection;
public HandshakeSessionHandler(MinecraftConnection connection) {
this.connection = Preconditions.checkNotNull(connection, "connection");
}
@Override
public void handle(MinecraftPacket packet) {
if (!(packet instanceof Handshake)) {
throw new IllegalArgumentException("Did not expect packet " + packet.getClass().getName());
}
Handshake handshake = (Handshake) packet;
connection.setProtocolVersion(handshake.getProtocolVersion());
switch (handshake.getNextStatus()) {
case 1:
// Status protocol
connection.setState(StateRegistry.STATUS);
connection.setSessionHandler(new StatusSessionHandler(connection));
break;
case 2:
connection.setState(StateRegistry.LOGIN);
connection.setSessionHandler(new LoginSessionHandler(connection));
break;
default:
throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus());
}
}
}

View File

@@ -0,0 +1,23 @@
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
public class InitialConnectSessionHandler implements MinecraftSessionHandler {
private final ConnectedPlayer player;
public InitialConnectSessionHandler(ConnectedPlayer player) {
this.player = player;
}
@Override
public void handle(MinecraftPacket packet) {
// No-op: will never handle packets
}
@Override
public void disconnected() {
// the user cancelled the login process
player.getConnectedServer().disconnect();
}
}

View File

@@ -0,0 +1,51 @@
package com.velocitypowered.proxy.connection.client;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packets.ServerLogin;
import com.velocitypowered.proxy.protocol.packets.ServerLoginSuccess;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.VelocityServer;
import com.velocitypowered.proxy.connection.backend.ServerConnection;
import com.velocitypowered.proxy.data.ServerInfo;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public class LoginSessionHandler implements MinecraftSessionHandler {
private final MinecraftConnection inbound;
public LoginSessionHandler(MinecraftConnection inbound) {
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
}
@Override
public void handle(MinecraftPacket packet) {
Preconditions.checkArgument(packet instanceof ServerLogin, "Expected a ServerLogin packet, not " + packet.getClass().getName());
// TODO: Encryption
inbound.setCompressionThreshold(256);
String username = ((ServerLogin) packet).getUsername();
ServerLoginSuccess success = new ServerLoginSuccess();
success.setUsername(username);
success.setUuid(generateOfflinePlayerUuid(username));
inbound.write(success);
// Initiate a regular connection and move over to it.
ConnectedPlayer player = new ConnectedPlayer(success.getUsername(), success.getUuid(), inbound);
ServerInfo info = new ServerInfo("test", new InetSocketAddress("localhost", 25565));
ServerConnection connection = new ServerConnection(info, player, VelocityServer.getServer());
inbound.setState(StateRegistry.PLAY);
inbound.setSessionHandler(new InitialConnectSessionHandler(player));
connection.connect();
}
private static UUID generateOfflinePlayerUuid(String username) {
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
}
}

View File

@@ -0,0 +1,36 @@
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packets.Ping;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf;
public class PlaySessionHandler implements MinecraftSessionHandler {
private final ConnectedPlayer player;
public PlaySessionHandler(ConnectedPlayer player) {
this.player = player;
}
@Override
public void handle(MinecraftPacket packet) {
if (packet instanceof Ping) {
// Handle the ping.
player.getConnection().write(packet);
return;
}
// If we don't want to handle this packet, just forward it on.
player.getConnectedServer().getChannel().write(packet);
}
@Override
public void handleUnknown(ByteBuf buf) {
player.getConnectedServer().getChannel().write(buf.retain());
}
@Override
public void disconnected() {
player.getConnectedServer().disconnect();
}
}

View File

@@ -0,0 +1,50 @@
package com.velocitypowered.proxy.connection.client;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packets.Ping;
import com.velocitypowered.proxy.protocol.packets.StatusRequest;
import com.velocitypowered.proxy.protocol.packets.StatusResponse;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.data.ServerPing;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.serializer.GsonComponentSerializer;
public class StatusSessionHandler implements MinecraftSessionHandler {
private static final Gson GSON = new GsonBuilder()
.registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
.create();
private final MinecraftConnection connection;
public StatusSessionHandler(MinecraftConnection connection) {
this.connection = connection;
}
@Override
public void handle(MinecraftPacket packet) {
Preconditions.checkArgument(packet instanceof Ping || packet instanceof StatusRequest,
"Unrecognized packet type " + packet.getClass().getName());
if (packet instanceof Ping) {
// Just send back the client's packet, no processing to do here.
connection.closeWith(packet);
return;
}
// Status request
System.out.println("Got status request!");
ServerPing ping = new ServerPing(
new ServerPing.Version(340, "1.12.2"),
new ServerPing.Players(0, 0),
TextComponent.of("test"),
null
);
StatusResponse response = new StatusResponse();
response.setStatus(GSON.toJson(ping));
connection.write(response);
}
}

View File

@@ -0,0 +1,44 @@
package com.velocitypowered.proxy.data;
import java.net.InetSocketAddress;
import java.util.Objects;
public class ServerInfo {
private final String name;
private final InetSocketAddress address;
public ServerInfo(String name, InetSocketAddress address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public InetSocketAddress getAddress() {
return address;
}
@Override
public String toString() {
return "ServerInfo{" +
"name='" + name + '\'' +
", address=" + address +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ServerInfo that = (ServerInfo) o;
return Objects.equals(name, that.name) &&
Objects.equals(address, that.address);
}
@Override
public int hashCode() {
return Objects.hash(name, address);
}
}

View File

@@ -0,0 +1,95 @@
package com.velocitypowered.proxy.data;
import net.kyori.text.Component;
public class ServerPing {
private final Version version;
private final Players players;
private final Component description;
private final String favicon;
public ServerPing(Version version, Players players, Component description, String favicon) {
this.version = version;
this.players = players;
this.description = description;
this.favicon = favicon;
}
public Version getVersion() {
return version;
}
public Players getPlayers() {
return players;
}
public Component getDescription() {
return description;
}
public String getFavicon() {
return favicon;
}
@Override
public String toString() {
return "ServerPing{" +
"version=" + version +
", players=" + players +
", description=" + description +
", favicon='" + favicon + '\'' +
'}';
}
public static class Version {
private final int protocol;
private final String name;
public Version(int protocol, String name) {
this.protocol = protocol;
this.name = name;
}
public int getProtocol() {
return protocol;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Version{" +
"protocol=" + protocol +
", name='" + name + '\'' +
'}';
}
}
public static class Players {
private final int online;
private final int max;
public Players(int online, int max) {
this.online = online;
this.max = max;
}
public int getOnline() {
return online;
}
public int getMax() {
return max;
}
@Override
public String toString() {
return "Players{" +
"online=" + online +
", max=" + max +
'}';
}
}
}

View File

@@ -0,0 +1,9 @@
package com.velocitypowered.proxy.protocol;
import io.netty.buffer.ByteBuf;
public interface MinecraftPacket {
void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion);
void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion);
}

View File

@@ -0,0 +1,30 @@
package com.velocitypowered.proxy.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
public class PacketWrapper {
private final MinecraftPacket packet;
private final ByteBuf buffer;
public PacketWrapper(MinecraftPacket packet, ByteBuf buffer) {
this.packet = packet;
this.buffer = buffer;
}
public MinecraftPacket getPacket() {
return packet;
}
public ByteBuf getBuffer() {
return buffer;
}
@Override
public String toString() {
return "PacketWrapper{" +
"packet=" + packet +
", buffer=" + ByteBufUtil.hexDump(buffer) +
'}';
}
}

View File

@@ -0,0 +1,10 @@
package com.velocitypowered.proxy.protocol;
public enum ProtocolConstants { ;
public static final int MINECRAFT_1_12 = 340;
public enum Direction {
TO_SERVER,
TO_CLIENT
}
}

View File

@@ -0,0 +1,52 @@
package com.velocitypowered.proxy.protocol;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import java.nio.charset.StandardCharsets;
public enum ProtocolUtils { ;
private static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB
public static int readVarInt(ByteBuf buf) {
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) {
while (true) {
if ((value & 0xFFFFFF80) == 0) {
buf.writeByte(value);
return;
}
buf.writeByte(value & 0x7F | 0x80);
value >>>= 7;
}
}
public static String readString(ByteBuf buf) {
return readString(buf, DEFAULT_MAX_STRING_SIZE);
}
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);
byte[] str = new byte[length];
buf.readBytes(str);
return new String(str, StandardCharsets.UTF_8);
}
public static void writeString(ByteBuf buf, String str) {
byte[] asUtf8 = str.getBytes(StandardCharsets.UTF_8);
writeVarInt(buf, asUtf8.length);
buf.writeBytes(asUtf8);
}
}

View File

@@ -0,0 +1,80 @@
package com.velocitypowered.proxy.protocol;
import com.velocitypowered.proxy.protocol.packets.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public enum StateRegistry {
HANDSHAKE {
{
TO_SERVER.register(0x00, Handshake.class, Handshake::new);
}
},
STATUS {
{
TO_SERVER.register(0x00, StatusRequest.class, StatusRequest::new);
TO_SERVER.register(0x01, Ping.class, Ping::new);
TO_CLIENT.register(0x00, StatusResponse.class, StatusResponse::new);
TO_CLIENT.register(0x01, Ping.class, Ping::new);
}
},
PLAY {
{
TO_SERVER.register(0x02, Chat.class, Chat::new);
TO_SERVER.register(0x0b, Ping.class, Ping::new);
TO_CLIENT.register(0x0F, Chat.class, Chat::new);
TO_CLIENT.register(0x1A, Disconnect.class, Disconnect::new);
TO_CLIENT.register(0x1F, Ping.class, Ping::new);
}
},
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, ServerLoginSuccess::new);
TO_CLIENT.register(0x03, SetCompression.class, SetCompression::new);
}
};
public final ProtocolMappings TO_CLIENT = new ProtocolMappings(ProtocolConstants.Direction.TO_CLIENT, this);
public final ProtocolMappings TO_SERVER = new ProtocolMappings(ProtocolConstants.Direction.TO_SERVER, this);
public static class ProtocolMappings {
private final ProtocolConstants.Direction direction;
private final StateRegistry state;
private final Map<Integer, Supplier<? extends MinecraftPacket>> idsToSuppliers = new HashMap<>();
private final Map<Class<? extends MinecraftPacket>, Integer> packetClassesToIds = new HashMap<>();
public ProtocolMappings(ProtocolConstants.Direction direction, StateRegistry state) {
this.direction = direction;
this.state = state;
}
public <P extends MinecraftPacket> void register(int id, Class<P> clazz, Supplier<P> packetSupplier) {
idsToSuppliers.put(id, packetSupplier);
packetClassesToIds.put(clazz, id);
}
public MinecraftPacket createPacket(int id) {
Supplier<? extends MinecraftPacket> supplier = idsToSuppliers.get(id);
if (supplier == null) {
return null;
}
return supplier.get();
}
public int getId(MinecraftPacket packet) {
Integer id = packetClassesToIds.get(packet.getClass());
if (id == null) {
throw new IllegalArgumentException("Supplied packet " + packet.getClass().getName() + " doesn't have a mapping. Direction " + direction + " State " + state);
}
return id;
}
}
}

View File

@@ -0,0 +1,60 @@
package com.velocitypowered.proxy.protocol.compression;
import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
public class JavaVelocityCompressor implements VelocityCompressor {
private final Deflater deflater;
private final Inflater inflater;
private final byte[] buf;
private boolean disposed = false;
public JavaVelocityCompressor() {
this.deflater = new Deflater();
this.inflater = new Inflater();
this.buf = new byte[8192];
}
@Override
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed();
byte[] inData = new byte[source.readableBytes()];
source.readBytes(inData);
inflater.setInput(inData);
while (!inflater.finished()) {
int read = inflater.inflate(buf);
destination.writeBytes(buf, 0, read);
}
inflater.reset();
}
@Override
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed();
byte[] inData = new byte[source.readableBytes()];
source.readBytes(inData);
deflater.setInput(inData);
deflater.finish();
while (!deflater.finished()) {
int bytes = deflater.deflate(buf);
destination.writeBytes(buf, 0, bytes);
}
deflater.reset();
}
@Override
public void dispose() {
ensureNotDisposed();
disposed = true;
}
private void ensureNotDisposed() {
Preconditions.checkState(!disposed, "Object already disposed");
}
}

View File

@@ -0,0 +1,13 @@
package com.velocitypowered.proxy.protocol.compression;
import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException;
public interface VelocityCompressor {
void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
void dispose();
}

View File

@@ -0,0 +1,26 @@
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.protocol.packets.LegacyPing;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class LegacyPingDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 2) {
return;
}
short first = in.getUnsignedByte(in.readerIndex());
short second = in.getUnsignedByte(in.readerIndex() + 1);
if (first == 0xfe && second == 0x01) {
in.skipBytes(in.readableBytes());
out.add(new LegacyPing());
}
ctx.pipeline().remove(this);
}
}

View File

@@ -0,0 +1,41 @@
package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.proxy.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<LegacyPingResponse> {
public static final LegacyPingEncoder INSTANCE = new LegacyPingEncoder();
private LegacyPingEncoder() {}
@Override
protected void encode(ChannelHandlerContext ctx, LegacyPingResponse msg, ByteBuf out) throws Exception {
out.writeByte(0xff);
String serializedResponse = serialize(msg);
out.writeShort(serializedResponse.length());
out.writeBytes(serializedResponse.getBytes(StandardCharsets.UTF_16BE));
System.out.println(ByteBufUtil.prettyHexDump(out));
}
private String serialize(LegacyPingResponse response) {
List<String> parts = ImmutableList.of(
"§1",
Integer.toString(response.getProtocolVersion()),
response.getServerVersion(),
response.getMotd(),
Integer.toString(response.getPlayersOnline()),
Integer.toString(response.getPlayersMax())
);
return Joiner.on('\0').join(parts);
}
}

View File

@@ -0,0 +1,45 @@
package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.compression.VelocityCompressor;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import java.util.List;
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
private static final int MAXIMUM_INITIAL_BUFFER_SIZE = 65536; // 64KiB
private final int threshold;
private final VelocityCompressor compressor;
public MinecraftCompressDecoder(int threshold, VelocityCompressor compressor) {
this.threshold = threshold;
this.compressor = compressor;
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
int uncompressedSize = ProtocolUtils.readVarInt(msg);
if (uncompressedSize == 0) {
// Strip the now-useless uncompressed size, this message is already uncompressed.
out.add(msg.slice().retain());
msg.skipBytes(msg.readableBytes());
return;
}
Preconditions.checkState(uncompressedSize >= threshold, "Uncompressed size %s doesn't make sense with threshold %s", uncompressedSize, threshold);
ByteBuf uncompressed = ctx.alloc().buffer(Math.min(uncompressedSize, MAXIMUM_INITIAL_BUFFER_SIZE));
try {
compressor.inflate(msg, uncompressed);
Preconditions.checkState(uncompressedSize == uncompressed.readableBytes(), "Mismatched compression sizes");
out.add(uncompressed);
} catch (Exception e) {
// If something went wrong, rethrow the exception, but ensure we free our temporary buffer first.
uncompressed.release();
throw e;
}
}
}

View File

@@ -0,0 +1,37 @@
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.compression.VelocityCompressor;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
private final int threshold;
private final VelocityCompressor compressor;
public MinecraftCompressEncoder(int threshold, VelocityCompressor compressor) {
this.threshold = threshold;
this.compressor = compressor;
}
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
if (msg.readableBytes() <= threshold) {
// Under the threshold, there is nothing to do.
ProtocolUtils.writeVarInt(out, 0);
out.writeBytes(msg);
return;
}
ByteBuf compressedBuffer = ctx.alloc().buffer();
try {
int uncompressed = msg.readableBytes();
compressor.deflate(msg, compressedBuffer);
ProtocolUtils.writeVarInt(out, uncompressed);
out.writeBytes(compressedBuffer);
} finally {
compressedBuffer.release();
}
}
}

View File

@@ -0,0 +1,67 @@
package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.*;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.MessageToMessageDecoder;
import java.util.List;
public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf> {
private StateRegistry state;
private final ProtocolConstants.Direction direction;
private int protocolVersion;
public MinecraftDecoder(ProtocolConstants.Direction direction) {
this.state = StateRegistry.HANDSHAKE;
this.direction = Preconditions.checkNotNull(direction, "direction");
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
if (!msg.isReadable()) {
return;
}
ByteBuf slice = msg.slice().retain();
int packetId = ProtocolUtils.readVarInt(msg);
StateRegistry.ProtocolMappings mappings = direction == ProtocolConstants.Direction.TO_CLIENT ? state.TO_CLIENT : state.TO_SERVER;
MinecraftPacket packet = mappings.createPacket(packetId);
if (packet == null) {
msg.skipBytes(msg.readableBytes());
out.add(new PacketWrapper(null, slice));
} else {
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);
}
System.out.println("IN: " + packet);
out.add(new PacketWrapper(packet, slice));
}
}
public int getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public StateRegistry getState() {
return state;
}
public void setState(StateRegistry state) {
this.state = state;
}
public ProtocolConstants.Direction getDirection() {
return direction;
}
}

View File

@@ -0,0 +1,50 @@
package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
private StateRegistry state;
private final ProtocolConstants.Direction direction;
private int protocolVersion;
public MinecraftEncoder(ProtocolConstants.Direction direction) {
this.state = StateRegistry.HANDSHAKE;
this.direction = Preconditions.checkNotNull(direction, "direction");
}
@Override
protected void encode(ChannelHandlerContext ctx, MinecraftPacket msg, ByteBuf out) throws Exception {
StateRegistry.ProtocolMappings mappings = direction == ProtocolConstants.Direction.TO_CLIENT ? state.TO_CLIENT : state.TO_SERVER;
int packetId = mappings.getId(msg);
ProtocolUtils.writeVarInt(out, packetId);
msg.encode(out, direction, protocolVersion);
System.out.println("OUT: " + msg);
}
public int getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public StateRegistry getState() {
return state;
}
public void setState(StateRegistry state) {
this.state = state;
}
public ProtocolConstants.Direction getDirection() {
return direction;
}
}

View File

@@ -0,0 +1,29 @@
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import io.netty.channel.Channel;
import io.netty.handler.timeout.ReadTimeoutHandler;
import java.util.concurrent.TimeUnit;
public class MinecraftPipelineUtils {
public static void strapPipelineForServer(Channel ch) {
ch.pipeline().addLast("read-timeout", new ReadTimeoutHandler(30, TimeUnit.SECONDS));
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));
}
public static void strapPipelineForProxy(Channel ch) {
ch.pipeline().addLast("read-timeout", new ReadTimeoutHandler(30, TimeUnit.SECONDS));
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_CLIENT));
ch.pipeline().addLast("minecraft-encoder", new MinecraftEncoder(ProtocolConstants.Direction.TO_SERVER));
}
}

View File

@@ -0,0 +1,27 @@
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.readableBytes() < 1) {
return;
}
in.markReaderIndex();
int packetLength = ProtocolUtils.readVarInt(in);
if (in.readableBytes() < packetLength) {
in.resetReaderIndex();
return;
}
out.add(in.slice(in.readerIndex(), packetLength).retain());
in.skipBytes(packetLength);
}
}

View File

@@ -0,0 +1,20 @@
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.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<ByteBuf> {
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());
out.writeBytes(msg);
}
}

View File

@@ -0,0 +1,51 @@
package com.velocitypowered.proxy.protocol.packets;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class Chat implements MinecraftPacket {
private String message;
private byte position;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public byte getPosition() {
return position;
}
public void setPosition(byte position) {
this.position = position;
}
@Override
public String toString() {
return "Chat{" +
"message='" + message + '\'' +
", position=" + position +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
message = ProtocolUtils.readString(buf);
if (direction == ProtocolConstants.Direction.TO_CLIENT) {
position = buf.readByte();
}
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeString(buf, message);
if (direction == ProtocolConstants.Direction.TO_CLIENT) {
buf.writeByte(position);
}
}
}

View File

@@ -0,0 +1,35 @@
package com.velocitypowered.proxy.protocol.packets;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class Disconnect implements MinecraftPacket {
private String reason;
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
@Override
public String toString() {
return "Disconnect{" +
"reason='" + reason + '\'' +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
reason = ProtocolUtils.readString(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeString(buf, reason);
}
}

View File

@@ -0,0 +1,71 @@
package com.velocitypowered.proxy.protocol.packets;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class Handshake implements MinecraftPacket {
private int protocolVersion;
private String serverAddress;
private int port;
private int nextStatus;
public int getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public String getServerAddress() {
return serverAddress;
}
public void setServerAddress(String serverAddress) {
this.serverAddress = serverAddress;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getNextStatus() {
return nextStatus;
}
public void setNextStatus(int nextStatus) {
this.nextStatus = nextStatus;
}
@Override
public String toString() {
return "Handshake{" +
"protocolVersion=" + protocolVersion +
", serverAddress='" + serverAddress + '\'' +
", port=" + port +
", nextStatus=" + nextStatus +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
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, this.protocolVersion);
ProtocolUtils.writeString(buf, this.serverAddress);
buf.writeShort(this.port);
ProtocolUtils.writeVarInt(buf, this.nextStatus);
}
}

View File

@@ -0,0 +1,4 @@
package com.velocitypowered.proxy.protocol.packets;
public class LegacyPing {
}

View File

@@ -0,0 +1,82 @@
package com.velocitypowered.proxy.protocol.packets;
import com.velocitypowered.proxy.data.ServerPing;
import net.kyori.text.serializer.ComponentSerializers;
public class LegacyPingResponse {
private int protocolVersion;
private String serverVersion;
private String motd;
private int playersOnline;
private int playersMax;
public LegacyPingResponse() {
}
public LegacyPingResponse(int protocolVersion, String serverVersion, String motd, int playersOnline, int playersMax) {
this.protocolVersion = protocolVersion;
this.serverVersion = serverVersion;
this.motd = motd;
this.playersOnline = playersOnline;
this.playersMax = playersMax;
}
public int getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(int protocolVersion) {
this.protocolVersion = protocolVersion;
}
public String getServerVersion() {
return serverVersion;
}
public void setServerVersion(String serverVersion) {
this.serverVersion = serverVersion;
}
public String getMotd() {
return motd;
}
public void setMotd(String motd) {
this.motd = motd;
}
public int getPlayersOnline() {
return playersOnline;
}
public void setPlayersOnline(int playersOnline) {
this.playersOnline = playersOnline;
}
public int getPlayersMax() {
return playersMax;
}
public void setPlayersMax(int playersMax) {
this.playersMax = playersMax;
}
@Override
public String toString() {
return "LegacyPingResponse{" +
"protocolVersion=" + protocolVersion +
", serverVersion='" + serverVersion + '\'' +
", motd='" + motd + '\'' +
", playersOnline=" + playersOnline +
", playersMax=" + playersMax +
'}';
}
public static LegacyPingResponse from(ServerPing ping) {
return new LegacyPingResponse(ping.getVersion().getProtocol(),
ping.getVersion().getName(),
ComponentSerializers.LEGACY.serialize(ping.getDescription()),
ping.getPlayers().getOnline(),
ping.getPlayers().getMax());
}
}

View File

@@ -0,0 +1,34 @@
package com.velocitypowered.proxy.protocol.packets;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import io.netty.buffer.ByteBuf;
public class Ping implements MinecraftPacket {
private long randomId;
public long getRandomId() {
return randomId;
}
public void setRandomId(long randomId) {
this.randomId = randomId;
}
@Override
public String toString() {
return "Ping{" +
"randomId=" + randomId +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
randomId = buf.readLong();
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
buf.writeLong(randomId);
}
}

View File

@@ -0,0 +1,35 @@
package com.velocitypowered.proxy.protocol.packets;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class ServerLogin implements MinecraftPacket {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "ServerLogin{" +
"username='" + username + '\'' +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
username = ProtocolUtils.readString(buf, 16);
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeString(buf, username);
}
}

View File

@@ -0,0 +1,49 @@
package com.velocitypowered.proxy.protocol.packets;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.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);
}
}

View File

@@ -0,0 +1,41 @@
package com.velocitypowered.proxy.protocol.packets;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class SetCompression implements MinecraftPacket {
private int threshold;
public SetCompression() {}
public SetCompression(int threshold) {
this.threshold = threshold;
}
public int getThreshold() {
return threshold;
}
public void setThreshold(int threshold) {
this.threshold = threshold;
}
@Override
public String toString() {
return "SetCompression{" +
"threshold=" + threshold +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
this.threshold = ProtocolUtils.readVarInt(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeVarInt(buf, threshold);
}
}

View File

@@ -0,0 +1,17 @@
package com.velocitypowered.proxy.protocol.packets;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import io.netty.buffer.ByteBuf;
public class StatusRequest implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
}
}

View File

@@ -0,0 +1,35 @@
package com.velocitypowered.proxy.protocol.packets;
import com.velocitypowered.proxy.protocol.ProtocolConstants;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class StatusResponse implements MinecraftPacket {
private String status;
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "StatusResponse{" +
"status='" + status + '\'' +
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
status = ProtocolUtils.readString(buf, Short.MAX_VALUE);
}
@Override
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
ProtocolUtils.writeString(buf, status);
}
}

View File

@@ -0,0 +1,9 @@
package com.velocitypowered.proxy.util;
public enum ThrowableUtils {
;
public static String briefDescription(Throwable throwable) {
return throwable.getClass().getName() + ": " + throwable.getMessage();
}
}