From 7db94f3a651f9c773b6d6656f64df86888505be9 Mon Sep 17 00:00:00 2001 From: themode Date: Mon, 9 Nov 2020 23:48:34 +0100 Subject: [PATCH] Support for velocity modern forwarding --- .../net/minestom/server/MinecraftServer.java | 15 +++- .../advancements/AdvancementManager.java | 2 +- .../server/advancements/AdvancementRoot.java | 14 ++-- .../net/minestom/server/entity/Player.java | 1 - .../server/extras/velocity/VelocityProxy.java | 78 +++++++++++++++++++ .../server/network/ConnectionManager.java | 16 ++++ .../network/netty/channel/ClientChannel.java | 1 + .../handler/ClientLoginPacketsHandler.java | 2 + .../client/handshake/HandshakePacket.java | 4 +- .../login/EncryptionResponsePacket.java | 20 ++--- .../login/LoginPluginResponsePacket.java | 69 ++++++++++++++++ .../packet/client/login/LoginStartPacket.java | 61 +++++++++++---- .../packet/server/ServerPacketIdentifier.java | 6 ++ .../server/login/EncryptionRequestPacket.java | 7 +- .../packet/server/login/LoginDisconnect.java | 25 ------ .../server/login/LoginDisconnectPacket.java | 31 ++++++++ .../login/LoginPluginRequestPacket.java | 27 +++++++ .../server/login/LoginSuccessPacket.java | 3 +- .../server/login/SetCompressionPacket.java | 3 +- .../{login => play}/JoinGamePacket.java | 2 +- .../server/play/PluginMessagePacket.java | 1 + .../network/player/NettyPlayerConnection.java | 74 ++++++++++++++++++ .../network/player/PlayerConnection.java | 13 +--- .../server/utils/binary/BinaryWriter.java | 2 + src/test/java/demo/Main.java | 4 + src/test/java/demo/PlayerInit.java | 6 ++ 26 files changed, 404 insertions(+), 83 deletions(-) create mode 100644 src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java create mode 100644 src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java delete mode 100644 src/main/java/net/minestom/server/network/packet/server/login/LoginDisconnect.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/login/LoginDisconnectPacket.java create mode 100644 src/main/java/net/minestom/server/network/packet/server/login/LoginPluginRequestPacket.java rename src/main/java/net/minestom/server/network/packet/server/{login => play}/JoinGamePacket.java (97%) diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 68c6a7dd1..311de281f 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -54,6 +54,7 @@ import net.minestom.server.utils.validate.Check; import net.minestom.server.world.Difficulty; import net.minestom.server.world.DimensionTypeManager; import net.minestom.server.world.biomes.BiomeManager; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -227,15 +228,18 @@ public class MinecraftServer { * * @return the server brand name */ + @NotNull public static String getBrandName() { return brandName; } /** - * Changes the server brand name, update the name to all connected players. + * Changes the server brand name and send the change to all connected players. * * @param brandName the server brand name + * @throws NullPointerException if {@code brandName} is null */ + @NotNull public static void setBrandName(String brandName) { Check.notNull(brandName, "The brand name cannot be null"); MinecraftServer.brandName = brandName; @@ -267,6 +271,7 @@ public class MinecraftServer { * * @return the server difficulty */ + @NotNull public static Difficulty getDifficulty() { return difficulty; } @@ -276,7 +281,9 @@ public class MinecraftServer { * * @param difficulty the new server difficulty */ - public static void setDifficulty(Difficulty difficulty) { + @NotNull + public static void setDifficulty(@NotNull Difficulty difficulty) { + Check.notNull(difficulty, "The server difficulty cannot be null."); MinecraftServer.difficulty = difficulty; // The difficulty packet @@ -603,7 +610,7 @@ public class MinecraftServer { * @param responseDataConsumer the response data consumer, can be null * @throws IllegalStateException if called before {@link #init()} or if the server is already running */ - public void start(String address, int port, ResponseDataConsumer responseDataConsumer) { + public void start(@NotNull String address, int port, @Nullable ResponseDataConsumer responseDataConsumer) { Check.stateCondition(!initialized, "#start can only be called after #init"); Check.stateCondition(started, "The server is already started"); @@ -632,7 +639,7 @@ public class MinecraftServer { * @param port the server port * @see #start(String, int, ResponseDataConsumer) */ - public void start(String address, int port) { + public void start(@NotNull String address, int port) { start(address, port, null); } diff --git a/src/main/java/net/minestom/server/advancements/AdvancementManager.java b/src/main/java/net/minestom/server/advancements/AdvancementManager.java index 25e6c49a5..2ce596795 100644 --- a/src/main/java/net/minestom/server/advancements/AdvancementManager.java +++ b/src/main/java/net/minestom/server/advancements/AdvancementManager.java @@ -15,7 +15,7 @@ import java.util.concurrent.ConcurrentHashMap; */ public class AdvancementManager { - // root identifier TO its advancement tab + // root identifier = its advancement tab private final Map advancementTabMap = new ConcurrentHashMap<>(); /** diff --git a/src/main/java/net/minestom/server/advancements/AdvancementRoot.java b/src/main/java/net/minestom/server/advancements/AdvancementRoot.java index 3850be9e9..2867ddc99 100644 --- a/src/main/java/net/minestom/server/advancements/AdvancementRoot.java +++ b/src/main/java/net/minestom/server/advancements/AdvancementRoot.java @@ -3,6 +3,8 @@ package net.minestom.server.advancements; import net.minestom.server.chat.ColoredText; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Represents an {@link Advancement} which is the root of an {@link AdvancementTab}. @@ -12,18 +14,18 @@ import net.minestom.server.item.Material; */ public class AdvancementRoot extends Advancement { - public AdvancementRoot(ColoredText title, ColoredText description, - ItemStack icon, FrameType frameType, + public AdvancementRoot(@NotNull ColoredText title, @NotNull ColoredText description, + @NotNull ItemStack icon, @NotNull FrameType frameType, float x, float y, - String background) { + @Nullable String background) { super(title, description, icon, frameType, x, y); setBackground(background); } - public AdvancementRoot(ColoredText title, ColoredText description, - Material icon, FrameType frameType, + public AdvancementRoot(@NotNull ColoredText title, @NotNull ColoredText description, + @NotNull Material icon, FrameType frameType, float x, float y, - String background) { + @Nullable String background) { super(title, description, icon, frameType, x, y); setBackground(background); } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 8d1a46fe9..9936148f6 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -34,7 +34,6 @@ import net.minestom.server.network.PlayerProvider; import net.minestom.server.network.packet.client.ClientPlayPacket; import net.minestom.server.network.packet.client.play.ClientChatMessagePacket; import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.login.JoinGamePacket; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.player.NettyPlayerConnection; import net.minestom.server.network.player.PlayerConnection; diff --git a/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java b/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java new file mode 100644 index 000000000..08e9cb4ec --- /dev/null +++ b/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java @@ -0,0 +1,78 @@ +package net.minestom.server.extras.velocity; + +import net.minestom.server.utils.binary.BinaryReader; +import org.jetbrains.annotations.NotNull; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class VelocityProxy { + + public static final String PLAYER_INFO_CHANNEL = "velocity:player_info"; + + private static boolean enabled; + private static byte[] secret; + + /** + * Enables velocity modern forwarding. + * + * @param secret the forwarding secret, + * be sure to do not hardcode it in your code but to retrieve it from a file or anywhere else safe + */ + public static void enable(@NotNull String secret) { + VelocityProxy.enabled = true; + VelocityProxy.secret = secret.getBytes(); + } + + /** + * Gets if velocity modern forwarding is enabled. + * + * @return true if velocity modern forwarding is enabled + */ + public static boolean isEnabled() { + return enabled; + } + + public static boolean checkIntegrity(@NotNull BinaryReader reader) { + + if (!enabled) { + return false; + } + + final byte[] signature = reader.readBytes(32); + + final byte[] data = reader.getRemainingBytes(); + + try { + final Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(secret, "HmacSHA256")); + final byte[] mySignature = mac.doFinal(data); + if (!MessageDigest.isEqual(signature, mySignature)) { + return false; + } + } catch (final InvalidKeyException | NoSuchAlgorithmException e) { + throw new AssertionError(e); + } + + /*int version = buf.readVarInt(); + if (version != SUPPORTED_FORWARDING_VERSION) { + throw new IllegalStateException("Unsupported forwarding version " + version + ", wanted " + SUPPORTED_FORWARDING_VERSION); + }*/ + + return true; + } + + private static void readProperties(final BinaryReader reader) { + final int properties = reader.readVarInt(); + for (int i1 = 0; i1 < properties; i1++) { + final String name = reader.readSizedString(); + final String value = reader.readSizedString(); + final String signature = reader.readBoolean() ? reader.readSizedString() : null; + System.out.println("test: " + name + " " + value + " " + signature); + } + } + +} diff --git a/src/main/java/net/minestom/server/network/ConnectionManager.java b/src/main/java/net/minestom/server/network/ConnectionManager.java index 9aeae34bc..e18558b01 100644 --- a/src/main/java/net/minestom/server/network/ConnectionManager.java +++ b/src/main/java/net/minestom/server/network/ConnectionManager.java @@ -5,6 +5,7 @@ import net.minestom.server.entity.Player; import net.minestom.server.entity.fakeplayer.FakePlayer; import net.minestom.server.listener.manager.PacketConsumer; import net.minestom.server.network.packet.client.login.LoginStartPacket; +import net.minestom.server.network.packet.server.login.LoginSuccessPacket; import net.minestom.server.network.packet.server.play.ChatMessagePacket; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.utils.callback.validator.PlayerValidator; @@ -269,4 +270,19 @@ public final class ConnectionManager { this.players.remove(player); this.connectionPlayerMap.remove(connection); } + + /** + * Sends a {@link LoginSuccessPacket} and change the connection state to {@link ConnectionState#PLAY}. + * + * @param connection the player connection + * @param uuid the uuid of the player + * @param username the username of the player + */ + public void startPlayState(@NotNull PlayerConnection connection, @NotNull UUID uuid, @NotNull String username) { + LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(uuid, username); + connection.sendPacket(loginSuccessPacket); + + connection.setConnectionState(ConnectionState.PLAY); + createPlayer(uuid, username, connection); + } } diff --git a/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java b/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java index 810cc7a6c..4b8a51f12 100644 --- a/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java +++ b/src/main/java/net/minestom/server/network/netty/channel/ClientChannel.java @@ -61,6 +61,7 @@ public class ClientChannel extends SimpleChannelInboundHandler { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { log.info(cause.getMessage()); + cause.printStackTrace(); ctx.close(); } } diff --git a/src/main/java/net/minestom/server/network/packet/client/handler/ClientLoginPacketsHandler.java b/src/main/java/net/minestom/server/network/packet/client/handler/ClientLoginPacketsHandler.java index c5f36e1f8..122026d74 100644 --- a/src/main/java/net/minestom/server/network/packet/client/handler/ClientLoginPacketsHandler.java +++ b/src/main/java/net/minestom/server/network/packet/client/handler/ClientLoginPacketsHandler.java @@ -1,6 +1,7 @@ package net.minestom.server.network.packet.client.handler; import net.minestom.server.network.packet.client.login.EncryptionResponsePacket; +import net.minestom.server.network.packet.client.login.LoginPluginResponsePacket; import net.minestom.server.network.packet.client.login.LoginStartPacket; public class ClientLoginPacketsHandler extends ClientPacketsHandler { @@ -8,6 +9,7 @@ public class ClientLoginPacketsHandler extends ClientPacketsHandler { public ClientLoginPacketsHandler() { register(0, LoginStartPacket::new); register(1, EncryptionResponsePacket::new); + register(2, LoginPluginResponsePacket::new); } } diff --git a/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java b/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java index 9b240405d..9658a9af1 100644 --- a/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/handshake/HandshakePacket.java @@ -5,7 +5,7 @@ import net.minestom.server.chat.ChatColor; import net.minestom.server.chat.ColoredText; import net.minestom.server.network.ConnectionState; import net.minestom.server.network.packet.client.ClientPreplayPacket; -import net.minestom.server.network.packet.server.login.LoginDisconnect; +import net.minestom.server.network.packet.server.login.LoginDisconnectPacket; import net.minestom.server.network.player.NettyPlayerConnection; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.utils.binary.BinaryReader; @@ -47,7 +47,7 @@ public class HandshakePacket implements ClientPreplayPacket { } } else { // Incorrect client version - connection.sendPacket(new LoginDisconnect(INVALID_VERSION_TEXT.toString())); + connection.sendPacket(new LoginDisconnectPacket(INVALID_VERSION_TEXT.toString())); connection.disconnect(); } break; diff --git a/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java b/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java index 084dbdf51..dbbe5ccc8 100644 --- a/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java @@ -5,9 +5,7 @@ import com.mojang.authlib.exceptions.AuthenticationUnavailableException; import net.minestom.server.MinecraftServer; import net.minestom.server.data.type.array.ByteArrayData; import net.minestom.server.extras.mojangAuth.MojangCrypt; -import net.minestom.server.network.ConnectionState; import net.minestom.server.network.packet.client.ClientPreplayPacket; -import net.minestom.server.network.packet.server.login.LoginSuccessPacket; import net.minestom.server.network.player.NettyPlayerConnection; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.utils.binary.BinaryReader; @@ -32,17 +30,18 @@ public class EncryptionResponsePacket implements ClientPreplayPacket { if (!(connection instanceof NettyPlayerConnection)) { return; } + final NettyPlayerConnection nettyConnection = (NettyPlayerConnection) connection; new Thread(THREAD_NAME + " #" + UNIQUE_THREAD_ID.incrementAndGet()) { public void run() { try { - if (!Arrays.equals(connection.getNonce(), getNonce())) { - MinecraftServer.getLOGGER().error(connection.getLoginUsername() + " tried to login with an invalid nonce!"); + final String loginUsername = nettyConnection.getLoginUsername(); + if (!Arrays.equals(nettyConnection.getNonce(), getNonce())) { + MinecraftServer.getLOGGER().error(loginUsername + " tried to login with an invalid nonce!"); return; } - if (!connection.getLoginUsername().isEmpty()) { - final NettyPlayerConnection nettyConnection = (NettyPlayerConnection) connection; + if (!loginUsername.isEmpty()) { final byte[] digestedData = MojangCrypt.digestData("", MinecraftServer.getKeyPair().getPublic(), getSecretKey()); @@ -54,7 +53,7 @@ public class EncryptionResponsePacket implements ClientPreplayPacket { } final String string3 = new BigInteger(digestedData).toString(16); - final GameProfile gameProfile = MinecraftServer.getSessionService().hasJoinedServer(new GameProfile(null, connection.getLoginUsername()), string3); + final GameProfile gameProfile = MinecraftServer.getSessionService().hasJoinedServer(new GameProfile(null, loginUsername), string3); nettyConnection.setEncryptionKey(getSecretKey()); final int threshold = MinecraftServer.getCompressionThreshold(); @@ -62,11 +61,8 @@ public class EncryptionResponsePacket implements ClientPreplayPacket { nettyConnection.enableCompression(threshold); } - LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(gameProfile.getId(), gameProfile.getName()); - connection.sendPacket(loginSuccessPacket); - MinecraftServer.getLOGGER().info("UUID of player {} is {}", connection.getLoginUsername(), gameProfile.getId()); - connection.setConnectionState(ConnectionState.PLAY); - CONNECTION_MANAGER.createPlayer(gameProfile.getId(), gameProfile.getName(), connection); + MinecraftServer.getLOGGER().info("UUID of player {} is {}", loginUsername, gameProfile.getId()); + CONNECTION_MANAGER.startPlayState(connection, gameProfile.getId(), gameProfile.getName()); } } catch (AuthenticationUnavailableException e) { e.printStackTrace(); diff --git a/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java b/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java new file mode 100644 index 000000000..9378c86a1 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/client/login/LoginPluginResponsePacket.java @@ -0,0 +1,69 @@ +package net.minestom.server.network.packet.client.login; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.chat.ChatColor; +import net.minestom.server.chat.ColoredText; +import net.minestom.server.extras.velocity.VelocityProxy; +import net.minestom.server.network.ConnectionManager; +import net.minestom.server.network.packet.client.ClientPreplayPacket; +import net.minestom.server.network.packet.server.login.LoginDisconnectPacket; +import net.minestom.server.network.player.NettyPlayerConnection; +import net.minestom.server.network.player.PlayerConnection; +import net.minestom.server.utils.binary.BinaryReader; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public class LoginPluginResponsePacket implements ClientPreplayPacket { + + private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager(); + + public static final ColoredText INVALID_PROXY_RESPONSE = ColoredText.of(ChatColor.RED, "Invalid proxy response!"); + + public int messageId; + public boolean successful; + public byte[] data; + + @Override + public void process(@NotNull PlayerConnection connection) { + + // Proxy support + if (connection instanceof NettyPlayerConnection) { + final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection; + final String channel = nettyPlayerConnection.getPluginRequestChannel(messageId); + + if (channel != null) { + boolean success = false; + + // Velocity + if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) { + if (data != null) { + BinaryReader reader = new BinaryReader(data); + success = VelocityProxy.checkIntegrity(reader); + } + } + + if (success) { + // Proxy usage always mean that the server is in offline mode + final String username = nettyPlayerConnection.getLoginUsername(); + final UUID playerUuid = CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username); + + CONNECTION_MANAGER.startPlayState(connection, playerUuid, username); + } else { + LoginDisconnectPacket disconnectPacket = new LoginDisconnectPacket(INVALID_PROXY_RESPONSE); + nettyPlayerConnection.sendPacket(disconnectPacket); + } + + } + } + } + + @Override + public void read(@NotNull BinaryReader reader) { + this.messageId = reader.readVarInt(); + this.successful = reader.readBoolean(); + if (successful) { + this.data = reader.getRemainingBytes(); + } + } +} diff --git a/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java b/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java index 733815319..16b8c2827 100644 --- a/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java @@ -4,39 +4,74 @@ import net.minestom.server.MinecraftServer; import net.minestom.server.chat.ChatColor; import net.minestom.server.chat.ColoredText; import net.minestom.server.extras.MojangAuth; +import net.minestom.server.extras.velocity.VelocityProxy; import net.minestom.server.network.ConnectionState; import net.minestom.server.network.packet.client.ClientPreplayPacket; import net.minestom.server.network.packet.server.login.EncryptionRequestPacket; -import net.minestom.server.network.packet.server.login.LoginDisconnect; -import net.minestom.server.network.packet.server.login.LoginSuccessPacket; +import net.minestom.server.network.packet.server.login.LoginDisconnectPacket; +import net.minestom.server.network.packet.server.login.LoginPluginRequestPacket; import net.minestom.server.network.player.NettyPlayerConnection; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.utils.binary.BinaryReader; import org.jetbrains.annotations.NotNull; import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; public class LoginStartPacket implements ClientPreplayPacket { - private static final String ALREADY_CONNECTED_JSON = - ColoredText.of(ChatColor.RED, "You are already on this server").toString(); + private static final ColoredText ALREADY_CONNECTED_JSON = ColoredText.of(ChatColor.RED, "You are already on this server"); public String username; @Override public void process(@NotNull PlayerConnection connection) { - if (MojangAuth.isUsingMojangAuth()) { + + // Cache the login username + if (connection instanceof NettyPlayerConnection) { + ((NettyPlayerConnection) connection).UNSAFE_setLoginUsername(username); + } + + // Proxy support (only for netty clients) + if (connection instanceof NettyPlayerConnection) { + final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection; + + { + // Velocity support + if (VelocityProxy.isEnabled()) { + + final int messageId = ThreadLocalRandom.current().nextInt(); + final String channel = VelocityProxy.PLAYER_INFO_CHANNEL; + + nettyPlayerConnection.addPluginRequestEntry(messageId, channel); + + LoginPluginRequestPacket loginPluginRequestPacket = new LoginPluginRequestPacket(); + loginPluginRequestPacket.messageId = messageId; + loginPluginRequestPacket.channel = channel; + loginPluginRequestPacket.data = null; + connection.sendPacket(loginPluginRequestPacket); + + return; + } + } + + } + + if (MojangAuth.isUsingMojangAuth() && connection instanceof NettyPlayerConnection) { + // Mojang auth if (CONNECTION_MANAGER.getPlayer(username) != null) { - connection.sendPacket(new LoginDisconnect(ALREADY_CONNECTED_JSON)); + connection.sendPacket(new LoginDisconnectPacket(ALREADY_CONNECTED_JSON)); connection.disconnect(); return; } - connection.setConnectionState(ConnectionState.LOGIN); - connection.setLoginUsername(username); - EncryptionRequestPacket encryptionRequestPacket = new EncryptionRequestPacket(connection); - connection.sendPacket(encryptionRequestPacket); + final NettyPlayerConnection nettyPlayerConnection = (NettyPlayerConnection) connection; + + nettyPlayerConnection.setConnectionState(ConnectionState.LOGIN); + EncryptionRequestPacket encryptionRequestPacket = new EncryptionRequestPacket(nettyPlayerConnection); + nettyPlayerConnection.sendPacket(encryptionRequestPacket); } else { + // Offline final UUID playerUuid = CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username); final int threshold = MinecraftServer.getCompressionThreshold(); @@ -45,11 +80,7 @@ public class LoginStartPacket implements ClientPreplayPacket { ((NettyPlayerConnection) connection).enableCompression(threshold); } - LoginSuccessPacket successPacket = new LoginSuccessPacket(playerUuid, username); - connection.sendPacket(successPacket); - - connection.setConnectionState(ConnectionState.PLAY); - CONNECTION_MANAGER.createPlayer(playerUuid, username, connection); + CONNECTION_MANAGER.startPlayState(connection, playerUuid, username); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java b/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java index 3971b8224..3b70103fc 100644 --- a/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java +++ b/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java @@ -2,6 +2,12 @@ package net.minestom.server.network.packet.server; public class ServerPacketIdentifier { + public static final int LOGIN_DISCONNECT = 0x00; + public static final int LOGIN_ENCRYPTION_REQUEST = 0x01; + public static final int LOGIN_SUCCESS = 0x02; + public static final int LOGIN_SET_COMPRESSION = 0x03; + public static final int LOGIN_PLUGIN_REQUEST = 0x04; + public static final int SPAWN_ENTITY = 0x00; public static final int SPAWN_EXPERIENCE_ORB = 0x01; public static final int SPAWN_LIVING_ENTITY = 0x02; diff --git a/src/main/java/net/minestom/server/network/packet/server/login/EncryptionRequestPacket.java b/src/main/java/net/minestom/server/network/packet/server/login/EncryptionRequestPacket.java index f3d5da982..0054054fe 100644 --- a/src/main/java/net/minestom/server/network/packet/server/login/EncryptionRequestPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/login/EncryptionRequestPacket.java @@ -3,7 +3,8 @@ package net.minestom.server.network.packet.server.login; import net.minestom.server.MinecraftServer; import net.minestom.server.data.type.array.ByteArrayData; import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.player.PlayerConnection; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.network.player.NettyPlayerConnection; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; @@ -14,7 +15,7 @@ public class EncryptionRequestPacket implements ServerPacket { public byte[] publicKey; public byte[] nonce = new byte[4]; - public EncryptionRequestPacket(PlayerConnection connection) { + public EncryptionRequestPacket(NettyPlayerConnection connection) { ThreadLocalRandom.current().nextBytes(nonce); connection.setNonce(nonce); } @@ -29,6 +30,6 @@ public class EncryptionRequestPacket implements ServerPacket { @Override public int getId() { - return 0x01; + return ServerPacketIdentifier.LOGIN_ENCRYPTION_REQUEST; } } diff --git a/src/main/java/net/minestom/server/network/packet/server/login/LoginDisconnect.java b/src/main/java/net/minestom/server/network/packet/server/login/LoginDisconnect.java deleted file mode 100644 index c1e5d3272..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/login/LoginDisconnect.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.minestom.server.network.packet.server.login; - -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.utils.binary.BinaryWriter; -import org.jetbrains.annotations.NotNull; - -public class LoginDisconnect implements ServerPacket { - - private String kickMessage; - - public LoginDisconnect(String kickMessage) { - this.kickMessage = kickMessage; - } - - @Override - public void write(@NotNull BinaryWriter writer) { - writer.writeSizedString(kickMessage); - } - - @Override - public int getId() { - return 0x00; - } - -} diff --git a/src/main/java/net/minestom/server/network/packet/server/login/LoginDisconnectPacket.java b/src/main/java/net/minestom/server/network/packet/server/login/LoginDisconnectPacket.java new file mode 100644 index 000000000..1038eb6a7 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/login/LoginDisconnectPacket.java @@ -0,0 +1,31 @@ +package net.minestom.server.network.packet.server.login; + +import net.minestom.server.chat.JsonMessage; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.utils.binary.BinaryWriter; +import org.jetbrains.annotations.NotNull; + +public class LoginDisconnectPacket implements ServerPacket { + + private String kickMessage; // JSON text + + public LoginDisconnectPacket(@NotNull String kickMessage) { + this.kickMessage = kickMessage; + } + + public LoginDisconnectPacket(@NotNull JsonMessage jsonKickMessage) { + this(jsonKickMessage.toString()); + } + + @Override + public void write(@NotNull BinaryWriter writer) { + writer.writeSizedString(kickMessage); + } + + @Override + public int getId() { + return ServerPacketIdentifier.LOGIN_DISCONNECT; + } + +} diff --git a/src/main/java/net/minestom/server/network/packet/server/login/LoginPluginRequestPacket.java b/src/main/java/net/minestom/server/network/packet/server/login/LoginPluginRequestPacket.java new file mode 100644 index 000000000..d2ae02ed6 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/login/LoginPluginRequestPacket.java @@ -0,0 +1,27 @@ +package net.minestom.server.network.packet.server.login; + +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.utils.binary.BinaryWriter; +import org.jetbrains.annotations.NotNull; + +public class LoginPluginRequestPacket implements ServerPacket { + + public int messageId; + public String channel; + public byte[] data; + + @Override + public void write(@NotNull BinaryWriter writer) { + writer.writeVarInt(messageId); + writer.writeSizedString(channel); + if (data != null && data.length > 0) { + writer.writeBytes(data); + } + } + + @Override + public int getId() { + return ServerPacketIdentifier.LOGIN_PLUGIN_REQUEST; + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/login/LoginSuccessPacket.java b/src/main/java/net/minestom/server/network/packet/server/login/LoginSuccessPacket.java index b48b936cc..81b57ba35 100644 --- a/src/main/java/net/minestom/server/network/packet/server/login/LoginSuccessPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/login/LoginSuccessPacket.java @@ -1,6 +1,7 @@ package net.minestom.server.network.packet.server.login; import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; @@ -24,6 +25,6 @@ public class LoginSuccessPacket implements ServerPacket { @Override public int getId() { - return 0x02; + return ServerPacketIdentifier.LOGIN_SUCCESS; } } diff --git a/src/main/java/net/minestom/server/network/packet/server/login/SetCompressionPacket.java b/src/main/java/net/minestom/server/network/packet/server/login/SetCompressionPacket.java index 88fb49cb9..152950c65 100644 --- a/src/main/java/net/minestom/server/network/packet/server/login/SetCompressionPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/login/SetCompressionPacket.java @@ -1,6 +1,7 @@ package net.minestom.server.network.packet.server.login; import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; @@ -19,6 +20,6 @@ public class SetCompressionPacket implements ServerPacket { @Override public int getId() { - return 0x03; + return ServerPacketIdentifier.LOGIN_SET_COMPRESSION; } } diff --git a/src/main/java/net/minestom/server/network/packet/server/login/JoinGamePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/JoinGamePacket.java similarity index 97% rename from src/main/java/net/minestom/server/network/packet/server/login/JoinGamePacket.java rename to src/main/java/net/minestom/server/network/packet/server/play/JoinGamePacket.java index 46191b208..eb481c51a 100644 --- a/src/main/java/net/minestom/server/network/packet/server/login/JoinGamePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/JoinGamePacket.java @@ -1,4 +1,4 @@ -package net.minestom.server.network.packet.server.login; +package net.minestom.server.network.packet.server.play; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.GameMode; diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PluginMessagePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PluginMessagePacket.java index 2d1af7f14..4b41e7dfb 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/PluginMessagePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/PluginMessagePacket.java @@ -29,6 +29,7 @@ public class PluginMessagePacket implements ServerPacket { * * @return the current brand name packet */ + @NotNull public static PluginMessagePacket getBrandPacket() { PluginMessagePacket brandMessage = new PluginMessagePacket(); brandMessage.channel = "minecraft:brand"; diff --git a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java index c13ec2b42..d9d09d1d7 100644 --- a/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/NettyPlayerConnection.java @@ -4,9 +4,11 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.socket.SocketChannel; import lombok.Getter; +import lombok.Setter; import net.minestom.server.extras.mojangAuth.Decrypter; import net.minestom.server.extras.mojangAuth.Encrypter; import net.minestom.server.extras.mojangAuth.MojangCrypt; +import net.minestom.server.network.ConnectionState; import net.minestom.server.network.netty.codec.PacketCompressor; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.login.SetCompressionPacket; @@ -16,6 +18,8 @@ import org.jetbrains.annotations.Nullable; import javax.crypto.SecretKey; import java.net.SocketAddress; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * Represents a networking connection with Netty. @@ -30,9 +34,19 @@ public class NettyPlayerConnection extends PlayerConnection { @Getter private boolean compressed = false; + //Could be null. Only used for Mojang Auth + @Getter + @Setter + private byte[] nonce = new byte[4]; + + private String loginUsername; private String serverAddress; private int serverPort; + // Used for the login plugin request packet, to retrive the channel from a message id, + // cleared once the player enters the play state + private Map pluginRequestMap = new ConcurrentHashMap<>(); + public NettyPlayerConnection(@NotNull SocketChannel channel) { super(); this.channel = channel; @@ -114,6 +128,27 @@ public class NettyPlayerConnection extends PlayerConnection { return channel; } + /** + * Retrieves the username received from the client during connection. + *

+ * This value has not been checked and could be anything. + * + * @return the username given by the client, unchecked + */ + @Nullable + public String getLoginUsername() { + return loginUsername; + } + + /** + * Sets the internal login username field + * + * @param loginUsername the new login username field + */ + public void UNSAFE_setLoginUsername(@NotNull String loginUsername) { + this.loginUsername = loginUsername; + } + /** * Gets the server address that the client used to connect. *

@@ -137,6 +172,45 @@ public class NettyPlayerConnection extends PlayerConnection { return serverPort; } + /** + * Adds an entry to the plugin request map. + *

+ * Only working if {@link #getConnectionState()} is {@link net.minestom.server.network.ConnectionState#LOGIN}. + * + * @param messageId the message id + * @param channel the packet channel + * @throws IllegalStateException if a messageId with the value {@code messageId} already exists for this connection + */ + public void addPluginRequestEntry(int messageId, @NotNull String channel) { + if (!getConnectionState().equals(ConnectionState.LOGIN)) { + return; + } + Check.stateCondition(pluginRequestMap.containsKey(messageId), "You cannot have two messageId with the same value"); + this.pluginRequestMap.put(messageId, channel); + } + + /** + * Gets a request channel from a message id, previously cached using {@link #addPluginRequestEntry(int, String)}. + *

+ * Be aware that the internal map is cleared once the player enters the play state. + * + * @param messageId the message id + * @return the channel linked to the message id, null if not found + */ + @Nullable + public String getPluginRequestChannel(int messageId) { + return pluginRequestMap.get(messageId); + } + + @Override + public void setConnectionState(@NotNull ConnectionState connectionState) { + super.setConnectionState(connectionState); + // Clear the plugin request map (since it is not used anymore) + if (connectionState.equals(ConnectionState.PLAY)) { + this.pluginRequestMap.clear(); + } + } + /** * Used in {@link net.minestom.server.network.packet.client.handshake.HandshakePacket} to change the internal fields. * diff --git a/src/main/java/net/minestom/server/network/player/PlayerConnection.java b/src/main/java/net/minestom/server/network/player/PlayerConnection.java index 37db193de..6a9c41acb 100644 --- a/src/main/java/net/minestom/server/network/player/PlayerConnection.java +++ b/src/main/java/net/minestom/server/network/player/PlayerConnection.java @@ -2,14 +2,13 @@ package net.minestom.server.network.player; import io.netty.buffer.ByteBuf; import lombok.Getter; -import lombok.Setter; import net.minestom.server.MinecraftServer; import net.minestom.server.chat.ChatColor; import net.minestom.server.chat.ColoredText; import net.minestom.server.entity.Player; import net.minestom.server.network.ConnectionState; import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.login.LoginDisconnect; +import net.minestom.server.network.packet.server.login.LoginDisconnectPacket; import net.minestom.server.network.packet.server.play.DisconnectPacket; import org.jetbrains.annotations.NotNull; @@ -23,14 +22,6 @@ import java.util.concurrent.atomic.AtomicInteger; public abstract class PlayerConnection { private Player player; - //Could be null. Only used for Mojang Auth - @Getter - @Setter - private String loginUsername; - //Could be null. Only used for Mojang Auth - @Getter - @Setter - private byte[] nonce = new byte[4]; private ConnectionState connectionState; private boolean online; @@ -64,7 +55,7 @@ public abstract class PlayerConnection { if (count > MinecraftServer.getRateLimit()) { // Sent too many packets if (connectionState == ConnectionState.LOGIN) { - sendPacket(new LoginDisconnect("Too Many Packets")); + sendPacket(new LoginDisconnectPacket("Too Many Packets")); } else { DisconnectPacket disconnectPacket = new DisconnectPacket(); disconnectPacket.message = rateLimitKickMessage; diff --git a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java index 09a992418..0169af600 100644 --- a/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java +++ b/src/main/java/net/minestom/server/utils/binary/BinaryWriter.java @@ -186,6 +186,8 @@ public class BinaryWriter extends OutputStream { /** * Writes a byte array. + *

+ * WARNING: it doesn't write the length of {@code bytes}. * * @param bytes the byte array to write */ diff --git a/src/test/java/demo/Main.java b/src/test/java/demo/Main.java index ba8a5f9d5..9b106397a 100644 --- a/src/test/java/demo/Main.java +++ b/src/test/java/demo/Main.java @@ -3,6 +3,7 @@ package demo; import demo.blocks.BurningTorchBlock; import demo.blocks.StoneBlock; import demo.blocks.UpdatableBlockDemo; +import demo.commands.GamemodeCommand; import demo.commands.TestCommand; import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandManager; @@ -28,6 +29,7 @@ public class Main { CommandManager commandManager = MinecraftServer.getCommandManager(); commandManager.register(new TestCommand()); + commandManager.register(new GamemodeCommand()); /*commandManager.register(new EntitySelectorCommand()); commandManager.register(new HealthCommand()); commandManager.register(new SimpleCommand()); @@ -48,6 +50,8 @@ public class Main { PlayerInit.init(); + //VelocityProxy.enable("rBeJJ79W4MVU"); + //MojangAuth.init(); minecraftServer.start("0.0.0.0", 25565, PlayerInit.getResponseDataConsumer()); diff --git a/src/test/java/demo/PlayerInit.java b/src/test/java/demo/PlayerInit.java index 4705aecbd..2dfa2c6e7 100644 --- a/src/test/java/demo/PlayerInit.java +++ b/src/test/java/demo/PlayerInit.java @@ -5,6 +5,8 @@ import demo.generator.NoiseTestGenerator; import net.minestom.server.MinecraftServer; import net.minestom.server.benchmark.BenchmarkManager; import net.minestom.server.chat.ColoredText; +import net.minestom.server.data.Data; +import net.minestom.server.data.NbtDataImpl; import net.minestom.server.entity.*; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.entity.type.monster.EntityZombie; @@ -172,7 +174,11 @@ public class PlayerInit { } ItemStack itemStack = new ItemStack(Material.DIAMOND_PICKAXE, (byte) 64); + Data data = new NbtDataImpl(); + data.set("test", 51); + itemStack.setData(data); player.getInventory().addItemStack(itemStack); + System.out.println("test " + data.get("test")); //player.getInventory().addItemStack(new ItemStack(Material.STONE, (byte)64)); });