diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 8eea08344..247b5e35f 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -55,6 +55,7 @@ import net.minestom.server.network.packet.server.SendablePacket; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.login.LoginDisconnectPacket; import net.minestom.server.network.packet.server.play.*; +import net.minestom.server.network.player.GameProfile; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.recipe.Recipe; @@ -263,7 +264,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable, sendPacket(new SpawnPositionPacket(respawnPoint, 0)); // Add player to list with spawning skin - PlayerSkinInitEvent skinInitEvent = new PlayerSkinInitEvent(this, skin); + PlayerSkin profileSkin = null; + if (playerConnection instanceof PlayerSocketConnection socketConnection) { + final GameProfile gameProfile = socketConnection.gameProfile(); + if (gameProfile != null) { + for (GameProfile.Property property : gameProfile.properties()) { + if (property.name().equals("textures")) { + profileSkin = new PlayerSkin(property.value(), property.signature()); + break; + } + } + } + } + PlayerSkinInitEvent skinInitEvent = new PlayerSkinInitEvent(this, profileSkin); EventDispatcher.call(skinInitEvent); this.skin = skinInitEvent.getSkin(); // FIXME: when using Geyser, this line remove the skin of the client diff --git a/src/main/java/net/minestom/server/extras/bungee/BungeeCordProxy.java b/src/main/java/net/minestom/server/extras/bungee/BungeeCordProxy.java index 126ccbace..c4f9f577e 100644 --- a/src/main/java/net/minestom/server/extras/bungee/BungeeCordProxy.java +++ b/src/main/java/net/minestom/server/extras/bungee/BungeeCordProxy.java @@ -1,12 +1,5 @@ package net.minestom.server.extras.bungee; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import net.minestom.server.entity.PlayerSkin; -import org.jetbrains.annotations.NotNull; - /** * BungeeCord forwarding support. This does not count as a security feature, and you will still be required to manage your firewall. *

