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.
*