Dedicated GameProfile record (#1482)

This commit is contained in:
TheMode 2022-10-27 14:33:48 +02:00 committed by GitHub
parent d7e958fa07
commit 3e2ac14048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 130 additions and 141 deletions

View File

@ -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

View File

@ -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.
* <p>
@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<GameProfile.Property> 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;
}
}
}
}

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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<Integer, String> pluginRequestMap = new ConcurrentHashMap<>();
// Bungee
private UUID bungeeUuid;
private PlayerSkin bungeeSkin;
private final List<BinaryBuffer> waitingBuffers = new ArrayList<>();
private final AtomicReference<BinaryBuffer> 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.
* <p>
@ -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.
* <p>