@@ -31,29 +24,4 @@ public final class BungeeCordProxy { public static boolean isEnabled() { return enabled; } - - public static PlayerSkin readSkin(@NotNull String json) { - JsonArray array = JsonParser.parseString(json).getAsJsonArray(); - String skinTexture = null; - String skinSignature = null; - - for (JsonElement element : array) { - JsonObject jsonObject = element.getAsJsonObject(); - JsonElement name = jsonObject.get("name"); - if (name == null || !name.getAsString().equals("textures")) continue; - - JsonElement value = jsonObject.get("value"); - JsonElement signature = jsonObject.get("signature"); - if (value == null || signature == null) continue; - - skinTexture = value.getAsString(); - skinSignature = signature.getAsString(); - } - - if (skinTexture != null && skinSignature != null) { - return new PlayerSkin(skinTexture, skinSignature); - } else { - return null; - } - } } diff --git a/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java b/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java index 9fc892398..c0a2e3c7e 100644 --- a/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java +++ b/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java @@ -1,7 +1,6 @@ package net.minestom.server.extras.velocity; import net.minestom.server.MinecraftServer; -import net.minestom.server.entity.PlayerSkin; import net.minestom.server.utils.binary.BinaryReader; import org.jetbrains.annotations.NotNull; @@ -76,26 +75,4 @@ public final class VelocityProxy { return null; } } - - public static PlayerSkin readSkin(@NotNull BinaryReader reader) { - String skinTexture = null; - String skinSignature = null; - final int properties = reader.readVarInt(); - for (int i = 0; i < properties; i++) { - final String name = reader.readSizedString(Short.MAX_VALUE); - final String value = reader.readSizedString(Short.MAX_VALUE); - final String signature = reader.readBoolean() ? reader.readSizedString(Short.MAX_VALUE) : null; - - if (name.equals("textures")) { - skinTexture = value; - skinSignature = signature; - } - } - - if (skinTexture != null && skinSignature != null) { - return new PlayerSkin(skinTexture, skinSignature); - } else { - return null; - } - } } 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 3b99d2337..232484855 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 @@ -1,13 +1,17 @@ package net.minestom.server.network.packet.client.handshake; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; -import net.minestom.server.entity.PlayerSkin; import net.minestom.server.extras.bungee.BungeeCordProxy; import net.minestom.server.network.ConnectionState; import net.minestom.server.network.packet.client.ClientPreplayPacket; import net.minestom.server.network.packet.server.login.LoginDisconnectPacket; +import net.minestom.server.network.player.GameProfile; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.utils.binary.BinaryReader; @@ -15,6 +19,8 @@ import net.minestom.server.utils.binary.BinaryWriter; import org.jetbrains.annotations.NotNull; import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; public record HandshakePacket(int protocolVersion, @NotNull String serverAddress, @@ -45,41 +51,49 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress @Override public void process(@NotNull PlayerConnection connection) { - String address = serverAddress; // Bungee support (IP forwarding) if (BungeeCordProxy.isEnabled() && connection instanceof PlayerSocketConnection socketConnection && nextState == 2) { - if (address != null) { - final String[] split = address.split("\00"); + final String[] split = address.split("\00"); - if (split.length == 3 || split.length == 4) { - address = split[0]; + if (split.length == 3 || split.length == 4) { + address = split[0]; - final SocketAddress socketAddress = new java.net.InetSocketAddress(split[1], - ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort()); - socketConnection.setRemoteAddress(socketAddress); + final SocketAddress socketAddress = new java.net.InetSocketAddress(split[1], + ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort()); + socketConnection.setRemoteAddress(socketAddress); - UUID playerUuid = UUID.fromString( - split[2] - .replaceFirst( - "(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5" - ) - ); - PlayerSkin playerSkin = null; + UUID playerUuid = UUID.fromString( + split[2] + .replaceFirst( + "(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5" + ) + ); - if (split.length == 4) { - playerSkin = BungeeCordProxy.readSkin(split[3]); + List properties = new ArrayList<>(); + if (split.length == 4) { + final String rawPropertyJson = split[3]; + final JsonArray propertyJson = JsonParser.parseString(rawPropertyJson).getAsJsonArray(); + for (JsonElement element : propertyJson) { + final JsonObject jsonObject = element.getAsJsonObject(); + final JsonElement name = jsonObject.get("name"); + final JsonElement value = jsonObject.get("value"); + final JsonElement signature = jsonObject.get("signature"); + if (name == null || value == null) continue; + + final String nameString = name.getAsString(); + final String valueString = value.getAsString(); + final String signatureString = signature == null ? null : signature.getAsString(); + + properties.add(new GameProfile.Property(nameString, valueString, signatureString)); } - - socketConnection.UNSAFE_setBungeeUuid(playerUuid); - socketConnection.UNSAFE_setBungeeSkin(playerSkin); - } else { - socketConnection.sendPacket(new LoginDisconnectPacket(INVALID_BUNGEE_FORWARDING)); - socketConnection.disconnect(); - return; } + + final GameProfile gameProfile = new GameProfile(playerUuid, "test", properties); + socketConnection.UNSAFE_setProfile(gameProfile); } else { - // Happen when a client ping the server, ignore + socketConnection.sendPacket(new LoginDisconnectPacket(INVALID_BUNGEE_FORWARDING)); + socketConnection.disconnect(); return; } } @@ -90,10 +104,8 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress } switch (nextState) { - case 1: - connection.setConnectionState(ConnectionState.STATUS); - break; - case 2: + case 1 -> connection.setConnectionState(ConnectionState.STATUS); + case 2 -> { if (protocolVersion == MinecraftServer.PROTOCOL_VERSION) { connection.setConnectionState(ConnectionState.LOGIN); } else { @@ -101,10 +113,10 @@ public record HandshakePacket(int protocolVersion, @NotNull String serverAddress connection.sendPacket(new LoginDisconnectPacket(INVALID_VERSION_TEXT)); connection.disconnect(); } - break; - default: + } + default -> { // Unexpected error - break; + } } } } 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 index f1d6cdb2c..7e94e76dd 100644 --- 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 @@ -3,12 +3,11 @@ package net.minestom.server.network.packet.client.login; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; -import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerSkin; 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.GameProfile; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.utils.binary.BinaryReader; @@ -19,7 +18,6 @@ import org.jetbrains.annotations.Nullable; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.util.UUID; public record LoginPluginResponsePacket(int messageId, byte @Nullable [] data) implements ClientPreplayPacket { private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager(); @@ -38,9 +36,7 @@ public record LoginPluginResponsePacket(int messageId, byte @Nullable [] data) i boolean success = false; SocketAddress socketAddress = null; - UUID playerUuid = null; - String playerUsername = null; - PlayerSkin playerSkin = null; + GameProfile gameProfile = null; // Velocity if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) { @@ -52,29 +48,15 @@ public record LoginPluginResponsePacket(int messageId, byte @Nullable [] data) i final InetAddress address = VelocityProxy.readAddress(reader); final int port = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort(); socketAddress = new InetSocketAddress(address, port); - - playerUuid = reader.readUuid(); - playerUsername = reader.readSizedString(16); - - playerSkin = VelocityProxy.readSkin(reader); + gameProfile = new GameProfile(reader); } } } if (success) { - if (socketAddress != null) { - socketConnection.setRemoteAddress(socketAddress); - } - if (playerUsername != null) { - socketConnection.UNSAFE_setLoginUsername(playerUsername); - } - - final String username = socketConnection.getLoginUsername(); - final UUID uuid = playerUuid != null ? - playerUuid : CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username); - - Player player = CONNECTION_MANAGER.startPlayState(connection, uuid, username, true); - player.setSkin(playerSkin); + socketConnection.setRemoteAddress(socketAddress); + socketConnection.UNSAFE_setProfile(gameProfile); + CONNECTION_MANAGER.startPlayState(connection, gameProfile.uuid(), gameProfile.name(), true); } else { LoginDisconnectPacket disconnectPacket = new LoginDisconnectPacket(INVALID_PROXY_RESPONSE); socketConnection.sendPacket(disconnectPacket); 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 418c82c6e..caa46bfb0 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 @@ -5,7 +5,6 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.MinecraftServer; import net.minestom.server.crypto.PlayerPublicKey; import net.minestom.server.crypto.SignatureValidator; -import net.minestom.server.entity.Player; import net.minestom.server.extras.MojangAuth; import net.minestom.server.extras.bungee.BungeeCordProxy; import net.minestom.server.extras.velocity.VelocityProxy; @@ -95,13 +94,9 @@ public record LoginStartPacket(@NotNull String username, final boolean bungee = BungeeCordProxy.isEnabled(); // Offline final UUID playerUuid = bungee && isSocketConnection ? - ((PlayerSocketConnection) connection).getBungeeUuid() : + ((PlayerSocketConnection) connection).gameProfile().uuid() : CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username); - - Player player = CONNECTION_MANAGER.startPlayState(connection, playerUuid, username, true); - if (bungee && isSocketConnection) { - player.setSkin(((PlayerSocketConnection) connection).getBungeeSkin()); - } + CONNECTION_MANAGER.startPlayState(connection, playerUuid, username, true); } } diff --git a/src/main/java/net/minestom/server/network/player/GameProfile.java b/src/main/java/net/minestom/server/network/player/GameProfile.java new file mode 100644 index 000000000..fb4f165cf --- /dev/null +++ b/src/main/java/net/minestom/server/network/player/GameProfile.java @@ -0,0 +1,54 @@ +package net.minestom.server.network.player; + +import net.minestom.server.utils.binary.BinaryReader; +import net.minestom.server.utils.binary.BinaryWriter; +import net.minestom.server.utils.binary.Writeable; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.UUID; + +@ApiStatus.Experimental +public record GameProfile(@NotNull UUID uuid, @NotNull String name, + @NotNull List<@NotNull Property> properties) implements Writeable { + public GameProfile { + if (name.isBlank()) + throw new IllegalArgumentException("Name cannot be blank"); + if (name.length() > 16) + throw new IllegalArgumentException("Name length cannot be greater than 16 characters"); + properties = List.copyOf(properties); + } + + public GameProfile(@NotNull BinaryReader reader) { + this(reader.readUuid(), reader.readSizedString(16), reader.readVarIntList(Property::new)); + } + + @Override + public void write(@NotNull BinaryWriter writer) { + writer.writeUuid(uuid); + writer.writeSizedString(name); + writer.writeVarIntList(properties, BinaryWriter::write); + } + + public record Property(@NotNull String name, @NotNull String value, + @Nullable String signature) implements Writeable { + public Property(@NotNull String name, @NotNull String value) { + this(name, value, null); + } + + public Property(@NotNull BinaryReader reader) { + this(reader.readSizedString(), reader.readSizedString(), + reader.readBoolean() ? reader.readSizedString() : null); + } + + @Override + public void write(@NotNull BinaryWriter writer) { + writer.writeSizedString(name); + writer.writeSizedString(value); + writer.writeBoolean(signature != null); + if (signature != null) writer.writeSizedString(signature); + } + } +} diff --git a/src/main/java/net/minestom/server/network/player/PlayerSocketConnection.java b/src/main/java/net/minestom/server/network/player/PlayerSocketConnection.java index 7e8f4faf9..cc335a6f6 100644 --- a/src/main/java/net/minestom/server/network/player/PlayerSocketConnection.java +++ b/src/main/java/net/minestom/server/network/player/PlayerSocketConnection.java @@ -4,7 +4,6 @@ import net.kyori.adventure.translation.GlobalTranslator; import net.minestom.server.MinecraftServer; import net.minestom.server.adventure.MinestomAdventure; import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerSkin; import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.ListenerHandle; import net.minestom.server.event.player.PlayerPacketOutEvent; @@ -62,6 +61,7 @@ public class PlayerSocketConnection extends PlayerConnection { // Data from client packets private String loginUsername; + private GameProfile gameProfile; private String serverAddress; private int serverPort; private int protocolVersion; @@ -70,10 +70,6 @@ public class PlayerSocketConnection extends PlayerConnection { // cleared once the player enters the play state private final Map pluginRequestMap = new ConcurrentHashMap<>(); - // Bungee - private UUID bungeeUuid; - private PlayerSkin bungeeSkin; - private final List waitingBuffers = new ArrayList<>(); private final AtomicReference tickBuffer = new AtomicReference<>(POOL.get()); private BinaryBuffer cacheBuffer; @@ -216,6 +212,14 @@ public class PlayerSocketConnection extends PlayerConnection { return channel; } + public @Nullable GameProfile gameProfile() { + return gameProfile; + } + + public void UNSAFE_setProfile(@NotNull GameProfile gameProfile) { + this.gameProfile = gameProfile; + } + /** * Retrieves the username received from the client during connection. *

@@ -283,22 +287,6 @@ public class PlayerSocketConnection extends PlayerConnection { this.protocolVersion = protocolVersion; } - public @Nullable UUID getBungeeUuid() { - return bungeeUuid; - } - - public void UNSAFE_setBungeeUuid(UUID bungeeUuid) { - this.bungeeUuid = bungeeUuid; - } - - public @Nullable PlayerSkin getBungeeSkin() { - return bungeeSkin; - } - - public void UNSAFE_setBungeeSkin(PlayerSkin bungeeSkin) { - this.bungeeSkin = bungeeSkin; - } - /** * Adds an entry to the plugin request map. *