Execute the async login event before sending the login success packet

This commit is contained in:
themode 2021-01-06 19:02:35 +01:00
parent deb8cab03a
commit 6b39cb1e32
14 changed files with 208 additions and 156 deletions

View File

@ -675,7 +675,7 @@ public abstract class Entity implements Viewable, EventHandler, DataContainer, P
*
* @param uuid the new entity uuid
*/
protected void setUuid(@NotNull UUID uuid) {
public void setUuid(@NotNull UUID uuid) {
// Refresh internal map
Entity.entityByUuid.remove(this.uuid);
Entity.entityByUuid.put(uuid, this);

View File

@ -4,20 +4,14 @@ import com.google.common.collect.Queues;
import net.minestom.server.MinecraftServer;
import net.minestom.server.chat.ChatColor;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.entity.fakeplayer.FakePlayerOption;
import net.minestom.server.event.player.AsyncPlayerPreLoginEvent;
import net.minestom.server.event.player.PlayerLoginEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.packet.server.play.KeepAlivePacket;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import java.util.Queue;
import java.util.UUID;
import java.util.function.Consumer;
public final class EntityManager {
@ -76,49 +70,11 @@ public final class EntityManager {
}
/**
* Calls the player initialization callbacks and the event {@link AsyncPlayerPreLoginEvent}.
* If the {@link Player} hasn't been kicked, add him to the waiting list.
* <p>
* Can be considered as a pre-init thing,
* currently executed in {@link ConnectionManager#createPlayer(UUID, String, PlayerConnection)}
* and {@link net.minestom.server.entity.fakeplayer.FakePlayer#initPlayer(UUID, String, FakePlayerOption, Consumer)}.
* Adds a player into the waiting list, to be handled during the next server tick.
*
* @param player the {@link Player player} to add to the waiting list
* @param player the {@link Player player} to add into the waiting list
*/
public void addWaitingPlayer(@NotNull Player player) {
// Init player (register events)
for (Consumer<Player> playerInitialization : MinecraftServer.getConnectionManager().getPlayerInitializations()) {
playerInitialization.accept(player);
}
AsyncUtils.runAsync(() -> {
// Call pre login event
AsyncPlayerPreLoginEvent asyncPlayerPreLoginEvent = new AsyncPlayerPreLoginEvent(player, player.getUsername(), player.getUuid());
player.callEvent(AsyncPlayerPreLoginEvent.class, asyncPlayerPreLoginEvent);
// Ignore the player if he has been disconnected (kick)
final boolean online = player.isOnline();
if (!online)
return;
// Change UUID/Username based on the event
{
final String username = asyncPlayerPreLoginEvent.getUsername();
final UUID uuid = asyncPlayerPreLoginEvent.getPlayerUuid();
if (!player.getUsername().equals(username)) {
player.setUsername(username);
}
if (!player.getUuid().equals(uuid)) {
player.setUuid(uuid);
}
}
// Add the player to the waiting list
this.waitingPlayers.add(player);
});
this.waitingPlayers.add(player);
}
}

View File

@ -31,10 +31,12 @@ import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.listener.PlayerDiggingListener;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.ConnectionState;
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.LoginDisconnectPacket;
import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
@ -1286,7 +1288,7 @@ public class Player extends LivingEntity implements CommandSender {
*
* @param username the new player name
*/
protected void setUsername(@NotNull String username) {
public void setUsernameField(@NotNull String username) {
this.username = username;
}
@ -1740,8 +1742,16 @@ public class Player extends LivingEntity implements CommandSender {
* @param text the kick reason
*/
public void kick(@NotNull JsonMessage text) {
DisconnectPacket disconnectPacket = new DisconnectPacket();
disconnectPacket.message = text;
final ConnectionState connectionState = playerConnection.getConnectionState();
// Packet type depends on the current player connection state
final ServerPacket disconnectPacket;
if (connectionState == ConnectionState.LOGIN) {
disconnectPacket = new LoginDisconnectPacket(text);
} else {
disconnectPacket = new DisconnectPacket(text);
}
playerConnection.sendPacket(disconnectPacket);
playerConnection.refreshOnline(false);
}

View File

@ -3,12 +3,10 @@ package net.minestom.server.entity;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.minestom.server.utils.url.URLUtils;
import net.minestom.server.utils.mojang.MojangUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Objects;
/**
@ -35,27 +33,19 @@ public class PlayerSkin {
*/
@Nullable
public static PlayerSkin fromUuid(@NotNull String uuid) {
final String url = "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false";
final JsonObject jsonObject = MojangUtils.fromUuid(uuid);
final JsonArray propertiesArray = jsonObject.get("properties").getAsJsonArray();
try {
final String response = URLUtils.getText(url);
final JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject();
final JsonArray propertiesArray = jsonObject.get("properties").getAsJsonArray();
for (JsonElement jsonElement : propertiesArray) {
final JsonObject propertyObject = jsonElement.getAsJsonObject();
final String name = propertyObject.get("name").getAsString();
if (!name.equals("textures"))
continue;
final String textureValue = propertyObject.get("value").getAsString();
final String signatureValue = propertyObject.get("signature").getAsString();
return new PlayerSkin(textureValue, signatureValue);
}
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
for (JsonElement jsonElement : propertiesArray) {
final JsonObject propertyObject = jsonElement.getAsJsonObject();
final String name = propertyObject.get("name").getAsString();
if (!name.equals("textures"))
continue;
final String textureValue = propertyObject.get("value").getAsString();
final String signatureValue = propertyObject.get("signature").getAsString();
return new PlayerSkin(textureValue, signatureValue);
}
return null;
}
/**
@ -66,19 +56,10 @@ public class PlayerSkin {
*/
@Nullable
public static PlayerSkin fromUsername(@NotNull String username) {
final String url = "https://api.mojang.com/users/profiles/minecraft/" + username;
try {
// Retrieve the mojang uuid from the name
final String response = URLUtils.getText(url);
final JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject();
final String uuid = jsonObject.get("id").getAsString();
// Retrieve the skin data from the mojang uuid
return fromUuid(uuid);
} catch (IOException e) {
e.printStackTrace();
return null;
}
final JsonObject jsonObject = MojangUtils.fromUsername(username);
final String uuid = jsonObject.get("id").getAsString();
// Retrieve the skin data from the mojang uuid
return fromUuid(uuid);
}
/**

View File

@ -3,6 +3,7 @@ package net.minestom.server.entity.fakeplayer;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerSpawnEvent;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.player.FakePlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.time.TimeUnit;
@ -22,6 +23,8 @@ import java.util.function.Consumer;
*/
public class FakePlayer extends Player {
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
private final FakePlayerOption option;
private final FakePlayerController fakePlayerController;
@ -32,16 +35,29 @@ public class FakePlayer extends Player {
* @param username The username for the fake player.
* @param option Any option for the fake player.
*/
private FakePlayer(@NotNull UUID uuid, @NotNull String username, @NotNull FakePlayerOption option) {
private FakePlayer(@NotNull UUID uuid, @NotNull String username,
@NotNull FakePlayerOption option,
@Nullable Consumer<FakePlayer> spawnCallback) {
super(uuid, username, new FakePlayerConnection());
this.option = option;
this.fakePlayerController = new FakePlayerController(this);
if (option.isRegistered()) {
MinecraftServer.getConnectionManager().createPlayer(this);
if (spawnCallback != null) {
addEventCallback(PlayerSpawnEvent.class,
event -> {
if (event.isFirstSpawn()) {
spawnCallback.accept(this);
}
});
}
if (option.isRegistered()) {
CONNECTION_MANAGER.registerPlayer(this);
}
CONNECTION_MANAGER.startPlayState(this);
}
/**
@ -53,18 +69,7 @@ public class FakePlayer extends Player {
*/
public static void initPlayer(@NotNull UUID uuid, @NotNull String username,
@NotNull FakePlayerOption option, @Nullable Consumer<FakePlayer> spawnCallback) {
final FakePlayer fakePlayer = new FakePlayer(uuid, username, option);
if (spawnCallback != null) {
fakePlayer.addEventCallback(PlayerSpawnEvent.class,
event -> {
if (event.isFirstSpawn()) {
spawnCallback.accept(fakePlayer);
}
});
}
MinecraftServer.getEntityManager().addWaitingPlayer(fakePlayer);
new FakePlayer(uuid, username, option, spawnCallback);
}
/**

View File

@ -17,6 +17,7 @@ import net.minestom.server.network.packet.server.play.DisconnectPacket;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.utils.PacketUtils;
import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.callback.validator.PlayerValidator;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -318,27 +319,11 @@ public final class ConnectionManager {
*
* @param player the player to add
*/
public synchronized void createPlayer(@NotNull Player player) {
public synchronized void registerPlayer(@NotNull Player player) {
this.players.add(player);
this.connectionPlayerMap.put(player.getPlayerConnection(), player);
}
/**
* Creates a {@link Player} object and register it.
*
* @param uuid the new player uuid
* @param username the new player username
* @param connection the new player connection
* @return the newly created player object
*/
@NotNull
public Player createPlayer(@NotNull UUID uuid, @NotNull String username, @NotNull PlayerConnection connection) {
final Player player = getPlayerProvider().createPlayer(uuid, username, connection);
createPlayer(player);
MinecraftServer.getEntityManager().addWaitingPlayer(player);
return player;
}
/**
* Removes a {@link Player} from the players list.
* <p>
@ -357,28 +342,99 @@ public final class ConnectionManager {
}
/**
* Sends a {@link LoginSuccessPacket} and change the connection state to {@link ConnectionState#PLAY}.
* Calls the player initialization callbacks and the event {@link AsyncPlayerPreLoginEvent}.
* <p>
* Sends a {@link LoginSuccessPacket} if successful (not kicked)
* 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
* @return the newly created player object
* @param player the player
*/
@NotNull
public Player startPlayState(@NotNull PlayerConnection connection, @NotNull UUID uuid, @NotNull String username) {
LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(uuid, username);
connection.sendPacket(loginSuccessPacket);
public void startPlayState(@NotNull Player player) {
// Init player (register events)
for (Consumer<Player> playerInitialization : getPlayerInitializations()) {
playerInitialization.accept(player);
}
connection.setConnectionState(ConnectionState.PLAY);
return createPlayer(uuid, username, connection);
AsyncUtils.runAsync(() -> {
String username = player.getUsername();
UUID uuid = player.getUuid();
// Call pre login event
AsyncPlayerPreLoginEvent asyncPlayerPreLoginEvent = new AsyncPlayerPreLoginEvent(player, username, uuid);
player.callEvent(AsyncPlayerPreLoginEvent.class, asyncPlayerPreLoginEvent);
// Close the player channel if he has been disconnected (kick)
final boolean online = player.isOnline();
if (!online) {
final PlayerConnection playerConnection = player.getPlayerConnection();
if (playerConnection instanceof NettyPlayerConnection) {
((NettyPlayerConnection) playerConnection).getChannel().flush();
}
//playerConnection.disconnect();
return;
}
// Change UUID/Username based on the event
{
final String eventUsername = asyncPlayerPreLoginEvent.getUsername();
final UUID eventUuid = asyncPlayerPreLoginEvent.getPlayerUuid();
if (!player.getUsername().equals(eventUsername)) {
player.setUsernameField(eventUsername);
username = eventUsername;
}
if (!player.getUuid().equals(eventUuid)) {
player.setUuid(eventUuid);
uuid = eventUuid;
}
}
// Send login success packet
{
final PlayerConnection connection = player.getPlayerConnection();
LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(uuid, username);
connection.sendPacket(loginSuccessPacket);
connection.setConnectionState(ConnectionState.PLAY);
}
// Add the player to the waiting list
MinecraftServer.getEntityManager().addWaitingPlayer(player);
});
}
/**
* Shutdowns the connection manager by kicking all the currently connected players
* Creates a {@link Player} using the defined {@link PlayerProvider}
* and execute {@link #startPlayState(Player)}.
*
* @param register true to register the newly created player in {@link ConnectionManager} lists
* @return the newly created player object
* @see #startPlayState(Player)
*/
@NotNull
public Player startPlayState(@NotNull PlayerConnection connection,
@NotNull UUID uuid, @NotNull String username,
boolean register) {
final Player player = getPlayerProvider().createPlayer(uuid, username, connection);
if (register) {
registerPlayer(player);
}
startPlayState(player);
return player;
}
/**
* Shutdowns the connection manager by kicking all the currently connected players.
*/
public void shutdown() {
DisconnectPacket disconnectPacket = new DisconnectPacket();
disconnectPacket.message = getShutdownText();
DisconnectPacket disconnectPacket = new DisconnectPacket(getShutdownText());
for (Player player : getOnlinePlayers()) {
final PlayerConnection playerConnection = player.getPlayerConnection();
if (playerConnection instanceof NettyPlayerConnection) {

View File

@ -54,7 +54,7 @@ public class EncryptionResponsePacket implements ClientPreplayPacket {
nettyConnection.setEncryptionKey(getSecretKey());
MinecraftServer.LOGGER.info("UUID of player {} is {}", loginUsername, gameProfile.getId());
CONNECTION_MANAGER.startPlayState(connection, gameProfile.getId(), gameProfile.getName());
CONNECTION_MANAGER.startPlayState(connection, gameProfile.getId(), gameProfile.getName(), true);
}
} catch (AuthenticationUnavailableException e) {
e.printStackTrace();

View File

@ -77,7 +77,7 @@ public class LoginPluginResponsePacket implements ClientPreplayPacket {
final UUID uuid = playerUuid != null ?
playerUuid : CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
Player player = CONNECTION_MANAGER.startPlayState(connection, uuid, username);
Player player = CONNECTION_MANAGER.startPlayState(connection, uuid, username, true);
player.setSkin(playerSkin);
} else {
LoginDisconnectPacket disconnectPacket = new LoginDisconnectPacket(INVALID_PROXY_RESPONSE);

View File

@ -89,7 +89,7 @@ public class LoginStartPacket implements ClientPreplayPacket {
((NettyPlayerConnection) connection).getBungeeUuid() :
CONNECTION_MANAGER.getPlayerConnectionUuid(connection, username);
Player player = CONNECTION_MANAGER.startPlayState(connection, playerUuid, username);
Player player = CONNECTION_MANAGER.startPlayState(connection, playerUuid, username, true);
if (bungee && isNettyClient) {
player.setSkin(((NettyPlayerConnection) connection).getBungeeSkin());
}

View File

@ -8,7 +8,7 @@ import org.jetbrains.annotations.NotNull;
public class LoginDisconnectPacket implements ServerPacket {
private String kickMessage; // JSON text
private final String kickMessage; // JSON text
public LoginDisconnectPacket(@NotNull String kickMessage) {
this.kickMessage = kickMessage;

View File

@ -10,6 +10,10 @@ public class DisconnectPacket implements ServerPacket {
public JsonMessage message; // Only text
public DisconnectPacket(@NotNull JsonMessage message){
this.message = message;
}
@Override
public void write(@NotNull BinaryWriter writer) {
writer.writeSizedString(message.toString());

View File

@ -9,8 +9,6 @@ import net.minestom.server.listener.manager.ServerPacketConsumer;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.ConnectionState;
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.DisconnectPacket;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -58,20 +56,14 @@ public abstract class PlayerConnection {
this.packetCounter.set(0);
if (count > MinecraftServer.getRateLimit()) {
// Sent too many packets
if (connectionState == ConnectionState.LOGIN) {
sendPacket(new LoginDisconnectPacket("Too Many Packets"));
} else {
DisconnectPacket disconnectPacket = new DisconnectPacket();
disconnectPacket.message = rateLimitKickMessage;
sendPacket(disconnectPacket);
}
player.kick(rateLimitKickMessage);
disconnect();
refreshOnline(false);
}
}
}
}
@NotNull
public AtomicInteger getPacketCounter() {
return packetCounter;
}

View File

@ -0,0 +1,41 @@
package net.minestom.server.utils.mojang;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.minestom.server.utils.url.URLUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
/**
* Utils class using mojang API.
*/
public final class MojangUtils {
@Nullable
public static JsonObject fromUuid(@NotNull String uuid) {
final String url = "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false";
try {
final String response = URLUtils.getText(url);
return JsonParser.parseString(response).getAsJsonObject();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Nullable
public static JsonObject fromUsername(@NotNull String username) {
final String url = "https://api.mojang.com/users/profiles/minecraft/" + username;
try {
// Retrieve the mojang uuid from the name
final String response = URLUtils.getText(url);
return JsonParser.parseString(response).getAsJsonObject();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}

View File

@ -7,11 +7,10 @@ import net.minestom.server.benchmark.BenchmarkManager;
import net.minestom.server.chat.ColoredText;
import net.minestom.server.entity.*;
import net.minestom.server.entity.damage.DamageType;
import net.minestom.server.entity.fakeplayer.FakePlayer;
import net.minestom.server.entity.type.monster.EntityZombie;
import net.minestom.server.event.GlobalEventHandler;
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.event.entity.EntityPotionAddEvent;
import net.minestom.server.event.entity.EntityPotionRemoveEvent;
import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.event.item.PickupItemEvent;
import net.minestom.server.event.player.*;
@ -242,6 +241,8 @@ public class PlayerInit {
globalEventHandler.addEventCallback(PlayerUseItemEvent.class, useEvent -> {
final Player player = useEvent.getPlayer();
player.sendMessage("Using item in air: " + useEvent.getItemStack().getMaterial());
FakePlayer.initPlayer(UUID.randomUUID(), "test", null);
});
globalEventHandler.addEventCallback(PlayerUseItemOnBlockEvent.class, useEvent -> {
@ -265,16 +266,22 @@ public class PlayerInit {
}
});
globalEventHandler.addEventCallback(EntityPotionAddEvent.class, event -> {
if (event.getEntity() instanceof Player) {
((Player) event.getEntity()).sendMessage("Potion added: " + event.getPotion().getEffect());
}
globalEventHandler.addEventCallback(PlayerLoginEvent.class, event -> {
//event.setPlayerUuid(UUID.randomUUID());
//System.out.println("random "+event.getPlayerUuid());
System.out.println("lOGIN EVENT");
});
globalEventHandler.addEventCallback(EntityPotionRemoveEvent.class, event -> {
if (event.getEntity() instanceof Player) {
((Player) event.getEntity()).sendMessage("Potion removed: " + event.getPotion().getEffect());
}
globalEventHandler.addEventCallback(AsyncPlayerPreLoginEvent.class, event -> {
//event.setPlayerUuid(UUID.randomUUID());
//System.out.println("random "+event.getPlayerUuid());
//event.getPlayer().kick("test");
System.out.println("PRElOGIN EVENT");
});
globalEventHandler.addEventCallback(PlayerSkinInitEvent.class, event -> {
//event.setSkin(PlayerSkin.fromUsername("TheMode911"));
});
}