diff --git a/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index 1663fe226..7abd117f7 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -40,6 +40,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { break; } connection.getProxyPlayer().getConnection().write(packet); + } else if (packet instanceof PluginMessage) { + playerHandler.handlePluginMessage((PluginMessage) packet, true); } else { // Just forward the packet on. We don't have anything to handle at this time. connection.getProxyPlayer().getConnection().write(packet); diff --git a/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 4f1b2933e..edd404932 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -4,10 +4,12 @@ import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.connection.backend.ServerConnection; import com.velocitypowered.proxy.data.ServerInfo; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.packets.*; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.util.ThrowableUtils; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.channel.EventLoop; import net.kyori.text.TextComponent; import net.kyori.text.format.TextColor; @@ -15,15 +17,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; public class ClientPlaySessionHandler implements MinecraftSessionHandler { private static final Logger logger = LogManager.getLogger(ClientPlaySessionHandler.class); + private static final int MAX_PLUGIN_CHANNELS = 128; private final ConnectedPlayer player; private ScheduledFuture pingTask; @@ -76,6 +77,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } } + if (packet instanceof PluginMessage) { + handlePluginMessage((PluginMessage) packet, false); + return; + } + // If we don't want to handle this packet, just forward it on. player.getConnectedServer().getChannel().write(packet); } @@ -149,4 +155,64 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { public List getServerBossBars() { return serverBossBars; } + + public void handlePluginMessage(PluginMessage packet, boolean fromBackend) { + logger.info("Got plugin message packet {}", packet); + + // TODO: this certainly isn't the right approach, need a better way! + /*if (packet.getChannel().equals("REGISTER")) { + List actuallyRegistered = new ArrayList<>(); + List channels = PluginMessageUtil.getChannels(packet); + for (String channel : channels) { + if (pluginMessageChannels.size() >= MAX_PLUGIN_CHANNELS && !pluginMessageChannels.contains(channel)) { + throw new IllegalStateException("Too many plugin message channels registered"); + } + if (pluginMessageChannels.add(channel)) { + actuallyRegistered.add(channel); + } + } + + if (actuallyRegistered.size() > 0) { + logger.info("Rewritten register packet: {}", actuallyRegistered); + PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket("REGISTER", actuallyRegistered); + if (fromBackend) { + player.getConnection().write(newRegisterPacket); + } else { + player.getConnectedServer().getChannel().write(newRegisterPacket); + } + } + + return; + } + + if (packet.getChannel().equals("UNREGISTER")) { + List channels = PluginMessageUtil.getChannels(packet); + pluginMessageChannels.removeAll(channels); + }*/ + + if (packet.getChannel().equals("MC|Brand")) { + // Rewrite this packet to indicate that Velocity is running. Hurrah! + ByteBuf currentBrandBuf = Unpooled.wrappedBuffer(packet.getData()); + + ByteBuf buf = Unpooled.buffer(); + byte[] rewrittenBrand; + try { + String currentBrand = ProtocolUtils.readString(currentBrandBuf); + logger.info("Remote server brand: {}", currentBrand); + ProtocolUtils.writeString(buf, currentBrand + " (Velocity)"); + rewrittenBrand = new byte[buf.readableBytes()]; + buf.readBytes(rewrittenBrand); + } finally { + buf.release(); + } + packet.setData(rewrittenBrand); + } + + // No other special handling? + if (fromBackend) { + player.getConnection().write(packet); + } else { + player.getConnectedServer().getChannel().write(packet); + } + } } diff --git a/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java b/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java index fc4a19306..edbf3c80c 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/ProtocolConstants.java @@ -3,6 +3,7 @@ package com.velocitypowered.proxy.protocol; import java.util.Arrays; public enum ProtocolConstants { ; + public static final int MINECRAFT_1_7_2 = 4; public static final int MINECRAFT_1_11 = 315; public static final int MINECRAFT_1_11_1 = 316; public static final int MINECRAFT_1_12 = 335; @@ -10,6 +11,7 @@ public enum ProtocolConstants { ; public static final int MINECRAFT_1_12_2 = 340; public static final int MINIMUM_VERSION_SUPPORTED = MINECRAFT_1_11; + public static final int MINIMUM_GENERIC_VERSION = MINECRAFT_1_11; public static final int[] SUPPORTED_VERSIONS = new int[] { MINECRAFT_1_11, diff --git a/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 1c77bae87..fb0ed5498 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -13,20 +13,20 @@ public enum StateRegistry { HANDSHAKE { { SERVERBOUND.register(Handshake.class, Handshake::new, - lowestVersion(0x00)); + genericMapping(0x00)); } }, STATUS { { SERVERBOUND.register(StatusRequest.class, StatusRequest::new, - lowestVersion(0x00)); + genericMapping(0x00)); SERVERBOUND.register(StatusPing.class, StatusPing::new, - lowestVersion(0x01)); + genericMapping(0x01)); CLIENTBOUND.register(StatusResponse.class, StatusResponse::new, - lowestVersion(0x00)); + genericMapping(0x00)); CLIENTBOUND.register(StatusPing.class, StatusPing::new, - lowestVersion(0x01)); + genericMapping(0x01)); } }, PLAY { @@ -69,18 +69,18 @@ public enum StateRegistry { LOGIN { { SERVERBOUND.register(ServerLogin.class, ServerLogin::new, - lowestVersion(0x00)); + genericMapping(0x00)); SERVERBOUND.register(EncryptionResponse.class, EncryptionResponse::new, - lowestVersion(0x01)); + genericMapping(0x01)); CLIENTBOUND.register(Disconnect.class, Disconnect::new, - lowestVersion(0x00)); + genericMapping(0x00)); CLIENTBOUND.register(EncryptionRequest.class, EncryptionRequest::new, - lowestVersion(0x01)); + genericMapping(0x01)); CLIENTBOUND.register(ServerLoginSuccess.class, ServerLoginSuccess::new, - lowestVersion(0x02)); + genericMapping(0x02)); CLIENTBOUND.register(SetCompression.class, SetCompression::new, - lowestVersion(0x03)); + genericMapping(0x03)); } }; @@ -104,6 +104,7 @@ public enum StateRegistry { for (int version : ProtocolConstants.SUPPORTED_VERSIONS) { versions.put(version, new ProtocolVersion(version)); } + versions.put(MINIMUM_GENERIC_VERSION, new ProtocolVersion(MINIMUM_GENERIC_VERSION)); } public ProtocolVersion getVersion(final int version) { @@ -214,7 +215,7 @@ public enum StateRegistry { return new PacketMapping(id, version); } - private static PacketMapping lowestVersion(int id) { - return new PacketMapping(id, MINECRAFT_1_11); + private static PacketMapping genericMapping(int id) { + return new PacketMapping(id, MINIMUM_GENERIC_VERSION); } } diff --git a/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java b/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java index 5e6e6cf90..74cf1faba 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftDecoder.java @@ -17,7 +17,7 @@ public class MinecraftDecoder extends MessageToMessageDecoder { public MinecraftDecoder(ProtocolConstants.Direction direction) { this.state = StateRegistry.HANDSHAKE; this.direction = Preconditions.checkNotNull(direction, "direction"); - this.setProtocolVersion(ProtocolConstants.MINIMUM_VERSION_SUPPORTED); + this.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION); } @Override diff --git a/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java b/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java index 1ccf1e293..9607c371d 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/netty/MinecraftEncoder.java @@ -17,7 +17,7 @@ public class MinecraftEncoder extends MessageToByteEncoder { public MinecraftEncoder(ProtocolConstants.Direction direction) { this.state = StateRegistry.HANDSHAKE; this.direction = Preconditions.checkNotNull(direction, "direction"); - this.setProtocolVersion(ProtocolConstants.MINIMUM_VERSION_SUPPORTED); + this.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION); } @Override diff --git a/src/main/java/com/velocitypowered/proxy/protocol/packets/PluginMessage.java b/src/main/java/com/velocitypowered/proxy/protocol/packets/PluginMessage.java index ed7ad1da2..b00ea6afd 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/packets/PluginMessage.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/packets/PluginMessage.java @@ -5,10 +5,36 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants; import com.velocitypowered.proxy.protocol.ProtocolUtils; import io.netty.buffer.ByteBuf; +import java.util.Arrays; + public class PluginMessage implements MinecraftPacket { private String channel; private byte[] data; + public String getChannel() { + return channel; + } + + public void setChannel(String channel) { + this.channel = channel; + } + + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + @Override + public String toString() { + return "PluginMessage{" + + "channel='" + channel + '\'' + + ", data=" + Arrays.toString(data) + + '}'; + } + @Override public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { this.channel = ProtocolUtils.readString(buf, 20); diff --git a/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java b/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java new file mode 100644 index 000000000..ea026e487 --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/protocol/util/PluginMessageUtil.java @@ -0,0 +1,27 @@ +package com.velocitypowered.proxy.protocol.util; + +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.velocitypowered.proxy.protocol.packets.PluginMessage; + +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.List; + +public enum PluginMessageUtil { + ; + + public static List getChannels(PluginMessage message) { + Preconditions.checkArgument(message.getChannel().equals("REGISTER") || + message.getChannel().equals("UNREGISTER"), "Unknown channel type " + message.getChannel()); + return ImmutableList.copyOf(new String(message.getData()).split("\0")); + } + + public static PluginMessage constructChannelsPacket(String channel, Collection channels) { + PluginMessage message = new PluginMessage(); + message.setChannel(channel); + message.setData(Joiner.on("\0").join(channels).getBytes(StandardCharsets.UTF_8)); + return message; + } +}