diff --git a/build-data/paper.at b/build-data/paper.at index 1692a54925..594a83bd5f 100644 --- a/build-data/paper.at +++ b/build-data/paper.at @@ -104,6 +104,9 @@ public net.minecraft.server.level.ServerPlayer wardenSpawnTracker public net.minecraft.server.level.ServerPlayer$RespawnPosAngle public net.minecraft.server.level.ServerPlayerGameMode level public net.minecraft.server.level.TicketType register(Ljava/lang/String;JZLnet/minecraft/server/level/TicketType$TicketUse;)Lnet/minecraft/server/level/TicketType; +public net.minecraft.server.network.ServerConfigurationPacketListenerImpl clientInformation +public net.minecraft.server.network.ServerConfigurationPacketListenerImpl currentTask +public net.minecraft.server.network.ServerConfigurationPacketListenerImpl finishCurrentTask(Lnet/minecraft/server/network/ConfigurationTask$Type;)V public net.minecraft.server.network.ServerGamePacketListenerImpl isChatMessageIllegal(Ljava/lang/String;)Z public net.minecraft.server.network.ServerLoginPacketListenerImpl authenticatedProfile public net.minecraft.server.network.ServerLoginPacketListenerImpl connection diff --git a/paper-api/src/main/java/io/papermc/paper/connection/PlayerCommonConnection.java b/paper-api/src/main/java/io/papermc/paper/connection/PlayerCommonConnection.java new file mode 100644 index 0000000000..cae2e5ba5d --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/connection/PlayerCommonConnection.java @@ -0,0 +1,45 @@ +package io.papermc.paper.connection; + +import java.util.Map; + +import com.destroystokyo.paper.ClientOption; +import org.bukkit.ServerLinks; + +/** + * Represents a connection that has properties shared between the GAME and CONFIG stage. + */ +public interface PlayerCommonConnection extends WritablePlayerCookieConnection, ReadablePlayerCookieConnection { + + /** + * Sends data to appear in this connection's report logs. + * This is useful for debugging server state that may be causing + * player disconnects. + *

+ * These are formatted as key - value, where keys are limited to a length of 128 characters, + * values are limited to 4096, and 32 maximum entries can be sent. + * + * @param details report details + */ + void sendReportDetails(Map details); + + /** + * Sends the given server links to this connection. + * + * @param links links to send + */ + void sendLinks(ServerLinks links); + + /** + * Transfers this connection to another server. + * + * @param host host + * @param port port + */ + void transfer(String host, int port); + + /** + * @param type client option + * @return the client option value of the player + */ + T getClientOption(ClientOption type); +} diff --git a/paper-api/src/main/java/io/papermc/paper/connection/PlayerConfigurationConnection.java b/paper-api/src/main/java/io/papermc/paper/connection/PlayerConfigurationConnection.java new file mode 100644 index 0000000000..dc68010fca --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/connection/PlayerConfigurationConnection.java @@ -0,0 +1,37 @@ +package io.papermc.paper.connection; + +import com.destroystokyo.paper.ClientOption; +import com.destroystokyo.paper.profile.PlayerProfile; +import net.kyori.adventure.audience.Audience; + +public interface PlayerConfigurationConnection extends PlayerCommonConnection { + + /** + * Returns the audience representing the player in configuration mode. + * This can be used to interact with the Adventure API during the configuration stage. + * This is guaranteed to be an instance of {@link PlayerConfigurationConnection} + * + * @return the configuring player audience + */ + Audience getAudience(); + + /** + * Gets the profile for this connection. + * + * @return profile + */ + PlayerProfile getProfile(); + + /** + * Clears the players chat history and their local chat. + */ + void clearChat(); + + /** + * Completes the configuration for this player, which will cause this player to reenter the game. + *

+ * Note, this should be only be called if you are reconfiguring the player. + */ + void completeReconfiguration(); + +} diff --git a/paper-api/src/main/java/io/papermc/paper/connection/PlayerConnection.java b/paper-api/src/main/java/io/papermc/paper/connection/PlayerConnection.java new file mode 100644 index 0000000000..9430d33edf --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/connection/PlayerConnection.java @@ -0,0 +1,60 @@ +package io.papermc.paper.connection; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import net.kyori.adventure.text.Component; +import org.jspecify.annotations.Nullable; + +public interface PlayerConnection { + + /** + * Disconnects the player connection. + *

+ * Note that calling this during connection related events may caused undefined behavior. + * + * @param component disconnect reason + */ + void disconnect(Component component); + + /** + * Gets if this connection originated from a transferred connection. + *

+ * Do note that this is sent and stored on the client. + * + * @return is transferred + */ + boolean isTransferred(); + + /** + * Gets the raw remote address of the connection. This may be a proxy address + * or a Unix domain socket address, depending on how the channel was established. + * + * @return the remote {@link SocketAddress} of the channel + */ + SocketAddress getAddress(); + + /** + * Gets the real client address of the player. If the connection is behind a proxy, + * this will be the actual player’s IP address extracted from the proxy handshake. + * + * @return the client {@link InetSocketAddress} + */ + InetSocketAddress getClientAddress(); + + /** + * Returns the virtual host the client is connected to. + * + *

The virtual host refers to the hostname/port the client used to + * connect to the server.

+ * + * @return The client's virtual host, or {@code null} if unknown + */ + @Nullable InetSocketAddress getVirtualHost(); + + /** + * Gets the socket address of this player's proxy + * + * @return the player's proxy address, null if the server doesn't have Proxy Protocol enabled, or the player didn't connect to an HAProxy instance + */ + @Nullable InetSocketAddress getHAProxyAddress(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/connection/PlayerGameConnection.java b/paper-api/src/main/java/io/papermc/paper/connection/PlayerGameConnection.java new file mode 100644 index 0000000000..da74c5f390 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/connection/PlayerGameConnection.java @@ -0,0 +1,21 @@ +package io.papermc.paper.connection; + +import org.bukkit.entity.Player; + +public interface PlayerGameConnection extends PlayerCommonConnection { + + /** + * Bumps the player to the configuration stage. + *

+ * This will, by default, cause the player to stay until their connection is released by + * {@link PlayerConfigurationConnection#completeReconfiguration()} + */ + void reenterConfiguration(); + + /** + * Gets the player that is associated with this game connection. + * + * @return player + */ + Player getPlayer(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/connection/PlayerLoginConnection.java b/paper-api/src/main/java/io/papermc/paper/connection/PlayerLoginConnection.java new file mode 100644 index 0000000000..dfe95ad426 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/connection/PlayerLoginConnection.java @@ -0,0 +1,23 @@ +package io.papermc.paper.connection; + +import com.destroystokyo.paper.profile.PlayerProfile; +import org.jspecify.annotations.Nullable; + +public interface PlayerLoginConnection extends ReadablePlayerCookieConnection { + + /** + * Gets the authenticated profile for this connection. + * This may return null depending on what stage this connection is at. + * + * @return authenticated profile, or null if not present + */ + @Nullable PlayerProfile getAuthenticatedProfile(); + + /** + * Gets the player profile that this connection is requesting to authenticate as. + * + * @return the unsafe unauthenticated profile, or null if not sent + */ + @Nullable + PlayerProfile getUnsafeProfile(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/connection/ReadablePlayerCookieConnection.java b/paper-api/src/main/java/io/papermc/paper/connection/ReadablePlayerCookieConnection.java new file mode 100644 index 0000000000..d51fe45989 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/connection/ReadablePlayerCookieConnection.java @@ -0,0 +1,18 @@ +package io.papermc.paper.connection; + +import java.util.concurrent.CompletableFuture; +import org.bukkit.NamespacedKey; + +public interface ReadablePlayerCookieConnection extends PlayerConnection { + + /** + * Retrieves a cookie from this connection. + * + * @param key the key identifying the cookie + * @return a {@link CompletableFuture} that will be completed when the + * Cookie response is received or otherwise available. If the cookie is not + * set in the client, the {@link CompletableFuture} will complete with a + * null value. + */ + CompletableFuture retrieveCookie(NamespacedKey key); +} diff --git a/paper-api/src/main/java/io/papermc/paper/connection/WritablePlayerCookieConnection.java b/paper-api/src/main/java/io/papermc/paper/connection/WritablePlayerCookieConnection.java new file mode 100644 index 0000000000..e9d3e7127b --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/connection/WritablePlayerCookieConnection.java @@ -0,0 +1,15 @@ +package io.papermc.paper.connection; + +import org.bukkit.NamespacedKey; + +public interface WritablePlayerCookieConnection extends PlayerConnection { + + /** + * Stores a cookie in this player's client. + * + * @param key the key identifying the cookie + * @param value the data to store in the cookie + * @throws IllegalStateException if a cookie cannot be stored at this time + */ + void storeCookie(NamespacedKey key, byte[] value); +} diff --git a/paper-api/src/main/java/io/papermc/paper/connection/package-info.java b/paper-api/src/main/java/io/papermc/paper/connection/package-info.java new file mode 100644 index 0000000000..bc3c1ad42a --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/connection/package-info.java @@ -0,0 +1,9 @@ +/** + * This package contains events related to player connections, such as joining and leaving the server. + */ +@ApiStatus.Experimental +@NullMarked +package io.papermc.paper.connection; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/io/papermc/paper/event/connection/PlayerConnectionValidateLoginEvent.java b/paper-api/src/main/java/io/papermc/paper/event/connection/PlayerConnectionValidateLoginEvent.java new file mode 100644 index 0000000000..ed06472e4c --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/connection/PlayerConnectionValidateLoginEvent.java @@ -0,0 +1,86 @@ +package io.papermc.paper.event.connection; + +import io.papermc.paper.connection.PlayerConnection; +import net.kyori.adventure.text.Component; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.Nullable; + +/** + * Validates whether a player connection is able to log in. + *

+ * Called when is attempting to log in for the first time, or is finishing up + * being configured. + */ +public class PlayerConnectionValidateLoginEvent extends Event { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final PlayerConnection connection; + private @Nullable Component kickMessage; + + @ApiStatus.Internal + public PlayerConnectionValidateLoginEvent(final PlayerConnection connection, final @Nullable Component kickMessage) { + super(false); + this.connection = connection; + this.kickMessage = kickMessage; + } + + /** + * Gets the connection of the player in this event. + * Note, the type of this connection is not guaranteed to be stable across versions. + * Additionally, disconnecting the player through this connection / using any methods that may send packets + * is not supported. + * + * @return connection + */ + public PlayerConnection getConnection() { + return this.connection; + } + + /** + * Allows the player to log in. + * This skips any login validation checks. + */ + public void allow() { + this.kickMessage = null; + } + + /** + * Disallows the player from logging in, with the given reason + * + * @param message Kick message to display to the user + */ + public void kickMessage(final Component message) { + this.kickMessage = message; + } + + /** + * Gets the reason for why a player is not allowed to join the server. + * This will be null in the case that the player is allowed to log in. + * + * @return disallow reason + */ + public @Nullable Component getKickMessage() { + return this.kickMessage; + } + + /** + * Gets if the player is allowed to enter the next stage. + * + * @return if allowed + */ + public boolean isAllowed() { + return this.kickMessage == null; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/AsyncPlayerConnectionConfigureEvent.java b/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/AsyncPlayerConnectionConfigureEvent.java new file mode 100644 index 0000000000..02bae8e427 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/AsyncPlayerConnectionConfigureEvent.java @@ -0,0 +1,39 @@ +package io.papermc.paper.event.connection.configuration; + +import io.papermc.paper.connection.PlayerConfigurationConnection; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.ApiStatus; + +/** + * An event that allows you to configure the player. + * This is async and allows you to run configuration code on the player. + * Once this event has finished execution, the player connection will continue. + *

+ * This occurs after configuration, but before the player has entered the world. + */ +public class AsyncPlayerConnectionConfigureEvent extends Event { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final PlayerConfigurationConnection connection; + + @ApiStatus.Internal + public AsyncPlayerConnectionConfigureEvent(final PlayerConfigurationConnection connection) { + super(true); + this.connection = connection; + } + + public PlayerConfigurationConnection getConnection() { + return this.connection; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/PlayerConnectionInitialConfigureEvent.java b/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/PlayerConnectionInitialConfigureEvent.java new file mode 100644 index 0000000000..ac63562936 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/PlayerConnectionInitialConfigureEvent.java @@ -0,0 +1,36 @@ +package io.papermc.paper.event.connection.configuration; + +import io.papermc.paper.connection.PlayerConfigurationConnection; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.ApiStatus; + +/** + * Indicates that this player is being configured for the first time, meaning that the connection will start being configured automatically + */ +public class PlayerConnectionInitialConfigureEvent extends Event { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final PlayerConfigurationConnection connection; + + @ApiStatus.Internal + public PlayerConnectionInitialConfigureEvent(final PlayerConfigurationConnection connection) { + super(!Bukkit.isPrimaryThread()); + this.connection = connection; + } + + public PlayerConfigurationConnection getConnection() { + return this.connection; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/PlayerConnectionReconfigureEvent.java b/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/PlayerConnectionReconfigureEvent.java new file mode 100644 index 0000000000..e6fa54aa1a --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/PlayerConnectionReconfigureEvent.java @@ -0,0 +1,37 @@ +package io.papermc.paper.event.connection.configuration; + +import io.papermc.paper.connection.PlayerConfigurationConnection; +import org.bukkit.Bukkit; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.ApiStatus; + +/** + * Indicates that this player is being reconfigured, meaning that this connection will be held in the configuration + * stage unless kicked out through {@link PlayerConfigurationConnection#completeReconfiguration()} + */ +public class PlayerConnectionReconfigureEvent extends Event { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final PlayerConfigurationConnection connection; + + @ApiStatus.Internal + public PlayerConnectionReconfigureEvent(final PlayerConfigurationConnection connection) { + super(!Bukkit.isPrimaryThread()); + this.connection = connection; + } + + public PlayerConfigurationConnection getConnection() { + return this.connection; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/package-info.java b/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/package-info.java new file mode 100644 index 0000000000..41c6599aa5 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/connection/configuration/package-info.java @@ -0,0 +1,9 @@ +/** + * Configuration connection events. + */ +@ApiStatus.Experimental +@NullMarked +package io.papermc.paper.event.connection.configuration; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/io/papermc/paper/event/connection/package-info.java b/paper-api/src/main/java/io/papermc/paper/event/connection/package-info.java new file mode 100644 index 0000000000..d67749daac --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/connection/package-info.java @@ -0,0 +1,9 @@ +/** + * Common connection events. + */ +@NullMarked +@ApiStatus.Experimental +package io.papermc.paper.event.connection; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerServerFullCheckEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerServerFullCheckEvent.java new file mode 100644 index 0000000000..8bee45911f --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerServerFullCheckEvent.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2017 - Daniel Ennis (Aikar) - MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package io.papermc.paper.event.player; + +import com.destroystokyo.paper.profile.PlayerProfile; +import net.kyori.adventure.text.Component; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.NullMarked; + +/** + * Fires when computing if a server is currently considered full for a player. + */ +@NullMarked +public class PlayerServerFullCheckEvent extends Event { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final PlayerProfile profile; + private Component kickMessage; + private boolean allow; + + @ApiStatus.Internal + public PlayerServerFullCheckEvent(final PlayerProfile profile, final Component kickMessage, final boolean shouldKick) { + this.profile = profile; + this.kickMessage = kickMessage; + this.allow = !shouldKick; + } + + /** + * @return the currently planned message to send to the user if they are unable to join the server + */ + @Contract(pure = true) + public Component kickMessage() { + return this.kickMessage; + } + + /** + * @param kickMessage The message to send to the player on kick if not able to join. + */ + public void deny(final Component kickMessage) { + this.kickMessage = kickMessage; + } + + /** + * @return The profile of the player trying to connect + */ + public PlayerProfile getPlayerProfile() { + return this.profile; + } + + /** + * Sets whether the player is able to join this server. + * @param allow can join the server + */ + public void allow(final boolean allow) { + this.allow = allow; + } + + /** + * Gets if the player is currently able to join the server. + * + * @return can join the server, or false if the server should be considered full + */ + public boolean isAllowed() { + return this.allow; + } + + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } +} diff --git a/paper-api/src/main/java/org/bukkit/entity/Player.java b/paper-api/src/main/java/org/bukkit/entity/Player.java index d34419693f..acb7a39ac6 100644 --- a/paper-api/src/main/java/org/bukkit/entity/Player.java +++ b/paper-api/src/main/java/org/bukkit/entity/Player.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import io.papermc.paper.connection.PlayerGameConnection; import io.papermc.paper.entity.LookAnchor; import io.papermc.paper.entity.PlayerGiveResult; import io.papermc.paper.math.Position; @@ -3925,4 +3926,12 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param score New death screen score of player */ void setDeathScreenScore(int score); + + /** + * Gets the game connection for this player. + * + * @return the game connection + */ + @ApiStatus.Experimental + PlayerGameConnection getConnection(); } diff --git a/paper-api/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java b/paper-api/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java index cf7593ce00..cd5eb46544 100644 --- a/paper-api/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java @@ -5,6 +5,7 @@ import java.util.UUID; import com.destroystokyo.paper.profile.PlayerProfile; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import io.papermc.paper.connection.PlayerLoginConnection; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.ApiStatus; @@ -33,6 +34,7 @@ public class AsyncPlayerPreLoginEvent extends Event { private Result result; private Component message; private PlayerProfile profile; + private final PlayerLoginConnection playerLoginConnection; @ApiStatus.Internal @Deprecated(since = "1.7.5", forRemoval = true) @@ -60,11 +62,11 @@ public class AsyncPlayerPreLoginEvent extends Event { @ApiStatus.Internal @Deprecated(forRemoval = true) public AsyncPlayerPreLoginEvent(@NotNull final String name, @NotNull final InetAddress ipAddress, @NotNull final InetAddress rawAddress, @NotNull final UUID uniqueId, boolean transferred, @NotNull com.destroystokyo.paper.profile.PlayerProfile profile) { - this(name, ipAddress, rawAddress, uniqueId, transferred, profile, ""); + this(name, ipAddress, rawAddress, uniqueId, transferred, profile, "", null); } @ApiStatus.Internal - public AsyncPlayerPreLoginEvent(@NotNull final String name, @NotNull final InetAddress ipAddress, @NotNull final InetAddress rawAddress, @NotNull final UUID uniqueId, boolean transferred, @NotNull com.destroystokyo.paper.profile.PlayerProfile profile, @NotNull String hostname) { + public AsyncPlayerPreLoginEvent(@NotNull final String name, @NotNull final InetAddress ipAddress, @NotNull final InetAddress rawAddress, @NotNull final UUID uniqueId, boolean transferred, @NotNull com.destroystokyo.paper.profile.PlayerProfile profile, @NotNull String hostname, final PlayerLoginConnection playerLoginConnection) { super(true); this.result = Result.ALLOWED; this.message = Component.empty(); @@ -73,6 +75,7 @@ public class AsyncPlayerPreLoginEvent extends Event { this.rawAddress = rawAddress; this.hostname = hostname; this.transferred = transferred; + this.playerLoginConnection = playerLoginConnection; } /** @@ -301,6 +304,16 @@ public class AsyncPlayerPreLoginEvent extends Event { return this.transferred; } + /** + * Gets the connection for the player logging in. + * @return connection + */ + @ApiStatus.Experimental + @NotNull + public PlayerLoginConnection getConnection() { + return playerLoginConnection; + } + @NotNull @Override public HandlerList getHandlers() { diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerLinksSendEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerLinksSendEvent.java index 6fc2aa25da..9fe9133ffb 100644 --- a/paper-api/src/main/java/org/bukkit/event/player/PlayerLinksSendEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerLinksSendEvent.java @@ -1,7 +1,9 @@ package org.bukkit.event.player; +import io.papermc.paper.connection.PlayerCommonConnection; +import io.papermc.paper.connection.PlayerConfigurationConnection; import org.bukkit.ServerLinks; -import org.bukkit.entity.Player; +import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -10,18 +12,28 @@ import org.jetbrains.annotations.NotNull; * This event is called when the list of links is sent to the player. */ @ApiStatus.Experimental -public class PlayerLinksSendEvent extends PlayerEvent { +public class PlayerLinksSendEvent extends Event { private static final HandlerList HANDLER_LIST = new HandlerList(); private final ServerLinks links; + private final PlayerCommonConnection connection; @ApiStatus.Internal - public PlayerLinksSendEvent(@NotNull final Player player, @NotNull final ServerLinks links) { - super(player); + public PlayerLinksSendEvent(@NotNull final PlayerConfigurationConnection connection, @NotNull final ServerLinks links) { + this.connection = connection; this.links = links; } + /** + * Gets the connection that received the links. + * @return connection + */ + @NotNull + public PlayerCommonConnection getConnection() { + return connection; + } + /** * Gets the links to be sent, for modification. * diff --git a/paper-api/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java b/paper-api/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java index 996fffc845..7f2e0d895d 100644 --- a/paper-api/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java +++ b/paper-api/src/main/java/org/bukkit/event/player/PlayerLoginEvent.java @@ -1,6 +1,8 @@ package org.bukkit.event.player; import java.net.InetAddress; +import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; +import org.bukkit.Warning; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.entity.Player; @@ -14,7 +16,15 @@ import org.jetbrains.annotations.NotNull; * Note that this event is called early in the player initialization * process. It is recommended that most options involving the Player * entity be postponed to the {@link PlayerJoinEvent} instead. + * @deprecated Use {@link PlayerConnectionValidateLoginEvent} to handle pre-login logic + * (e.g. authentication or ban checks), or {@link io.papermc.paper.event.player.PlayerServerFullCheckEvent} to allow + * players to bypass the server's maximum player limit. + * Minecraft triggers this twice internally, using this event skips one of the validation checks done by the server. + * Additionally, this event causes the full player entity to be created much earlier than it would be in Vanilla, + * leaving it with mostly disfunctional methods and state. */ +@Warning(reason = "Listening to this event causes the player to be created early.") +@Deprecated(since = "1.21.6") public class PlayerLoginEvent extends PlayerEvent { private static final HandlerList HANDLER_LIST = new HandlerList(); diff --git a/paper-api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java b/paper-api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java index 70463cd059..a9561e3ba4 100644 --- a/paper-api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java +++ b/paper-api/src/main/java/org/bukkit/plugin/messaging/PluginMessageListener.java @@ -1,5 +1,6 @@ package org.bukkit.plugin.messaging; +import io.papermc.paper.connection.PlayerCommonConnection; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -18,4 +19,16 @@ public interface PluginMessageListener { * @param message The raw message that was sent. */ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte @NotNull [] message); + + /** + * A method that will be invoked when a PluginMessageSource sends a plugin + * message on a registered channel. + * + * @param channel Channel that the message was sent through. + * @param connection Source of the message. + * @param message The raw message that was sent. + */ + default void onPluginMessageReceived(@NotNull String channel, @NotNull PlayerCommonConnection connection, byte @NotNull [] message) { + + } } diff --git a/paper-server/patches/features/0001-Optimize-Network-Manager-and-add-advanced-packet-sup.patch b/paper-server/patches/features/0001-Optimize-Network-Manager-and-add-advanced-packet-sup.patch index 296185dc6a..b839e8f1b0 100644 --- a/paper-server/patches/features/0001-Optimize-Network-Manager-and-add-advanced-packet-sup.patch +++ b/paper-server/patches/features/0001-Optimize-Network-Manager-and-add-advanced-packet-sup.patch @@ -28,7 +28,7 @@ and then catch exceptions and close if they fire. Part of this commit was authored by: Spottedleaf, sandtechnology diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java -index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141eb8c72b5 100644 +index 14a25f4d9c6da9b9d7646cf88b80a04f5b4d0823..682fb4cbf9975b21171ae210defd6e338de3b718 100644 --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java @@ -85,7 +85,7 @@ public class Connection extends SimpleChannelInboundHandler> { @@ -40,10 +40,10 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141 public Channel channel; public SocketAddress address; // Spigot start -@@ -145,6 +145,10 @@ public class Connection extends SimpleChannelInboundHandler> { - } - // Paper end - packet limiter +@@ -146,6 +146,10 @@ public class Connection extends SimpleChannelInboundHandler> { @Nullable public SocketAddress haProxyAddress; // Paper - Add API to get player's proxy address + public @Nullable java.util.Optional legacySavedLoginEventResultOverride; // Paper - playerloginevent + public @Nullable net.minecraft.server.level.ServerPlayer savedPlayerForLoginEventLegacy; // Paper - playerloginevent + // Paper start - Optimize network + public boolean isPending = true; + public boolean queueImmunity; @@ -51,7 +51,7 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141 public Connection(PacketFlow receiving) { this.receiving = receiving; -@@ -419,11 +423,38 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -420,11 +424,38 @@ public class Connection extends SimpleChannelInboundHandler> { } public void send(Packet packet, @Nullable ChannelFutureListener channelFutureListener, boolean flag) { @@ -93,7 +93,7 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141 } } -@@ -432,7 +463,7 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -433,7 +464,7 @@ public class Connection extends SimpleChannelInboundHandler> { this.flushQueue(); action.accept(this); } else { @@ -102,7 +102,7 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141 } } -@@ -446,21 +477,41 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -447,21 +478,41 @@ public class Connection extends SimpleChannelInboundHandler> { } private void doSendPacket(Packet packet, @Nullable ChannelFutureListener channelFutureListener, boolean flag) { @@ -148,7 +148,7 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141 } } -@@ -472,16 +523,57 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -473,16 +524,57 @@ public class Connection extends SimpleChannelInboundHandler> { } } @@ -211,7 +211,7 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141 private static final int MAX_PER_TICK = io.papermc.paper.configuration.GlobalConfiguration.get().misc.maxJoinsPerTick; // Paper - Buffer joins to world private static int joinAttemptsThisTick; // Paper - Buffer joins to world -@@ -551,6 +643,7 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -552,6 +644,7 @@ public class Connection extends SimpleChannelInboundHandler> { public void disconnect(DisconnectionDetails disconnectionDetails) { this.preparing = false; // Spigot @@ -219,7 +219,7 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141 if (this.channel == null) { this.delayedDisconnect = disconnectionDetails; } -@@ -739,7 +832,7 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -740,7 +833,7 @@ public class Connection extends SimpleChannelInboundHandler> { public void handleDisconnection() { if (this.channel != null && !this.channel.isOpen()) { if (this.disconnectionHandled) { @@ -228,7 +228,7 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141 } else { this.disconnectionHandled = true; PacketListener packetListener = this.getPacketListener(); -@@ -750,7 +843,7 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -751,7 +844,7 @@ public class Connection extends SimpleChannelInboundHandler> { ); packetListener1.onDisconnect(disconnectionDetails); } @@ -237,7 +237,7 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141 // Paper start - Add PlayerConnectionCloseEvent if (packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl commonPacketListener) { /* Player was logged in, either game listener or configuration listener */ -@@ -785,4 +878,97 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -786,4 +879,97 @@ public class Connection extends SimpleChannelInboundHandler> { public void setBandwidthLogger(LocalSampleLogger bandwithLogger) { this.bandwidthDebugMonitor = new BandwidthDebugMonitor(bandwithLogger); } diff --git a/paper-server/patches/features/0005-Use-Velocity-compression-and-cipher-natives.patch b/paper-server/patches/features/0005-Use-Velocity-compression-and-cipher-natives.patch index 2c16d1c118..abf97b350c 100644 --- a/paper-server/patches/features/0005-Use-Velocity-compression-and-cipher-natives.patch +++ b/paper-server/patches/features/0005-Use-Velocity-compression-and-cipher-natives.patch @@ -269,10 +269,10 @@ index bc674b08a41d5529fe06c6d3f077051cf4138f73..ea8a894158c44c2e7943dea43ecd8e1f + // Paper end - Use Velocity cipher } diff --git a/net/minecraft/network/Connection.java b/net/minecraft/network/Connection.java -index 235a076f982247552fef6d86d7e1b141eb8c72b5..34524dc5a503bebcec99ada0d9560d6f4df48cdf 100644 +index 682fb4cbf9975b21171ae210defd6e338de3b718..8bab2c26e10e8495fd39be470bcb02917fe56f40 100644 --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java -@@ -762,11 +762,22 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -763,11 +763,22 @@ public class Connection extends SimpleChannelInboundHandler> { return connection; } @@ -299,7 +299,7 @@ index 235a076f982247552fef6d86d7e1b141eb8c72b5..34524dc5a503bebcec99ada0d9560d6f public boolean isEncrypted() { return this.encrypted; -@@ -805,16 +816,17 @@ public class Connection extends SimpleChannelInboundHandler> { +@@ -806,16 +817,17 @@ public class Connection extends SimpleChannelInboundHandler> { // Paper end - add proper async disconnect public void setupCompression(int threshold, boolean validateDecompressed) { if (threshold >= 0) { @@ -336,10 +336,10 @@ index 7de11ba404f0b60e7f7b7c16954811a343688219..bd07e6a5aa1883786d789ea71711a0c0 this.channels .add( diff --git a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index 5d48487568994860c153351ad4c6c33bc8aa5309..7950f4f88d8a83ed5610b7af4e134557d32da3f0 100644 +index 0b0e57cc534862e265108f0e9cbb324ffbc2bcd1..ebf4d4516233c002b33084f1679044b23869d848 100644 --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -275,11 +275,9 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, +@@ -242,11 +242,9 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, } SecretKey secretKey = packet.getSecretKey(_private); diff --git a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch index 29aae5b778..f29705d6b2 100644 --- a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch @@ -27703,8 +27703,8 @@ index 6f7f92cc43c56a7453b289f205502d089474ef6d..b390ba657b8b880e431c84e9dd948ac9 private static final Logger LOGGER = LogUtils.getLogger(); private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_XZ = 32; private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10; -@@ -426,6 +426,36 @@ public class ServerPlayer extends Player { - public @Nullable String clientBrandName = null; // Paper - Brand support +@@ -424,6 +424,36 @@ public class ServerPlayer extends Player { + public @Nullable com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent public @Nullable org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event + // Paper start - rewrite chunk system @@ -28333,10 +28333,10 @@ index b30f56fbc1fd17259a1d05dc9155fffcab292ca1..11fed81a4696ba18440e755c3b8a5ca3 this.generatingStep = generatingStep; this.cache = cache; diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index 891d3e13057c6034c59a78e594935c433921de04..a3aa852b04e6deccccf995cfc57f95fa79860c04 100644 +index 21b7b2f88708eb9e29d4c327b9bca79119b7124d..5a60527c9feb3606d85488b3cf215c451ca033de 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java -@@ -1328,7 +1328,7 @@ public abstract class PlayerList { +@@ -1312,7 +1312,7 @@ public abstract class PlayerList { public void setViewDistance(int viewDistance) { this.viewDistance = viewDistance; @@ -28345,7 +28345,7 @@ index 891d3e13057c6034c59a78e594935c433921de04..a3aa852b04e6deccccf995cfc57f95fa for (ServerLevel serverLevel : this.server.getAllLevels()) { if (serverLevel != null) { -@@ -1339,7 +1339,7 @@ public abstract class PlayerList { +@@ -1323,7 +1323,7 @@ public abstract class PlayerList { public void setSimulationDistance(int simulationDistance) { this.simulationDistance = simulationDistance; diff --git a/paper-server/patches/features/0024-Incremental-chunk-and-player-saving.patch b/paper-server/patches/features/0024-Incremental-chunk-and-player-saving.patch index d05c65bf8b..2b92a0550b 100644 --- a/paper-server/patches/features/0024-Incremental-chunk-and-player-saving.patch +++ b/paper-server/patches/features/0024-Incremental-chunk-and-player-saving.patch @@ -83,7 +83,7 @@ index 34b7769663e235b93c6388ab0c92c00f0297e42f..dda8d38ef61672cc714d9e5a475f9b04 // Paper start - add close param this.save(progress, flush, skipSave, false); diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index c0b74d408340101bc3aac4cb4b7232c5cc78b08a..b70929df38389d789dad46c0a6d94f6c08aa7eba 100644 +index ff2f4a0fd221c0b632066cdc93719ec6d66c2ff7..9cbadb677d2ab3e9e3ee45df6647a1cbd28fcfd7 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -195,6 +195,7 @@ import org.slf4j.Logger; @@ -95,7 +95,7 @@ index c0b74d408340101bc3aac4cb4b7232c5cc78b08a..b70929df38389d789dad46c0a6d94f6c private static final int NEUTRAL_MOB_DEATH_NOTIFICATION_RADII_Y = 10; private static final int FLY_STAT_RECORDING_SPEED = 25; diff --git a/net/minecraft/server/players/PlayerList.java b/net/minecraft/server/players/PlayerList.java -index a3aa852b04e6deccccf995cfc57f95fa79860c04..b1524279c02cd3be82338a6bd0320cb125a134d5 100644 +index 18aaa9bfb08ce36177a5f357644093630ce91264..fe3f36b3cd90766f026c6383cfedc42fa7ae5508 100644 --- a/net/minecraft/server/players/PlayerList.java +++ b/net/minecraft/server/players/PlayerList.java @@ -489,6 +489,7 @@ public abstract class PlayerList { @@ -106,7 +106,7 @@ index a3aa852b04e6deccccf995cfc57f95fa79860c04..b1524279c02cd3be82338a6bd0320cb1 this.playerIo.save(player); ServerStatsCounter serverStatsCounter = player.getStats(); // CraftBukkit if (serverStatsCounter != null) { -@@ -1075,9 +1076,23 @@ public abstract class PlayerList { +@@ -1059,9 +1060,23 @@ public abstract class PlayerList { } public void saveAll() { diff --git a/paper-server/patches/features/0026-Optional-per-player-mob-spawns.patch b/paper-server/patches/features/0026-Optional-per-player-mob-spawns.patch index 93df896305..f9cc310f3f 100644 --- a/paper-server/patches/features/0026-Optional-per-player-mob-spawns.patch +++ b/paper-server/patches/features/0026-Optional-per-player-mob-spawns.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Optional per player mob spawns diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index 86ba02916c4a1c1311eeca5b1fb10a46626ba9ab..b40b94c49dc0997654aca0a1a0dffe482cd14eac 100644 +index 56a1d081a28e8b38384cfca732b103462693e322..c1e61a9ccea0c66a664439309593c7f7e6e9e867 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -243,11 +243,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -43,7 +43,7 @@ index 86ba02916c4a1c1311eeca5b1fb10a46626ba9ab..b40b94c49dc0997654aca0a1a0dffe48 protected ChunkGenerator generator() { return this.worldGenContext.generator(); diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 8e9988130d9b912e5b858cd7792bdcefdeb66ada..25d74f866546362a17505b5d4abf85382c0df20c 100644 +index 7c9a2eed4441f816723562e0012f918db265912e..cc8638a6dab16ffbdf6951fa10182bd5763df90f 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -541,9 +541,18 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -78,7 +78,7 @@ index 8e9988130d9b912e5b858cd7792bdcefdeb66ada..25d74f866546362a17505b5d4abf8538 profiler.popPush("tickSpawningChunks"); diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index b70929df38389d789dad46c0a6d94f6c08aa7eba..e96bb37bb7a7340a7ee33046820652ecd4accc53 100644 +index 9cbadb677d2ab3e9e3ee45df6647a1cbd28fcfd7..d2e78e9e113c9b3e35ae401f2eb055a98df12c06 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -406,6 +406,10 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @@ -90,8 +90,8 @@ index b70929df38389d789dad46c0a6d94f6c08aa7eba..e96bb37bb7a7340a7ee33046820652ec + public final int[] mobCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; + // Paper end - Optional per player mob spawns // CraftBukkit start - public org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection transferCookieConnection; public String displayName; + public net.kyori.adventure.text.Component adventure$displayName; // Paper diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java index 3a864c568cd66a680760bb4df2cb020e323e9a9d..c710e08ab48075ce7854e56826adb8f0364b025b 100644 --- a/net/minecraft/world/level/NaturalSpawner.java diff --git a/paper-server/patches/features/0027-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch b/paper-server/patches/features/0027-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch index 7a941a9142..a2da25bd63 100644 --- a/paper-server/patches/features/0027-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch +++ b/paper-server/patches/features/0027-Improve-cancelling-PreCreatureSpawnEvent-with-per-pl.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Improve cancelling PreCreatureSpawnEvent with per player mob diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java -index b40b94c49dc0997654aca0a1a0dffe482cd14eac..b72000ce6bdcb97b787bfab79236f60bdb4aa2ee 100644 +index c1e61a9ccea0c66a664439309593c7f7e6e9e867..eb352aa4296abc3ed4cf31c590bc0be66daf4de3 100644 --- a/net/minecraft/server/level/ChunkMap.java +++ b/net/minecraft/server/level/ChunkMap.java @@ -262,8 +262,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider @@ -37,7 +37,7 @@ index b40b94c49dc0997654aca0a1a0dffe482cd14eac..b72000ce6bdcb97b787bfab79236f60b // Paper end - Optional per player mob spawns diff --git a/net/minecraft/server/level/ServerChunkCache.java b/net/minecraft/server/level/ServerChunkCache.java -index 25d74f866546362a17505b5d4abf85382c0df20c..7d78d270b7a076f595301acd9b3c6275e804496e 100644 +index cc8638a6dab16ffbdf6951fa10182bd5763df90f..2882cd829d4d8e1f8615f085f6908efcdf68ac62 100644 --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java @@ -546,7 +546,17 @@ public class ServerChunkCache extends ChunkSource implements ca.spottedleaf.moon @@ -60,7 +60,7 @@ index 25d74f866546362a17505b5d4abf85382c0df20c..7d78d270b7a076f595301acd9b3c6275 spawnState = NaturalSpawner.createState(naturalSpawnChunkCount, this.level.getAllEntities(), this::getFullChunk, null, true); } else { diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index e96bb37bb7a7340a7ee33046820652ecd4accc53..53f038e1b5e7a13a08a0c925c8bd3f8a40868195 100644 +index d2e78e9e113c9b3e35ae401f2eb055a98df12c06..34e5cbb7eb58590f82510601e73c4e442a2addec 100644 --- a/net/minecraft/server/level/ServerPlayer.java +++ b/net/minecraft/server/level/ServerPlayer.java @@ -410,6 +410,7 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc @@ -69,8 +69,8 @@ index e96bb37bb7a7340a7ee33046820652ecd4accc53..53f038e1b5e7a13a08a0c925c8bd3f8a // Paper end - Optional per player mob spawns + public final int[] mobBackoffCounts = new int[MOBCATEGORY_TOTAL_ENUMS]; // Paper - per player mob count backoff // CraftBukkit start - public org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection transferCookieConnection; public String displayName; + public net.kyori.adventure.text.Component adventure$displayName; // Paper diff --git a/net/minecraft/world/level/NaturalSpawner.java b/net/minecraft/world/level/NaturalSpawner.java index c710e08ab48075ce7854e56826adb8f0364b025b..14a2514a408a66a83f7b5fb43b4c4dc8f23fd5f4 100644 --- a/net/minecraft/world/level/NaturalSpawner.java diff --git a/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch b/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch index 73a0f055bd..c02eb1f83a 100644 --- a/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch +++ b/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Optimise collision checking in player move packet handling Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index c9a0df5e5617e62703787942d067883ea537618b..aeb43902a09ef9c1b137964065780be3e87648f4 100644 +index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c8857c064d55 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -592,6 +592,7 @@ public class ServerGamePacketListenerImpl +@@ -594,6 +594,7 @@ public class ServerGamePacketListenerImpl } rootVehicle.move(MoverType.PLAYER, new Vec3(d3, d4, d5)); @@ -17,7 +17,7 @@ index c9a0df5e5617e62703787942d067883ea537618b..aeb43902a09ef9c1b137964065780be3 double verticalDelta = d4; d3 = d - rootVehicle.getX(); d4 = d1 - rootVehicle.getY(); -@@ -603,12 +604,21 @@ public class ServerGamePacketListenerImpl +@@ -605,12 +606,21 @@ public class ServerGamePacketListenerImpl d7 = d3 * d3 + d4 * d4 + d5 * d5; boolean flag1 = false; if (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot @@ -42,7 +42,7 @@ index c9a0df5e5617e62703787942d067883ea537618b..aeb43902a09ef9c1b137964065780be3 rootVehicle.absSnapTo(x, y, z, f, f1); this.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle)); rootVehicle.removeLatestMovementRecording(); -@@ -687,9 +697,32 @@ public class ServerGamePacketListenerImpl +@@ -689,9 +699,32 @@ public class ServerGamePacketListenerImpl } private boolean noBlocksAround(Entity entity) { @@ -78,7 +78,7 @@ index c9a0df5e5617e62703787942d067883ea537618b..aeb43902a09ef9c1b137964065780be3 } @Override -@@ -1465,7 +1498,7 @@ public class ServerGamePacketListenerImpl +@@ -1467,7 +1500,7 @@ public class ServerGamePacketListenerImpl } } @@ -87,7 +87,7 @@ index c9a0df5e5617e62703787942d067883ea537618b..aeb43902a09ef9c1b137964065780be3 d3 = d - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above d4 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above d5 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above -@@ -1504,6 +1537,7 @@ public class ServerGamePacketListenerImpl +@@ -1506,6 +1539,7 @@ public class ServerGamePacketListenerImpl boolean flag1 = this.player.verticalCollisionBelow; this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5)); this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move @@ -95,7 +95,7 @@ index c9a0df5e5617e62703787942d067883ea537618b..aeb43902a09ef9c1b137964065780be3 // Paper start - prevent position desync if (this.awaitingPositionFromClient != null) { return; // ... thanks Mojang for letting move calls teleport across dimensions. -@@ -1536,7 +1570,17 @@ public class ServerGamePacketListenerImpl +@@ -1538,7 +1572,17 @@ public class ServerGamePacketListenerImpl } // Paper start - Add fail move event @@ -114,7 +114,7 @@ index c9a0df5e5617e62703787942d067883ea537618b..aeb43902a09ef9c1b137964065780be3 if (!allowMovement) { io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, toX, toY, toZ, toYaw, toPitch, false); -@@ -1672,7 +1716,7 @@ public class ServerGamePacketListenerImpl +@@ -1674,7 +1718,7 @@ public class ServerGamePacketListenerImpl private boolean updateAwaitingTeleport() { if (this.awaitingPositionFromClient != null) { @@ -123,7 +123,7 @@ index c9a0df5e5617e62703787942d067883ea537618b..aeb43902a09ef9c1b137964065780be3 this.awaitingTeleportTime = this.tickCount; this.teleport( this.awaitingPositionFromClient.x, -@@ -1691,6 +1735,33 @@ public class ServerGamePacketListenerImpl +@@ -1693,6 +1737,33 @@ public class ServerGamePacketListenerImpl } } diff --git a/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch b/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch index a0955e50f0..0d8ff77121 100644 --- a/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch +++ b/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch @@ -11,15 +11,16 @@ keepalives in case multiple end up in flight. Additionally, replace the latency calculation with a true average over the last 5 seconds of keepalive transactions. -diff --git a/net/minecraft/server/level/ServerPlayer.java b/net/minecraft/server/level/ServerPlayer.java -index 53f038e1b5e7a13a08a0c925c8bd3f8a40868195..f3eca351021c37b64315872d075bd0a84aeee267 100644 ---- a/net/minecraft/server/level/ServerPlayer.java -+++ b/net/minecraft/server/level/ServerPlayer.java -@@ -461,6 +461,70 @@ public class ServerPlayer extends Player implements ca.spottedleaf.moonrise.patc - return this.viewDistanceHolder; - } - // Paper end - rewrite chunk system -+ // Paper start - improve keepalives +diff --git a/io/papermc/paper/util/KeepAlive.java b/io/papermc/paper/util/KeepAlive.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4a2520f554c2ee74faf86d7c93baccf0f391a6b3 +--- /dev/null ++++ b/io/papermc/paper/util/KeepAlive.java +@@ -0,0 +1,67 @@ ++package io.papermc.paper.util; ++ ++public class KeepAlive { ++ + public long lastKeepAliveTx = System.nanoTime(); + public static final record KeepAliveResponse(long txTimeNS, long rxTimeNS) { + public long latencyNS() { @@ -82,15 +83,27 @@ index 53f038e1b5e7a13a08a0c925c8bd3f8a40868195..f3eca351021c37b64315872d075bd0a8 + return ret; + } + } -+ // Paper end - improve keepalives ++} +diff --git a/net/minecraft/server/network/CommonListenerCookie.java b/net/minecraft/server/network/CommonListenerCookie.java +index b94988d6dd98bfb7d3d2f08c2adaa96d7c7a8b55..7e61c8222d567feb8c2b988699e1cfe83bf4be31 100644 +--- a/net/minecraft/server/network/CommonListenerCookie.java ++++ b/net/minecraft/server/network/CommonListenerCookie.java +@@ -3,8 +3,8 @@ package net.minecraft.server.network; + import com.mojang.authlib.GameProfile; + import net.minecraft.server.level.ClientInformation; - public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { - super(level, gameProfile); +-public record CommonListenerCookie(GameProfile gameProfile, int latency, ClientInformation clientInformation, boolean transferred, @org.jetbrains.annotations.Nullable String brandName) { // Paper ++public record CommonListenerCookie(GameProfile gameProfile, int latency, ClientInformation clientInformation, boolean transferred, @org.jetbrains.annotations.Nullable String brandName, io.papermc.paper.util.KeepAlive keepAlive) { // Paper + public static CommonListenerCookie createInitial(GameProfile gameProfile, boolean transferred) { +- return new CommonListenerCookie(gameProfile, 0, ClientInformation.createDefault(), transferred, null); // Paper ++ return new CommonListenerCookie(gameProfile, 0, ClientInformation.createDefault(), transferred, null, new io.papermc.paper.util.KeepAlive()); // Paper + } + } diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 85e01c3a1536b41a0301a5a6506e058ff9633a4a..43f70a5561d6cc62aaeba6d1e39598ecb382e369 100644 +index fae1523404b7afa2b904faad9242273b3c406aac..16962ccab91d0941e428a2e53aa37e9ca975f62f 100644 --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -38,12 +38,12 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -38,12 +38,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack protected final MinecraftServer server; public final Connection connection; // Paper private final boolean transferred; @@ -104,19 +117,26 @@ index 85e01c3a1536b41a0301a5a6506e058ff9633a4a..43f70a5561d6cc62aaeba6d1e39598ec private boolean closed = false; - private int latency; + private volatile int latency; // Paper - improve keepalives - make volatile ++ private final io.papermc.paper.util.KeepAlive keepAlive; // Paper - improve keepalives private volatile boolean suspendFlushingOnServerThread = false; // CraftBukkit start - protected final net.minecraft.server.level.ServerPlayer player; -@@ -57,7 +57,7 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack - public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, net.minecraft.server.level.ServerPlayer player) { // CraftBukkit + protected final org.bukkit.craftbukkit.CraftServer cserver; +@@ -57,12 +58,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) { this.server = server; this.connection = connection; - this.keepAliveTime = Util.getMillis(); + //this.keepAliveTime = Util.getMillis(); // Paper - improve keepalives this.latency = cookie.latency(); this.transferred = cookie.transferred(); - // CraftBukkit start - add fields and methods -@@ -120,13 +120,41 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + // Paper start + this.playerBrand = cookie.brandName(); + this.cserver = server.server; ++ this.keepAlive = cookie.keepAlive(); + // Paper end + } + +@@ -89,13 +91,41 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack @Override public void handleKeepAlive(ServerboundKeepAlivePacket packet) { @@ -128,27 +148,27 @@ index 85e01c3a1536b41a0301a5a6506e058ff9633a4a..43f70a5561d6cc62aaeba6d1e39598ec - this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect + // Paper start - improve keepalives + long now = System.nanoTime(); -+ net.minecraft.server.level.ServerPlayer.PendingKeepAlive pending = this.player.pendingKeepAlives.peek(); ++ io.papermc.paper.util.KeepAlive.PendingKeepAlive pending = this.keepAlive.pendingKeepAlives.peek(); + if (pending != null && pending.challengeId() == packet.getId()) { -+ this.player.pendingKeepAlives.remove(pending); ++ this.keepAlive.pendingKeepAlives.remove(pending); + -+ net.minecraft.server.level.ServerPlayer.KeepAliveResponse response = new net.minecraft.server.level.ServerPlayer.KeepAliveResponse(pending.txTimeNS(), now); ++ io.papermc.paper.util.KeepAlive.KeepAliveResponse response = new io.papermc.paper.util.KeepAlive.KeepAliveResponse(pending.txTimeNS(), now); + -+ this.player.pingCalculator1m.update(response); -+ this.player.pingCalculator5s.update(response); ++ this.keepAlive.pingCalculator1m.update(response); ++ this.keepAlive.pingCalculator5s.update(response); + -+ this.latency = this.player.pingCalculator5s.getAvgLatencyMS(); ++ this.latency = this.keepAlive.pingCalculator5s.getAvgLatencyMS(); + return; + } + -+ for (java.util.Iterator itr = this.player.pendingKeepAlives.iterator(); itr.hasNext();) { -+ net.minecraft.server.level.ServerPlayer.PendingKeepAlive ka = itr.next(); ++ for (java.util.Iterator itr = this.keepAlive.pendingKeepAlives.iterator(); itr.hasNext();) { ++ io.papermc.paper.util.KeepAlive.PendingKeepAlive ka = itr.next(); + if (ka.challengeId() == packet.getId()) { + itr.remove(); + + if (!this.processedDisconnect) { -+ LOGGER.info("Disconnecting " + this.player.getScoreboardName() + " for sending keepalive response (" + packet.getId() + ") out-of-order!"); -+ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); ++ LOGGER.info("Disconnecting {} for sending keepalive response ({}) out-of-order!", this.playerProfile().getName(), packet.getId()); ++ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, io.papermc.paper.connection.DisconnectionReason.TIMEOUT); + return; + } + break; @@ -156,15 +176,15 @@ index 85e01c3a1536b41a0301a5a6506e058ff9633a4a..43f70a5561d6cc62aaeba6d1e39598ec } + + if (!this.processedDisconnect) { -+ LOGGER.info("Disconnecting " + this.player.getScoreboardName() + " for sending keepalive response (" + packet.getId() + ") without matching challenge!"); -+ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); ++ LOGGER.info("Disconnecting {} for sending keepalive response ({}) without matching challenge!", this.playerProfile().getName(), packet.getId()); ++ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, io.papermc.paper.connection.DisconnectionReason.TIMEOUT); + return; + } + // Paper end - improve keepalives } @Override -@@ -247,20 +275,23 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -148,20 +178,23 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack protected void keepConnectionAlive() { Profiler.get().push("keepAlive"); long millis = Util.getMillis(); @@ -186,19 +206,29 @@ index 85e01c3a1536b41a0301a5a6506e058ff9633a4a..43f70a5561d6cc62aaeba6d1e39598ec + if (this.checkIfClosed(millis) && !this.processedDisconnect) { + long currTime = System.nanoTime(); + -+ if ((currTime - this.player.lastKeepAliveTx) >= java.util.concurrent.TimeUnit.SECONDS.toNanos(1L)) { -+ this.player.lastKeepAliveTx = currTime; ++ if ((currTime - this.keepAlive.lastKeepAliveTx) >= java.util.concurrent.TimeUnit.SECONDS.toNanos(1L)) { ++ this.keepAlive.lastKeepAliveTx = currTime; + -+ net.minecraft.server.level.ServerPlayer.PendingKeepAlive pka = new net.minecraft.server.level.ServerPlayer.PendingKeepAlive(currTime, millis); -+ this.player.pendingKeepAlives.add(pka); ++ io.papermc.paper.util.KeepAlive.PendingKeepAlive pka = new io.papermc.paper.util.KeepAlive.PendingKeepAlive(currTime, millis); ++ this.keepAlive.pendingKeepAlives.add(pka); + this.send(new ClientboundKeepAlivePacket(pka.challengeId())); + } + -+ net.minecraft.server.level.ServerPlayer.PendingKeepAlive oldest = this.player.pendingKeepAlives.peek(); ++ io.papermc.paper.util.KeepAlive.PendingKeepAlive oldest = this.keepAlive.pendingKeepAlives.peek(); + if (oldest != null && (currTime - oldest.txTimeNS()) > java.util.concurrent.TimeUnit.MILLISECONDS.toNanos(KEEPALIVE_LIMIT)) { -+ LOGGER.warn(this.player.getScoreboardName() + " was kicked due to keepalive timeout!"); -+ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); ++ LOGGER.info("{} was kicked due to keepalive timeout!", this.playerProfile().getName()); ++ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, io.papermc.paper.connection.DisconnectionReason.TIMEOUT); // Paper - kick event cause + // Paper end - improve keepalives } } +@@ -341,6 +374,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack + } + + protected CommonListenerCookie createCookie(ClientInformation clientInformation) { +- return new CommonListenerCookie(this.playerProfile(), this.latency, clientInformation, this.transferred, this.playerBrand); // Paper ++ return new CommonListenerCookie(this.playerProfile(), this.latency, clientInformation, this.transferred, this.playerBrand, this.keepAlive); // Paper + } +-} ++} +\ No newline at end of file diff --git a/paper-server/patches/sources/net/minecraft/network/Connection.java.patch b/paper-server/patches/sources/net/minecraft/network/Connection.java.patch index 773b494465..ef3704f1e0 100644 --- a/paper-server/patches/sources/net/minecraft/network/Connection.java.patch +++ b/paper-server/patches/sources/net/minecraft/network/Connection.java.patch @@ -29,7 +29,7 @@ @Nullable private volatile PacketListener disconnectListener; @Nullable -@@ -106,6 +_,40 @@ +@@ -106,6 +_,41 @@ private volatile DisconnectionDetails delayedDisconnect; @Nullable BandwidthDebugMonitor bandwidthDebugMonitor; @@ -44,8 +44,7 @@ + if (this.packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl impl) { + return impl.player; + } else if (this.packetListener instanceof net.minecraft.server.network.ServerCommonPacketListenerImpl impl) { -+ org.bukkit.craftbukkit.entity.CraftPlayer player = impl.getCraftPlayer(); -+ return player == null ? null : player.getHandle(); ++ return null; + } + return null; + } @@ -67,6 +66,8 @@ + } + // Paper end - packet limiter + @Nullable public SocketAddress haProxyAddress; // Paper - Add API to get player's proxy address ++ public @Nullable java.util.Optional legacySavedLoginEventResultOverride; // Paper - playerloginevent ++ public @Nullable net.minecraft.server.level.ServerPlayer savedPlayerForLoginEventLegacy; // Paper - playerloginevent public Connection(PacketFlow receiving) { this.receiving = receiving; diff --git a/paper-server/patches/sources/net/minecraft/network/DisconnectionDetails.java.patch b/paper-server/patches/sources/net/minecraft/network/DisconnectionDetails.java.patch new file mode 100644 index 0000000000..434d042dcd --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/network/DisconnectionDetails.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/network/DisconnectionDetails.java ++++ b/net/minecraft/network/DisconnectionDetails.java +@@ -5,7 +_,13 @@ + import java.util.Optional; + import net.minecraft.network.chat.Component; + +-public record DisconnectionDetails(Component reason, Optional report, Optional bugReportLink) { ++// Paper start - Configuration API: add support for enhanced disconnection causes ++public record DisconnectionDetails(Component reason, Optional report, Optional bugReportLink, Optional quitMessage, Optional disconnectionReason) { ++ public DisconnectionDetails(Component reason, Optional report, Optional bugReportLink) { ++ this(reason, report, bugReportLink, Optional.empty(), Optional.empty()); ++ } ++// Paper end - Configuration API: add support for enhanced disconnection causes ++ + public DisconnectionDetails(Component reason) { + this(reason, Optional.empty(), Optional.empty()); + } diff --git a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch index 382b39f8c0..0103d1fb0f 100644 --- a/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/level/ServerPlayer.java.patch @@ -57,7 +57,7 @@ @Override public void dataChanged(AbstractContainerMenu containerMenu, int dataSlotIndex, int value) { } -@@ -354,9 +_,43 @@ +@@ -354,9 +_,41 @@ public void sendSystemMessage(Component component) { ServerPlayer.this.sendSystemMessage(component); } @@ -79,7 +79,6 @@ + public @Nullable net.minecraft.network.protocol.game.ClientboundSetHealthPacket queuedHealthUpdatePacket; + // Paper end - cancellable death event + // CraftBukkit start -+ public org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection transferCookieConnection; + public String displayName; + public net.kyori.adventure.text.Component adventure$displayName; // Paper + public @Nullable Component listName; @@ -96,7 +95,6 @@ + // CraftBukkit end + public boolean isRealPlayer; // Paper + public @Nullable com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper - PlayerNaturallySpawnCreaturesEvent -+ public @Nullable String clientBrandName = null; // Paper - Brand support + public @Nullable org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - Add API for quit reason; there are a lot of changes to do if we change all methods leading to the event public ServerPlayer(MinecraftServer server, ServerLevel level, GameProfile gameProfile, ClientInformation clientInformation) { diff --git a/paper-server/patches/sources/net/minecraft/server/network/CommonListenerCookie.java.patch b/paper-server/patches/sources/net/minecraft/server/network/CommonListenerCookie.java.patch new file mode 100644 index 0000000000..38dc666c1a --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/CommonListenerCookie.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/server/network/CommonListenerCookie.java ++++ b/net/minecraft/server/network/CommonListenerCookie.java +@@ -3,8 +_,8 @@ + import com.mojang.authlib.GameProfile; + import net.minecraft.server.level.ClientInformation; + +-public record CommonListenerCookie(GameProfile gameProfile, int latency, ClientInformation clientInformation, boolean transferred) { ++public record CommonListenerCookie(GameProfile gameProfile, int latency, ClientInformation clientInformation, boolean transferred, @org.jetbrains.annotations.Nullable String brandName) { // Paper + public static CommonListenerCookie createInitial(GameProfile gameProfile, boolean transferred) { +- return new CommonListenerCookie(gameProfile, 0, ClientInformation.createDefault(), transferred); ++ return new CommonListenerCookie(gameProfile, 0, ClientInformation.createDefault(), transferred, null); // Paper + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch index dac8b4dcd5..a3d4b912c9 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -1,14 +1,6 @@ --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -29,14 +_,14 @@ - import net.minecraft.util.profiling.Profiler; - import org.slf4j.Logger; - --public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener { -+public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener, org.bukkit.craftbukkit.entity.CraftPlayer.TransferCookieConnection { // CraftBukkit - private static final Logger LOGGER = LogUtils.getLogger(); - public static final int LATENCY_CHECK_INTERVAL = 15000; - private static final int CLOSED_LISTENER_TIMEOUT = 15000; +@@ -36,7 +_,7 @@ private static final Component TIMEOUT_DISCONNECTION_MESSAGE = Component.translatable("disconnect.timeout"); static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response"); protected final MinecraftServer server; @@ -17,73 +9,32 @@ private final boolean transferred; private long keepAliveTime; private boolean keepAlivePending; -@@ -45,14 +_,51 @@ +@@ -45,6 +_,14 @@ private boolean closed = false; private int latency; private volatile boolean suspendFlushingOnServerThread = false; + // CraftBukkit start -+ protected final net.minecraft.server.level.ServerPlayer player; + protected final org.bukkit.craftbukkit.CraftServer cserver; + public boolean processedDisconnect; + // CraftBukkit end + public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks + private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit + protected static final net.minecraft.resources.ResourceLocation MINECRAFT_BRAND = net.minecraft.resources.ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support ++ public @Nullable String playerBrand; // Paper -- public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) { -+ public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, net.minecraft.server.level.ServerPlayer player) { // CraftBukkit + public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) { this.server = server; - this.connection = connection; +@@ -52,6 +_,10 @@ this.keepAliveTime = Util.getMillis(); this.latency = cookie.latency(); this.transferred = cookie.transferred(); -- } -+ // CraftBukkit start - add fields and methods -+ this.player = player; -+ this.player.transferCookieConnection = this; ++ // Paper start ++ this.playerBrand = cookie.brandName(); + this.cserver = server.server; -+ } -+ -+ public org.bukkit.craftbukkit.entity.CraftPlayer getCraftPlayer() { -+ return this.player.getBukkitEntity(); -+ } -+ -+ @Override -+ public boolean isTransferred() { -+ return this.transferred; -+ } -+ -+ @Override -+ public net.minecraft.network.ConnectionProtocol getProtocol() { -+ return this.protocol(); -+ } -+ -+ @Override -+ public void sendPacket(Packet packet) { -+ this.send(packet); -+ } -+ -+ @Override -+ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes -+ this.disconnect(reason, cause); // Paper - kick event causes -+ } -+ // CraftBukkit end ++ // Paper end + } private void close() { - if (!this.closed) { -@@ -63,6 +_,12 @@ - - @Override - public void onDisconnect(DisconnectionDetails details) { -+ // Paper start - Fix kick event leave message not being sent -+ this.onDisconnect(details, null); -+ } -+ -+ public void onDisconnect(DisconnectionDetails details, @Nullable net.kyori.adventure.text.Component quitMessage) { -+ // Paper end - Fix kick event leave message not being sent - if (this.isSingleplayerOwner()) { - LOGGER.info("Stopping singleplayer server as player logged out"); - this.server.halt(false); @@ -82,7 +_,7 @@ this.latency = (this.latency * 3 + i) / 4; this.keepAlivePending = false; @@ -93,88 +44,13 @@ } } -@@ -90,9 +_,73 @@ - public void handlePong(ServerboundPongPacket packet) { - } - -+ public static final net.minecraft.resources.ResourceLocation CUSTOM_REGISTER = net.minecraft.resources.ResourceLocation.withDefaultNamespace("register"); // CraftBukkit -+ private static final net.minecraft.resources.ResourceLocation CUSTOM_UNREGISTER = net.minecraft.resources.ResourceLocation.withDefaultNamespace("unregister"); // CraftBukkit -+ - @Override - public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { -- } -+ // Paper start -+ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload(String brand)) { -+ this.player.clientBrandName = brand; -+ } -+ -+ if (!(packet.payload() instanceof final net.minecraft.network.protocol.common.custom.DiscardedPayload discardedPayload)) { -+ return; -+ } -+ -+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level()); -+ -+ final net.minecraft.resources.ResourceLocation identifier = packet.payload().type().id(); -+ final byte[] data = discardedPayload.data(); -+ try { -+ final boolean registerChannel = ServerCommonPacketListenerImpl.CUSTOM_REGISTER.equals(identifier); -+ if (registerChannel || ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER.equals(identifier)) { -+ // Strings separated by zeros instead of length prefixes -+ int startIndex = 0; -+ for (int i = 0; i < data.length; i++) { -+ final byte b = data[i]; -+ if (b != 0) { -+ continue; -+ } -+ -+ readChannelIdentifier(data, startIndex, i, registerChannel); -+ startIndex = i + 1; -+ } -+ -+ // Read the last one -+ readChannelIdentifier(data, startIndex, data.length, registerChannel); -+ return; -+ } -+ -+ if (identifier.equals(MINECRAFT_BRAND)) { -+ this.player.clientBrandName = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.wrappedBuffer(data)).readUtf(256); -+ } -+ -+ this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); -+ } catch (final Exception e) { -+ ServerGamePacketListenerImpl.LOGGER.error("Couldn't handle custom payload on channel {}", identifier, e); -+ this.disconnect(Component.literal("Invalid custom payload payload!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause -+ } -+ } -+ -+ private void readChannelIdentifier(final byte[] data, final int from, final int to, final boolean register) { -+ final int length = to - from; -+ if (length == 0) { -+ return; -+ } -+ -+ final String channel = new String(data, from, length, java.nio.charset.StandardCharsets.US_ASCII); -+ if (register) { -+ this.getCraftPlayer().addChannel(channel); -+ } else { -+ this.getCraftPlayer().removeChannel(channel); -+ } -+ } -+ -+ public final boolean isDisconnected() { -+ return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs -+ } -+ // Paper end - - @Override - public void handleCustomClickAction(ServerboundCustomClickActionPacket packet) { -@@ -105,21 +_,50 @@ +@@ -105,21 +_,46 @@ PacketUtils.ensureRunningOnSameThread(packet, this, this.server); if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id()); - this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); - } -+ this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), org.bukkit.event.player.PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause ++ this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), io.papermc.paper.connection.DisconnectionReason.RESOURCE_PACK_REJECTION); // Paper - kick event cause + } + // Paper start - adventure pack callbacks + // call the callbacks before the previously-existing event so the event has final say @@ -185,26 +61,22 @@ + callback = this.packCallbacks.get(packet.id()); + } + if (callback != null) { -+ callback.packEventReceived(packet.id(), net.kyori.adventure.resource.ResourcePackStatus.valueOf(packet.action().name()), this.getCraftPlayer()); ++ net.kyori.adventure.audience.Audience audience = switch (this) { ++ case ServerGamePacketListenerImpl serverGamePacketListener -> serverGamePacketListener.getCraftPlayer(); ++ case ServerConfigurationPacketListenerImpl configurationPacketListener -> configurationPacketListener.paperConnection.getAudience(); ++ default -> throw new IllegalStateException("Unexpected value: " + this); ++ }; ++ ++ callback.packEventReceived(packet.id(), net.kyori.adventure.resource.ResourcePackStatus.valueOf(packet.action().name()), audience); + } + // Paper end -+ // Paper start - store last pack status -+ org.bukkit.event.player.PlayerResourcePackStatusEvent.Status packStatus = org.bukkit.event.player.PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()]; -+ this.player.getBukkitEntity().resourcePackStatus = packStatus; -+ this.cserver.getPluginManager().callEvent(new org.bukkit.event.player.PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit -+ // Paper end - store last pack status } @Override public void handleCookieResponse(ServerboundCookieResponsePacket packet) { - this.disconnect(DISCONNECT_UNEXPECTED_QUERY); -+ // CraftBukkit start -+ PacketUtils.ensureRunningOnSameThread(packet, this, this.server); -+ if (this.player.getBukkitEntity().handleCookieResponse(packet)) { -+ return; -+ } -+ // CraftBukkit end -+ this.disconnect(DISCONNECT_UNEXPECTED_QUERY, org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause ++ if (this.getCookieConnection().handleCookieResponse(packet)) return; // Paper ++ this.disconnect(DISCONNECT_UNEXPECTED_QUERY, io.papermc.paper.connection.DisconnectionReason.INVALID_COOKIE); // Paper - kick event cause } protected void keepConnectionAlive() { @@ -229,7 +101,7 @@ if (this.closed) { if (time - this.closedListenerTime >= 15000L) { - this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE); -+ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause ++ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, io.papermc.paper.connection.DisconnectionReason.TIMEOUT); // Paper - kick event cause } return false; @@ -240,43 +112,34 @@ + // CraftBukkit start + if (packet == null || this.processedDisconnect) { // Spigot + return; -+ } else if (packet instanceof net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket defaultSpawnPositionPacket) { -+ this.player.compassTarget = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(defaultSpawnPositionPacket.getPos(), this.getCraftPlayer().getWorld()); ++ } else if (packet instanceof net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket defaultSpawnPositionPacket && this instanceof ServerGamePacketListenerImpl serverGamePacketListener) { ++ serverGamePacketListener.player.compassTarget = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(defaultSpawnPositionPacket.getPos(), serverGamePacketListener.getCraftPlayer().getWorld()); + } + // CraftBukkit end if (packet.isTerminal()) { this.close(); } -@@ -173,19 +_,115 @@ +@@ -173,19 +_,113 @@ } } -+ // Paper start - adventure -+ public void disconnect(final net.kyori.adventure.text.Component reason) { -+ this.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); -+ } -+ -+ public void disconnect(final net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { -+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause); -+ } -+ // Paper end - adventure -+ + @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - kick event causes public void disconnect(Component reason) { - this.disconnect(new DisconnectionDetails(reason)); -- } -- -- public void disconnect(DisconnectionDetails disconnectionDetails) { + // Paper start - kick event causes -+ this.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ this.disconnect(reason, io.papermc.paper.connection.DisconnectionReason.UNKNOWN); + } + -+ public void disconnect(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { -+ this.disconnect(new DisconnectionDetails(reason), cause); ++ public void disconnect(Component reason, io.papermc.paper.connection.DisconnectionReason cause) { ++ this.disconnect(new DisconnectionDetails(reason, java.util.Optional.empty(), java.util.Optional.empty(), java.util.Optional.empty(), java.util.Optional.of(cause))); + // Paper end - kick event causes -+ } -+ -+ public void disconnect(DisconnectionDetails disconnectionDetails, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event cause + } + + public void disconnect(DisconnectionDetails disconnectionDetails) { +- this.connection +- .send( +- new ClientboundDisconnectPacket(disconnectionDetails.reason()), +- PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails)) + // CraftBukkit start - fire PlayerKickEvent + if (this.processedDisconnect) { + return; @@ -285,7 +148,7 @@ + org.bukkit.craftbukkit.util.Waitable waitable = new org.bukkit.craftbukkit.util.Waitable() { + @Override + protected Object evaluate() { -+ ServerCommonPacketListenerImpl.this.disconnect(disconnectionDetails, cause); // Paper - kick event causes ++ ServerCommonPacketListenerImpl.this.disconnect(disconnectionDetails); + return null; + } + }; @@ -302,71 +165,86 @@ + return; + } + -+ net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure ++ Component reason; ++ Component leaveMessage; ++ if (this instanceof ServerGamePacketListenerImpl serverGamePacketListener) { ++ net.kyori.adventure.text.Component rawLeaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? serverGamePacketListener.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(serverGamePacketListener.player.getScoreboardName())); // Paper - Adventure + -+ org.bukkit.event.player.PlayerKickEvent event = new org.bukkit.event.player.PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()), leaveMessage, cause); // Paper - adventure & kick event causes ++ net.minecraft.server.level.ServerPlayer player = serverGamePacketListener.player; ++ org.bukkit.event.player.PlayerKickEvent.Cause cause = disconnectionDetails.disconnectionReason().orElseThrow().game().orElse(org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ org.bukkit.event.player.PlayerKickEvent event = new org.bukkit.event.player.PlayerKickEvent( ++ player.getBukkitEntity(), ++ io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()), ++ rawLeaveMessage, cause + -+ if (this.cserver.getServer().isRunning()) { -+ this.cserver.getPluginManager().callEvent(event); -+ } + ); + -+ if (event.isCancelled()) { -+ // Do not kick the player -+ return; -+ } ++ if (this.cserver.getServer().isRunning()) { ++ this.cserver.getPluginManager().callEvent(event); ++ } + -+ // Log kick to console *after* event was processed. -+ switch (cause) { -+ case FLYING_PLAYER -> LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); -+ case FLYING_VEHICLE -> LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); ++ if (event.isCancelled()) { ++ // Do not kick the player ++ return; ++ } ++ ++ reason = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()); ++ leaveMessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(event.leaveMessage()); ++ serverGamePacketListener.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason ++ // Log kick to console *after* event was processed. ++ switch (cause) { ++ case FLYING_PLAYER -> LOGGER.warn("{} was kicked for floating too long!", player.getName().getString()); ++ case FLYING_VEHICLE -> LOGGER.warn("{} was kicked for floating a vehicle too long!", player.getName().getString()); ++ } ++ } else { ++ // TODO: Add event for config event ++ reason = disconnectionDetails.reason(); ++ leaveMessage = null; + } + + // Send the possibly modified leave message -+ this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionDetails.report(), disconnectionDetails.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message ++ this.disconnect0(new DisconnectionDetails(reason, disconnectionDetails.report(), disconnectionDetails.bugReportLink(), java.util.Optional.ofNullable(leaveMessage), disconnectionDetails.disconnectionReason())); + } + -+ private void disconnect0(DisconnectionDetails disconnectionDetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message -+ // CraftBukkit end -+ this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason - this.connection - .send( - new ClientboundDisconnectPacket(disconnectionDetails.reason()), - PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails)) - ); -- this.connection.setReadOnly(); ++ private void disconnect0(DisconnectionDetails disconnectionDetails) { ++ this.connection ++ .send( ++ new ClientboundDisconnectPacket(disconnectionDetails.reason()), ++ PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails)) ++ ); ++ this.onDisconnect(disconnectionDetails); + this.connection.setReadOnly(); - this.server.executeBlocking(this.connection::handleDisconnection); - } -+ this.onDisconnect(disconnectionDetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message -+ this.connection.setReadOnly(); + // CraftBukkit - Don't wait + this.server.scheduleOnMain(this.connection::handleDisconnection); // Paper + } + + // Paper start - add proper async disconnect -+ public void disconnectAsync(net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { -+ this.disconnectAsync(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause); ++ public final void disconnectAsync(Component component, io.papermc.paper.connection.DisconnectionReason reason) { ++ this.disconnectAsync(new DisconnectionDetails(component, java.util.Optional.empty(), java.util.Optional.empty(), java.util.Optional.empty(), java.util.Optional.of(reason))); + } + -+ public void disconnectAsync(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { -+ this.disconnectAsync(new DisconnectionDetails(reason), cause); ++ public final void disconnectAsync(Component component) { ++ this.disconnectAsync(new DisconnectionDetails(component)); + } + -+ public void disconnectAsync(DisconnectionDetails disconnectionInfo, org.bukkit.event.player.PlayerKickEvent.Cause cause) { -+ if (this.cserver.isPrimaryThread()) { -+ this.disconnect(disconnectionInfo, cause); -+ return; -+ } ++ public abstract void disconnectAsync(DisconnectionDetails disconnectionInfo); + -+ this.connection.setReadOnly(); -+ this.server.scheduleOnMain(() -> { -+ ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); -+ if (ServerCommonPacketListenerImpl.this.player.quitReason == null) { -+ // cancelled -+ ServerCommonPacketListenerImpl.this.connection.enableAutoRead(); -+ } -+ }); ++ public abstract io.papermc.paper.connection.ReadablePlayerCookieConnectionImpl getCookieConnection(); ++ ++ public boolean isTransferred() { ++ return this.transferred; + } + // Paper end - add proper async disconnect protected boolean isSingleplayerOwner() { return this.server.isSingleplayerOwner(this.playerProfile()); +@@ -203,6 +_,6 @@ + } + + protected CommonListenerCookie createCookie(ClientInformation clientInformation) { +- return new CommonListenerCookie(this.playerProfile(), this.latency, clientInformation, this.transferred); ++ return new CommonListenerCookie(this.playerProfile(), this.latency, clientInformation, this.transferred, this.playerBrand); // Paper + } + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch index 12373ae87d..d14ee1bbae 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch @@ -1,18 +1,19 @@ --- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java -@@ -48,8 +_,10 @@ +@@ -47,11 +_,13 @@ + public ClientInformation clientInformation; @Nullable private SynchronizeRegistriesTask synchronizeRegistriesTask; ++ public io.papermc.paper.connection.PaperPlayerConfigurationConnection paperConnection; // Paper -- public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) { -- super(server, connection, cookie); -+ // CraftBukkit start -+ public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) { -+ super(server, connection, cookie, player); -+ // CraftBukkit end + public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) { + super(server, connection, cookie); this.gameProfile = cookie.gameProfile(); this.clientInformation = cookie.clientInformation(); ++ this.paperConnection = new io.papermc.paper.connection.PaperPlayerConfigurationConnection(this); // Paper } + + @Override @@ -61,6 +_,11 @@ @Override @@ -25,20 +26,32 @@ LOGGER.info("{} lost connection: {}", this.gameProfile, details.reason().getString()); super.onDisconnect(details); } -@@ -73,6 +_,12 @@ +@@ -71,10 +_,15 @@ + } + public void startConfiguration() { ++ new io.papermc.paper.event.connection.configuration.PlayerConnectionInitialConfigureEvent(this.paperConnection).callEvent(); // Paper this.send(new ClientboundCustomPayloadPacket(new BrandPayload(this.server.getServerModName()))); ServerLinks serverLinks = this.server.serverLinks(); -+ // CraftBukkit start -+ org.bukkit.craftbukkit.CraftServerLinks wrapper = new org.bukkit.craftbukkit.CraftServerLinks(serverLinks); -+ org.bukkit.event.player.PlayerLinksSendEvent event = new org.bukkit.event.player.PlayerLinksSendEvent(this.player.getBukkitEntity(), wrapper); -+ this.cserver.getPluginManager().callEvent(event); -+ serverLinks = wrapper.getServerLinks(); -+ // CraftBukkit end if (!serverLinks.isEmpty()) { - this.send(new ClientboundServerLinksPacket(serverLinks.untrust())); +- this.send(new ClientboundServerLinksPacket(serverLinks.untrust())); ++ // Paper start ++ org.bukkit.craftbukkit.CraftServerLinks links = new org.bukkit.craftbukkit.CraftServerLinks(serverLinks); ++ new org.bukkit.event.player.PlayerLinksSendEvent(this.paperConnection, links).callEvent(); ++ this.send(new ClientboundServerLinksPacket(links.getServerLinks().untrust())); ++ // Paper end } -@@ -105,6 +_,7 @@ + + LayeredRegistryAccess layeredRegistryAccess = this.server.registries(); +@@ -87,6 +_,7 @@ + this.synchronizeRegistriesTask = new SynchronizeRegistriesTask(list, layeredRegistryAccess); + this.configurationTasks.add(this.synchronizeRegistriesTask); + this.addOptionalTasks(); ++ this.configurationTasks.add(new io.papermc.paper.connection.PaperConfigurationTask(this)); // Paper + this.configurationTasks.add(new JoinWorldTask()); + this.startNextTask(); + } +@@ -105,12 +_,13 @@ @Override public void handleClientInformation(ServerboundClientInformationPacket packet) { this.clientInformation = packet.information(); @@ -46,20 +59,35 @@ } @Override -@@ -139,16 +_,22 @@ + public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { + super.handleResourcePackResponse(packet); +- if (packet.action().isTerminal()) { ++ if (packet.action().isTerminal() && packet.id().equals(this.server.getServerResourcePack().map(MinecraftServer.ServerResourcePackInfo::id).orElse(null))) { // Paper - Ignore resource pack requests that are not vanilla + this.finishCurrentTask(ServerResourcePackConfigurationTask.TYPE); + } + } +@@ -139,16 +_,30 @@ return; } - Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile); -+ Component component = null; // CraftBukkit - login checks already completed ++ Component component = org.bukkit.craftbukkit.event.CraftEventFactory.handleLoginResult(playerList.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile), this.paperConnection, this.connection, this.gameProfile, this.server, false); // Paper - Login event logic if (component != null) { this.disconnect(component); return; } - ServerPlayer serverPlayer = new ServerPlayer(this.server, this.server.overworld(), this.gameProfile, this.clientInformation); -+ ServerPlayer serverPlayer = this.player; // Paper -+ this.player.updateOptions(this.clientInformation); // Paper - Of course, we reuse the player ++ // Paper start ++ ServerPlayer serverPlayer; ++ if (this.connection.savedPlayerForLoginEventLegacy != null) { ++ serverPlayer = this.connection.savedPlayerForLoginEventLegacy; ++ // WE have to do this because the player isnt updated properly ++ serverPlayer.updateOptionsNoEvents(this.clientInformation); ++ } else { ++ serverPlayer = new ServerPlayer(this.server, this.server.overworld(), this.gameProfile, this.clientInformation); ++ } ++ // Paper end playerList.placeNewPlayer(this.connection, serverPlayer, this.createCookie(this.clientInformation)); } catch (Exception var5) { LOGGER.error("Couldn't place player in world", (Throwable)var5); @@ -71,3 +99,35 @@ this.connection.send(new ClientboundDisconnectPacket(DISCONNECT_REASON_INVALID_DATA)); this.connection.disconnect(DISCONNECT_REASON_INVALID_DATA); } +@@ -180,4 +_,31 @@ + this.startNextTask(); + } + } ++ ++ // Paper start ++ @Override ++ public void disconnectAsync(final net.minecraft.network.DisconnectionDetails disconnectionInfo) { ++ if (this.cserver.isPrimaryThread()) { ++ this.disconnect(disconnectionInfo); ++ return; ++ } ++ ++ this.connection.setReadOnly(); ++ this.server.scheduleOnMain(() -> { ++ this.disconnect(disconnectionInfo); // Currently you cannot cancel disconnect during the config stage ++ }); ++ } ++ ++ @Override ++ public io.papermc.paper.connection.ReadablePlayerCookieConnectionImpl getCookieConnection() { ++ return this.paperConnection; ++ } ++ ++ @Override ++ public void handleCustomPayload(net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket packet) { ++ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload(String brand)) { ++ this.playerBrand = brand; ++ } ++ } ++ // Paper end + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index 70b6b96e54..cd9988cb2b 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -49,7 +49,7 @@ private double firstGoodX; private double firstGoodY; private double firstGoodZ; -@@ -252,23 +_,42 @@ +@@ -252,14 +_,33 @@ private int receivedMovePacketCount; private int knownMovePacketCount; private boolean receivedMovementThisTick; @@ -79,18 +79,18 @@ private boolean waitingForSwitchToConfig; + private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length + private final io.papermc.paper.event.packet.ClientTickEndEvent tickEndEvent; // Paper - add client tick end event ++ public final io.papermc.paper.connection.PaperPlayerGameConnection playerGameConnection; // Paper public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie cookie) { -- super(server, connection, cookie); -+ super(server, connection, cookie, player); // CraftBukkit - this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection()); - this.player = player; + super(server, connection, cookie); +@@ -268,7 +_,9 @@ player.connection = this; player.getTextFilter().join(); this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(player.getUUID(), server::enforceSecureProfile); - this.chatMessageChain = new FutureChain(server); + this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat + this.tickEndEvent = new io.papermc.paper.event.packet.ClientTickEndEvent(player.getBukkitEntity()); // Paper - add client tick end event ++ this.playerGameConnection = new io.papermc.paper.connection.PaperPlayerGameConnection(this); // Paper } @Override @@ -1334,17 +1334,10 @@ return; } } -@@ -1347,24 +_,54 @@ +@@ -1347,24 +_,50 @@ @Override public void onDisconnect(DisconnectionDetails details) { -+ // Paper start - Fix kick event leave message not being sent -+ this.onDisconnect(details, null); -+ } -+ -+ @Override -+ public void onDisconnect(DisconnectionDetails details, @Nullable net.kyori.adventure.text.Component quitMessage) { -+ // Paper end - Fix kick event leave message not being sent + // CraftBukkit start - Rarely it would send a disconnect line twice + if (this.processedDisconnect) { + return; @@ -1354,9 +1347,11 @@ + // CraftBukkit end LOGGER.info("{} lost connection: {}", this.player.getName().getString(), details.reason().getString()); - this.removePlayerFromWorld(); -- super.onDisconnect(details); -+ this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent -+ super.onDisconnect(details, quitMessage); // Paper - Fix kick event leave message not being sent ++ // Paper start - Fix kick event leave message not being sent ++ final net.kyori.adventure.text.Component quitMessage = io.papermc.paper.adventure.PaperAdventure.asAdventure(details.quitMessage().orElse(null)); ++ this.removePlayerFromWorld(quitMessage); ++ // Paper end - Fix kick event leave message not being sent + super.onDisconnect(details); } private void removePlayerFromWorld() { @@ -2566,16 +2561,22 @@ } } } -@@ -2008,7 +_,7 @@ +@@ -2005,11 +_,13 @@ + if (!this.waitingForSwitchToConfig) { + throw new IllegalStateException("Client acknowledged config, but none was requested"); + } else { ++ final ServerConfigurationPacketListenerImpl listener = new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())); // Paper this.connection .setupInboundProtocol( ConfigurationProtocols.SERVERBOUND, - new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())) -+ new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()), this.player) // CraftBukkit ++ listener // Paper ); ++ new io.papermc.paper.event.connection.configuration.PlayerConnectionReconfigureEvent(listener.paperConnection).callEvent(); // Paper } } -@@ -2027,27 +_,32 @@ + +@@ -2027,27 +_,27 @@ private void resetPlayerChatState(RemoteChatSession chatSession) { this.chatSession = chatSession; @@ -2595,11 +2596,10 @@ + }); } ); - } - - @Override - public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { -+ super.handleCustomPayload(packet); // CraftBukkit - handled in super +- } +- +- @Override +- public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { } @Override @@ -2609,7 +2609,7 @@ if (!this.receivedMovementThisTick) { this.player.setKnownMovement(Vec3.ZERO); } -@@ -2078,4 +_,29 @@ +@@ -2078,4 +_,157 @@ interface EntityInteraction { InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); } @@ -2638,4 +2638,132 @@ + } + } + // Paper end - Implement click callbacks with custom click action ++ ++ // Paper start - add utilities ++ public org.bukkit.craftbukkit.entity.CraftPlayer getCraftPlayer() { ++ return this.player == null ? null : this.player.getBukkitEntity(); ++ } ++ ++ @Override ++ public void disconnectAsync(final net.minecraft.network.DisconnectionDetails disconnectionInfo) { ++ if (this.cserver.isPrimaryThread()) { ++ this.disconnect(disconnectionInfo); ++ return; ++ } ++ ++ this.connection.setReadOnly(); ++ this.server.scheduleOnMain(() -> { ++ this.disconnect(disconnectionInfo); ++ if (this.player.quitReason == null) { ++ // cancelled ++ this.connection.enableAutoRead(); ++ } ++ }); ++ } ++ ++ // Paper start ++ public static final ResourceLocation CUSTOM_REGISTER = ResourceLocation.withDefaultNamespace("register"); // CraftBukkit ++ private static final ResourceLocation CUSTOM_UNREGISTER = ResourceLocation.withDefaultNamespace("unregister"); // CraftBukkit ++ ++ @Override ++ public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { ++ // Paper start ++ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload(String brand)) { ++ this.playerBrand = brand; ++ } ++ ++ if (!(packet.payload() instanceof final net.minecraft.network.protocol.common.custom.DiscardedPayload discardedPayload)) { ++ return; ++ } ++ ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.level()); ++ ++ final net.minecraft.resources.ResourceLocation identifier = packet.payload().type().id(); ++ final byte[] data = discardedPayload.data(); ++ try { ++ final boolean registerChannel = CUSTOM_REGISTER.equals(identifier); ++ if (registerChannel || CUSTOM_UNREGISTER.equals(identifier)) { ++ // Strings separated by zeros instead of length prefixes ++ int startIndex = 0; ++ for (int i = 0; i < data.length; i++) { ++ final byte b = data[i]; ++ if (b != 0) { ++ continue; ++ } ++ ++ readChannelIdentifier(data, startIndex, i, registerChannel); ++ startIndex = i + 1; ++ } ++ ++ // Read the last one ++ readChannelIdentifier(data, startIndex, data.length, registerChannel); ++ return; ++ } ++ ++ if (identifier.equals(MINECRAFT_BRAND)) { ++ this.player.connection.playerBrand = new net.minecraft.network.FriendlyByteBuf(io.netty.buffer.Unpooled.wrappedBuffer(data)).readUtf(256); ++ } ++ ++ this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); ++ } catch (final Exception e) { ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn't handle custom payload on channel {}", identifier, e); ++ this.disconnect(Component.literal("Invalid custom payload payload!"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause ++ } ++ } ++ ++ private void readChannelIdentifier(final byte[] data, final int from, final int to, final boolean register) { ++ final int length = to - from; ++ if (length == 0) { ++ return; ++ } ++ ++ final String channel = new String(data, from, length, java.nio.charset.StandardCharsets.US_ASCII); ++ if (register) { ++ this.getCraftPlayer().addChannel(channel); ++ } else { ++ this.getCraftPlayer().removeChannel(channel); ++ } ++ } ++ ++ public final boolean isDisconnected() { ++ return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs ++ } ++ ++ @Override ++ public void handleResourcePackResponse(final net.minecraft.network.protocol.common.ServerboundResourcePackPacket packet) { ++ super.handleResourcePackResponse(packet); ++ // Paper start - store last pack status ++ org.bukkit.event.player.PlayerResourcePackStatusEvent.Status packStatus = org.bukkit.event.player.PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()]; ++ this.player.getBukkitEntity().resourcePackStatus = packStatus; ++ this.cserver.getPluginManager().callEvent(new org.bukkit.event.player.PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit ++ // Paper end - store last pack status ++ } ++ ++ // Paper start - adventure ++ public void disconnect(final net.kyori.adventure.text.Component reason) { ++ this.disconnect(reason, org.bukkit.event.player.PlayerKickEvent.Cause.UNKNOWN); ++ } ++ ++ public void disconnect(final net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), io.papermc.paper.connection.DisconnectionReason.game(cause)); ++ } ++ ++ public void disconnect(final Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { ++ this.disconnect(reason, io.papermc.paper.connection.DisconnectionReason.game(cause)); ++ } ++ ++ public void disconnectAsync(net.kyori.adventure.text.Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { ++ this.disconnectAsync(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause); ++ } ++ ++ public void disconnectAsync(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { ++ this.disconnectAsync(reason, io.papermc.paper.connection.DisconnectionReason.game(cause)); ++ } ++ ++ @Override ++ public io.papermc.paper.connection.ReadablePlayerCookieConnectionImpl getCookieConnection() { ++ return this.playerGameConnection; ++ } ++ ++ // Paper end } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch index d85b10aa39..9a11f8e87d 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch @@ -1,85 +1,72 @@ --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -44,9 +_,17 @@ +@@ -44,9 +_,16 @@ import org.apache.commons.lang3.Validate; import org.slf4j.Logger; --public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener { +// CraftBukkit start +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.util.Waitable; +import org.bukkit.event.player.AsyncPlayerPreLoginEvent; +import org.bukkit.event.player.PlayerPreLoginEvent; + -+public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener, CraftPlayer.TransferCookieConnection { -+ // CraftBukkit end + public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener { private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0); static final Logger LOGGER = LogUtils.getLogger(); + private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads private static final int MAX_TICKS_BEFORE_LOGIN = 600; private final byte[] challenge; final MinecraftServer server; -@@ -59,6 +_,9 @@ +@@ -54,21 +_,36 @@ + public volatile ServerLoginPacketListenerImpl.State state = ServerLoginPacketListenerImpl.State.HELLO; + private int tick; + @Nullable +- String requestedUsername; ++ public String requestedUsername; // Paper + @Nullable public GameProfile authenticatedProfile; private final String serverId = ""; - private final boolean transferred; -+ private net.minecraft.server.level.ServerPlayer player; // CraftBukkit +- private final boolean transferred; ++ public final boolean transferred; // Paper + public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding + private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support ++ public @Nullable java.util.UUID requestedUuid; // Paper ++ private final io.papermc.paper.connection.PaperPlayerLoginConnection paperLoginConnection; // Paper - Config API ++ private volatile boolean disconnecting = false; // Paper - Fix disconnect still ticking login public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { this.server = server; -@@ -67,11 +_,48 @@ + this.connection = connection; + this.challenge = Ints.toByteArray(RandomSource.create().nextInt()); this.transferred = transferred; ++ this.paperLoginConnection = new io.papermc.paper.connection.PaperPlayerLoginConnection(this); // Paper } -+ // CraftBukkit start -+ @Override -+ public boolean isTransferred() { -+ return this.transferred; -+ } -+ -+ @Override -+ public net.minecraft.network.ConnectionProtocol getProtocol() { -+ return net.minecraft.network.ConnectionProtocol.LOGIN; -+ } -+ -+ @Override -+ public void sendPacket(net.minecraft.network.protocol.Packet packet) { -+ this.connection.send(packet); -+ } -+ -+ @Override -+ public void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause) { // Paper - kick event causes - during login, no event can be called. -+ this.disconnect(reason); -+ } -+ // CraftBukkit end -+ @Override public void tick() { -+ // Paper start - Do not allow logins while the server is shutting down -+ if (!this.server.isRunning()) { -+ this.disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(org.spigotmc.SpigotConfig.restartMessage)[0]); ++ if (this.disconnecting) return; // Paper - Fix disconnect being called multiple times due to not awaiting for disconnect ++ // Paper start - login cookie API ++ // Don't block the connection ++ if (this.paperLoginConnection.isAwaitingCookies()) { ++ this.tickTimeout(); // Ensure we tick timeout logic + return; + } -+ // Paper end - Do not allow logins while the server is shutting down ++ // Paper end - login cookie API + if (this.state == ServerLoginPacketListenerImpl.State.VERIFYING) { -+ if (this.connection.isConnected()) { // Paper - prevent logins to be processed even though disconnect was called this.verifyLoginAndFinishConnectionSetup(Objects.requireNonNull(this.authenticatedProfile)); -- } -+ } // Paper - prevent logins to be processed even though disconnect was called -+ } -+ -+ // CraftBukkit start -+ if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES && !this.player.getBukkitEntity().isAwaitingCookies()) { -+ this.postCookies(this.authenticatedProfile); -+ } -+ // CraftBukkit end + } +@@ -78,11 +_,23 @@ + this.finishLoginAndWaitForClient(this.authenticatedProfile); + } - if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT - && !this.isPlayerAlreadyInWorld(Objects.requireNonNull(this.authenticatedProfile))) { -@@ -83,6 +_,13 @@ ++ // Paper start - login cookie API ++ this.tickTimeout(); ++ } ++ public void tickTimeout() { ++ // Paper end - login cookie API + if (this.tick++ == 600) { + this.disconnect(Component.translatable("multiplayer.disconnect.slow_login")); } } @@ -93,7 +80,15 @@ @Override public boolean isAcceptingMessages() { return this.connection.isConnected(); -@@ -115,7 +_,13 @@ +@@ -93,6 +_,7 @@ + LOGGER.info("Disconnecting {}: {}", this.getUserName(), reason.getString()); + this.connection.send(new ClientboundLoginDisconnectPacket(reason)); + this.connection.disconnect(reason); ++ this.disconnecting = true; // Paper - Fix disconnect still ticking login + } catch (Exception var3) { + LOGGER.error("Error whilst disconnecting player", (Throwable)var3); + } +@@ -115,7 +_,14 @@ @Override public void handleHello(ServerboundHelloPacket packet) { Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet"); @@ -104,6 +99,7 @@ + && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) { + Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username"); + } ++ this.requestedUuid = packet.profileId(); + // Paper end - Validate usernames this.requestedUsername = packet.name(); GameProfile singleplayerProfile = this.server.getSingleplayerProfile(); @@ -142,42 +138,15 @@ } } } -@@ -137,9 +_,23 @@ +@@ -137,7 +_,7 @@ private void verifyLoginAndFinishConnectionSetup(GameProfile profile) { PlayerList playerList = this.server.getPlayerList(); - Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), profile); -- if (component != null) { -- this.disconnect(component); -+ // CraftBukkit start - fire PlayerLoginEvent -+ this.player = playerList.canPlayerLogin(this, profile); // CraftBukkit -+ if (this.player != null) { -+ if (this.player.getBukkitEntity().isAwaitingCookies()) { -+ this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES; -+ } else { -+ this.postCookies(profile); -+ } -+ } -+ } -+ -+ private void postCookies(GameProfile profile) { -+ PlayerList playerList = this.server.getPlayerList(); -+ -+ if (this.player == null) { -+ // this.disconnect(component); -+ // CraftBukkit end ++ Component component = org.bukkit.craftbukkit.event.CraftEventFactory.handleLoginResult(playerList.canPlayerLogin(this.connection.getRemoteAddress(), profile), this.paperLoginConnection, this.connection, profile, this.server, true); // Paper + if (component != null) { + this.disconnect(component); } else { - if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) { - this.connection -@@ -149,7 +_,7 @@ - ); - } - -- boolean flag = playerList.disconnectAllPlayersWithProfile(profile); -+ boolean flag = playerList.disconnectAllPlayersWithProfile(profile, this.player); // CraftBukkit - add player reference - if (flag) { - this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT; - } else { @@ -184,7 +_,8 @@ throw new IllegalStateException("Protocol error", var7); } @@ -226,7 +195,7 @@ } } -@@ -222,24 +_,120 @@ +@@ -222,18 +_,113 @@ ? ((InetSocketAddress)remoteAddress).getAddress() : null; } @@ -254,7 +223,7 @@ + // Paper start - Add more fields to AsyncPlayerPreLoginEvent + final InetAddress rawAddress = ((InetSocketAddress) this.connection.channel.remoteAddress()).getAddress(); + com.destroystokyo.paper.profile.PlayerProfile profile = com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(gameprofile); // Paper - setPlayerProfileAPI -+ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, this.transferred, profile, this.connection.hostname); ++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, rawAddress, uniqueId, this.transferred, profile, this.connection.hostname, this.paperLoginConnection); // Paper + server.getPluginManager().callEvent(asyncEvent); + profile = asyncEvent.getPlayerProfile(); + profile.complete(true); // Paper - setPlayerProfileAPI @@ -344,24 +313,11 @@ Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet"); this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); CommonListenerCookie commonListenerCookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred); - ServerConfigurationPacketListenerImpl serverConfigurationPacketListenerImpl = new ServerConfigurationPacketListenerImpl( -- this.server, this.connection, commonListenerCookie -+ this.server, this.connection, commonListenerCookie, this.player // CraftBukkit - ); -+ - this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, serverConfigurationPacketListenerImpl); - serverConfigurationPacketListenerImpl.startConfiguration(); - this.state = ServerLoginPacketListenerImpl.State.ACCEPTED; -@@ -252,8 +_,36 @@ +@@ -252,8 +_,31 @@ @Override public void handleCookieResponse(ServerboundCookieResponsePacket packet) { -+ // CraftBukkit start -+ net.minecraft.network.protocol.PacketUtils.ensureRunningOnSameThread(packet, this, this.server); -+ if (this.player != null && this.player.getBukkitEntity().handleCookieResponse(packet)) { -+ return; -+ } -+ // CraftBukkit end ++ if (this.paperLoginConnection.handleCookieResponse(packet)) return; // Paper this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); } + @@ -389,11 +345,3 @@ public static enum State { HELLO, -@@ -261,6 +_,7 @@ - AUTHENTICATING, - NEGOTIATING, - VERIFYING, -+ WAITING_FOR_COOKIES, // CraftBukkit - WAITING_FOR_DUPE_DISCONNECT, - PROTOCOL_SWITCHING, - ACCEPTED; diff --git a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch index 35cfaca434..53789f75a2 100644 --- a/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/players/PlayerList.java.patch @@ -339,7 +339,7 @@ } @Override -@@ -319,56 +_,156 @@ +@@ -319,56 +_,138 @@ } protected void save(ServerPlayer player) { @@ -446,6 +446,12 @@ - } - - this.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(player.getUUID()))); +- } +- +- @Nullable +- public Component canPlayerLogin(SocketAddress socketAddress, GameProfile gameProfile) { +- if (this.bans.isBanned(gameProfile)) { +- UserBanListEntry userBanListEntry = this.bans.get(gameProfile); + // CraftBukkit start + // this.stats.remove(uuid); + // this.advancements.remove(uuid); @@ -468,140 +474,76 @@ + this.cserver.getScoreboardManager().removePlayer(player.getBukkitEntity()); + // CraftBukkit end + return playerQuitEvent.quitMessage(); // Paper - Adventure - } - -- @Nullable -- public Component canPlayerLogin(SocketAddress socketAddress, GameProfile gameProfile) { -- if (this.bans.isBanned(gameProfile)) { -- UserBanListEntry userBanListEntry = this.bans.get(gameProfile); -+ // CraftBukkit start - Whole method, SocketAddress to LoginListener, added hostname to signature, return EntityPlayer -+ public @Nullable ServerPlayer canPlayerLogin(net.minecraft.server.network.ServerLoginPacketListenerImpl loginlistener, GameProfile gameProfile) { -+ // if (this.bans.isBanned(gameProfile)) { -+ // UserBanListEntry userBanListEntry = this.bans.get(gameProfile); -+ // Moved from disconnectAllPlayersWithProfile -+ UUID uuid = gameProfile.getId(); -+ List list = Lists.newArrayList(); ++ } + -+ for (net.minecraft.server.level.ServerPlayer serverPlayer : this.players) { -+ if (serverPlayer.getUUID().equals(uuid) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && serverPlayer.getGameProfile().getName().equalsIgnoreCase(gameProfile.getName()))) { // Paper - validate usernames -+ list.add(serverPlayer); -+ } ++ // Paper start - PlayerLoginEvent ++ public record LoginResult(@Nullable Component message, @org.jetbrains.annotations.NotNull org.bukkit.event.player.PlayerLoginEvent.Result result) { ++ public static LoginResult ALLOW = new net.minecraft.server.players.PlayerList.LoginResult(null, org.bukkit.event.player.PlayerLoginEvent.Result.ALLOWED); ++ ++ public boolean isAllowed() { ++ return this == ALLOW; + } -+ -+ for (final net.minecraft.server.level.ServerPlayer serverPlayer : list) { -+ this.save(serverPlayer); // CraftBukkit - Force the player's inventory to be saved -+ serverPlayer.connection.disconnect(DUPLICATE_LOGIN_DISCONNECT_MESSAGE, org.bukkit.event.player.PlayerKickEvent.Cause.DUPLICATE_LOGIN); // Paper - kick event cause -+ } -+ -+ // Instead of kicking then returning, we need to store the kick reason -+ // in the event, check with plugins to see if it's ok, and THEN kick -+ // depending on the outcome. -+ SocketAddress socketAddress = loginlistener.connection.getRemoteAddress(); -+ -+ ServerPlayer entity = new ServerPlayer(this.server, this.server.getLevel(Level.OVERWORLD), gameProfile, net.minecraft.server.level.ClientInformation.createDefault()); -+ entity.transferCookieConnection = loginlistener; -+ org.bukkit.entity.Player player = entity.getBukkitEntity(); -+ org.bukkit.event.player.PlayerLoginEvent event = new org.bukkit.event.player.PlayerLoginEvent(player, loginlistener.connection.hostname, ((java.net.InetSocketAddress) socketAddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.channel.remoteAddress()).getAddress()); -+ ++ } ++ // Paper end - PlayerLoginEvent ++ public LoginResult canPlayerLogin(SocketAddress socketAddress, GameProfile gameProfile) { // Paper - PlayerLoginEvent ++ LoginResult whitelistEventResult; // Paper + // Paper start - Fix MC-158900 + UserBanListEntry userBanListEntry; + if (this.bans.isBanned(gameProfile) && (userBanListEntry = this.bans.get(gameProfile)) != null) { -+ // Paper end - Fix MC-158900 ++ // Paper end - Fix MC-158900 MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned.reason", userBanListEntry.getReason()); if (userBanListEntry.getExpires() != null) { mutableComponent.append( -@@ -376,10 +_,12 @@ +@@ -376,9 +_,11 @@ ); } - return mutableComponent; - } else if (!this.isWhiteListed(gameProfile)) { - return Component.translatable("multiplayer.disconnect.not_whitelisted"); -- } else if (this.ipBans.isBanned(socketAddress)) { -+ // return mutableComponent; -+ event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(mutableComponent)); // Paper - Adventure -+ } else if (!this.isWhiteListed(gameProfile, event)) { // Paper - ProfileWhitelistVerifyEvent -+ // return Component.translatable("multiplayer.disconnect.not_whitelisted"); -+ //event.disallow(PlayerLoginEvent.Result.KICK_WHITELIST, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.whitelistMessage)); // Spigot // Paper - Adventure - moved to isWhitelisted -+ } else if (this.ipBans.isBanned(socketAddress) && getIpBans().get(socketAddress) != null && !this.getIpBans().get(socketAddress).hasExpired()) { // Paper - fix NPE with temp ip bans ++ return new LoginResult(mutableComponent, org.bukkit.event.player.PlayerLoginEvent.Result.KICK_BANNED); // Paper - PlayerLoginEvent ++ // Paper start - whitelist event ++ } else if ((whitelistEventResult = this.isWhiteListedLogin(gameProfile)).result == org.bukkit.event.player.PlayerLoginEvent.Result.KICK_WHITELIST) { ++ return whitelistEventResult; ++ // Paper end + } else if (this.ipBans.isBanned(socketAddress)) { IpBanListEntry ipBanListEntry = this.ipBans.get(socketAddress); MutableComponent mutableComponent = Component.translatable("multiplayer.disconnect.banned_ip.reason", ipBanListEntry.getReason()); - if (ipBanListEntry.getExpires() != null) { -@@ -388,65 +_,124 @@ +@@ -388,11 +_,9 @@ ); } - return mutableComponent; -+ // return mutableComponent; -+ event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(mutableComponent)); // Paper - Adventure ++ return new LoginResult(mutableComponent, org.bukkit.event.player.PlayerLoginEvent.Result.KICK_BANNED); // Paper - PlayerLoginEvent } else { - return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile) - ? Component.translatable("multiplayer.disconnect.server_full") - : null; -- } -- } -- -- public boolean disconnectAllPlayersWithProfile(GameProfile gameProfile) { -- UUID id = gameProfile.getId(); -- Set set = Sets.newIdentityHashSet(); -- -- for (ServerPlayer serverPlayer : this.players) { ++ return this.canBypassFullServerLogin(gameProfile, new LoginResult(Component.translatable("multiplayer.disconnect.server_full"), org.bukkit.event.player.PlayerLoginEvent.Result.KICK_FULL)); // Paper - PlayerServerFullCheckEvent + } + } + +@@ -401,7 +_,7 @@ + Set set = Sets.newIdentityHashSet(); + + for (ServerPlayer serverPlayer : this.players) { - if (serverPlayer.getUUID().equals(id)) { -- set.add(serverPlayer); -+ // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile) -+ // ? Component.translatable("multiplayer.disconnect.server_full") -+ // : null; -+ if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameProfile)) { -+ event.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure ++ if (serverPlayer.getUUID().equals(id) || (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && serverPlayer.getGameProfile().getName().equalsIgnoreCase(gameProfile.getName()))) { // Paper - validate usernames + set.add(serverPlayer); } } -- -- ServerPlayer serverPlayer1 = this.playersByUUID.get(gameProfile.getId()); -- if (serverPlayer1 != null) { -- set.add(serverPlayer1); -- } -- -- for (ServerPlayer serverPlayer2 : set) { +@@ -412,41 +_,87 @@ + } + + for (ServerPlayer serverPlayer2 : set) { - serverPlayer2.connection.disconnect(DUPLICATE_LOGIN_DISCONNECT_MESSAGE); -- } -- -- return !set.isEmpty(); -- } -- ++ serverPlayer2.connection.disconnect(DUPLICATE_LOGIN_DISCONNECT_MESSAGE, io.papermc.paper.connection.DisconnectionReason.DUPLICATE_LOGIN_MESSAGE); // Paper - disconnect API + } + + return !set.isEmpty(); + } + - public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason) { -+ this.cserver.getPluginManager().callEvent(event); -+ if (event.getResult() != org.bukkit.event.player.PlayerLoginEvent.Result.ALLOWED) { -+ loginlistener.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.kickMessage())); // Paper - Adventure -+ return null; -+ } -+ return entity; -+ } -+ -+ public boolean disconnectAllPlayersWithProfile(GameProfile gameProfile, ServerPlayer player) { // CraftBukkit - added ServerPlayer -+ // CraftBukkit start - Moved up -+ // UUID id = gameProfile.getId(); -+ // Set set = Sets.newIdentityHashSet(); -+ // -+ // for (ServerPlayer serverPlayer : this.players) { -+ // if (serverPlayer.getUUID().equals(id)) { -+ // set.add(serverPlayer); -+ // } -+ // } -+ // -+ // ServerPlayer serverPlayer1 = this.playersByUUID.get(gameProfile.getId()); -+ // if (serverPlayer1 != null) { -+ // set.add(serverPlayer1); -+ // } -+ // -+ // for (ServerPlayer serverPlayer2 : set) { -+ // serverPlayer2.connection.disconnect(DUPLICATE_LOGIN_DISCONNECT_MESSAGE); -+ // } -+ // -+ // return !set.isEmpty(); -+ return player == null; -+ // CraftBukkit end -+ } -+ + // CraftBukkit start + public ServerPlayer respawn(ServerPlayer player, boolean keepInventory, Entity.RemovalReason reason, @Nullable org.bukkit.event.player.PlayerRespawnEvent.RespawnReason eventReason) { + return this.respawn(player, keepInventory, reason, eventReason, null); @@ -836,41 +778,57 @@ if (player.connection != null) { byte b; if (permLevel <= 0) { -@@ -597,11 +_,33 @@ +@@ -597,9 +_,48 @@ player.connection.send(new ClientboundEntityEventPacket(player, b)); } + if (recalculatePermissions) { // Paper - Add sendOpLevel API + player.getBukkitEntity().recalculatePermissions(); // CraftBukkit this.server.getCommands().sendCommands(player); +- } +- + } // Paper - Add sendOpLevel API - } - - public boolean isWhiteListed(GameProfile profile) { -- return !this.doWhiteList || this.ops.contains(profile) || this.whitelist.contains(profile); -+ // Paper start - ProfileWhitelistVerifyEvent -+ return this.isWhiteListed(profile, null); + } + -+ public boolean isWhiteListed(GameProfile gameprofile, @Nullable org.bukkit.event.player.PlayerLoginEvent loginEvent) { -+ boolean isOp = this.ops.contains(gameprofile); -+ boolean isWhitelisted = !this.doWhiteList || isOp || this.whitelist.contains(gameprofile); -+ final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event; ++ // Paper start - whitelist verify event / login event ++ public LoginResult canBypassFullServerLogin(final GameProfile profile, final LoginResult currentResult) { ++ final boolean shouldKick = this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(profile); ++ final io.papermc.paper.event.player.PlayerServerFullCheckEvent fullCheckEvent = new io.papermc.paper.event.player.PlayerServerFullCheckEvent( ++ com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(profile), ++ io.papermc.paper.adventure.PaperAdventure.asAdventure(currentResult.message), ++ shouldKick ++ ); ++ ++ fullCheckEvent.callEvent(); ++ if (fullCheckEvent.isAllowed()) { ++ return net.minecraft.server.players.PlayerList.LoginResult.ALLOW; ++ } else { ++ return new net.minecraft.server.players.PlayerList.LoginResult( ++ io.papermc.paper.adventure.PaperAdventure.asVanilla(fullCheckEvent.kickMessage()), currentResult.result ++ ); ++ } ++ } ++ ++ public LoginResult isWhiteListedLogin(GameProfile profile) { ++ boolean isOp = this.ops.contains(profile); ++ boolean isWhitelisted = !this.doWhiteList || isOp || this.whitelist.contains(profile); + + final net.kyori.adventure.text.Component configuredMessage = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.whitelistMessage); -+ event = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(gameprofile), this.doWhiteList, isWhitelisted, isOp, configuredMessage); ++ final com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent event ++ = new com.destroystokyo.paper.event.profile.ProfileWhitelistVerifyEvent(com.destroystokyo.paper.profile.CraftPlayerProfile.asBukkitMirror(profile), this.doWhiteList, isWhitelisted, isOp, configuredMessage); + event.callEvent(); + if (!event.isWhitelisted()) { -+ if (loginEvent != null) { -+ loginEvent.disallow(org.bukkit.event.player.PlayerLoginEvent.Result.KICK_WHITELIST, event.kickMessage() == null ? configuredMessage : event.kickMessage()); -+ } -+ return false; ++ return new net.minecraft.server.players.PlayerList.LoginResult(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.kickMessage() == null ? configuredMessage : event.kickMessage()), org.bukkit.event.player.PlayerLoginEvent.Result.KICK_WHITELIST); + } -+ return true; -+ // Paper end - ProfileWhitelistVerifyEvent ++ ++ return net.minecraft.server.players.PlayerList.LoginResult.ALLOW; ++ } ++ // Paper end ++ ++ @io.papermc.paper.annotation.DoNotUse // Paper + public boolean isWhiteListed(GameProfile profile) { + return !this.doWhiteList || this.ops.contains(profile) || this.whitelist.contains(profile); } - - public boolean isOp(GameProfile profile) { @@ -612,21 +_,17 @@ @Nullable diff --git a/paper-server/src/main/java/io/papermc/paper/connection/DisconnectionReason.java b/paper-server/src/main/java/io/papermc/paper/connection/DisconnectionReason.java new file mode 100644 index 0000000000..64fac9b463 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/connection/DisconnectionReason.java @@ -0,0 +1,28 @@ +package io.papermc.paper.connection; + +import java.util.Optional; +import org.bukkit.event.player.PlayerKickEvent; + +// This class is used for when there may be a want for a common disconnect event for the configuration stage. +public interface DisconnectionReason { + + DisconnectionReason UNKNOWN = game(PlayerKickEvent.Cause.UNKNOWN); + DisconnectionReason TIMEOUT = game(PlayerKickEvent.Cause.TIMEOUT); + DisconnectionReason RESOURCE_PACK_REJECTION = game(PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); + DisconnectionReason INVALID_COOKIE = game(PlayerKickEvent.Cause.INVALID_COOKIE); + DisconnectionReason DUPLICATE_LOGIN_MESSAGE = game(PlayerKickEvent.Cause.DUPLICATE_LOGIN); + + Optional game(); + + static DisconnectionReason game(PlayerKickEvent.Cause cause) { + return new GameEntry(cause); + } + + record GameEntry(PlayerKickEvent.Cause gameQuitEvent) implements DisconnectionReason { + + @Override + public Optional game() { + return Optional.ofNullable(this.gameQuitEvent); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/connection/HorriblePlayerLoginEventHack.java b/paper-server/src/main/java/io/papermc/paper/connection/HorriblePlayerLoginEventHack.java new file mode 100644 index 0000000000..1e39d21a63 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/connection/HorriblePlayerLoginEventHack.java @@ -0,0 +1,99 @@ +package io.papermc.paper.connection; + +import com.mojang.authlib.GameProfile; +import com.mojang.logging.LogUtils; +import io.papermc.paper.adventure.PaperAdventure; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import io.papermc.paper.util.StackWalkerUtil; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; +import net.minecraft.world.level.Level; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.plugin.RegisteredListener; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; + +// TODO: DO we even need this? Who mutates the player here? it does nothing anyways. +// TODO: Make sure player settings are migrated too +@NullMarked +public class HorriblePlayerLoginEventHack { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + private static boolean nagged = false; + + public static void warnReenterConfiguration() { + LOGGER.warn(""" + + ============================================================ + WARNING: {} Attempted to use PlayerGameConnection#reenterConfiguration() + + This method currently requires that all plugins installed on the server + are not listening to the PlayerLoginEvent. + + Please look in your logs for the Plugins listening to this event. + ============================================================""", StackWalkerUtil.getFirstPluginCaller().getName()); + } + + public static @Nullable Component execute(final Connection connection, MinecraftServer server, GameProfile profile, PlayerList.LoginResult result) { + if (PlayerLoginEvent.getHandlerList().getRegisteredListeners().length == 0) { + return result.message(); + } + + if (!nagged) { + List plugins = new ArrayList<>(); + for (final RegisteredListener listener : PlayerLoginEvent.getHandlerList().getRegisteredListeners()) { + plugins.add(listener.getPlugin().getName()); + } + + LOGGER.warn(""" + + ============================================================ + WARNING: Legacy PlayerLoginEvent usage detected! + + This event forces an alternative player loading path that is + deprecated and will be removed in a future release. + For more information, see: https://forums.papermc.io/threads/1-21-7.1635 + + Please notify the following plugin developers: {} + ============================================================""", plugins.toString()); + nagged = true; + } + // We need to account for the fact that during the config stage we now call this event to mimic the old behavior. + // However, we want to make sure that we still properly hold the same player reference + ServerPlayer player; + if (connection.savedPlayerForLoginEventLegacy != null) { + player = connection.savedPlayerForLoginEventLegacy; + } else { + ServerPlayer serverPlayer = new ServerPlayer(server, server.getLevel(Level.OVERWORLD), profile, ClientInformation.createDefault()); + connection.savedPlayerForLoginEventLegacy = serverPlayer; + player = serverPlayer; + } + + CraftPlayer horribleBukkitPlayer = player.getBukkitEntity(); + PlayerLoginEvent event = new PlayerLoginEvent(horribleBukkitPlayer, connection.hostname, ((java.net.InetSocketAddress) connection.getRemoteAddress()).getAddress(), ((java.net.InetSocketAddress) connection.channel.remoteAddress()).getAddress()); + event.disallow(result.result(), PaperAdventure.asAdventure(result.message())); + event.callEvent(); + + Component finalResult; + if (event.getResult() == PlayerLoginEvent.Result.ALLOWED) { + finalResult = null; + } else { + finalResult = PaperAdventure.asVanilla(event.kickMessage()); + } + + // If the event changed the result, save this result. We need to save this for config + if (event.getResult() != result.result()) { + connection.legacySavedLoginEventResultOverride = Optional.ofNullable(finalResult); + } + + return finalResult; + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/connection/PaperCommonConnection.java b/paper-server/src/main/java/io/papermc/paper/connection/PaperCommonConnection.java new file mode 100644 index 0000000000..8fc168da1d --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/connection/PaperCommonConnection.java @@ -0,0 +1,114 @@ +package io.papermc.paper.connection; + +import com.destroystokyo.paper.ClientOption; +import com.destroystokyo.paper.PaperSkinParts; +import com.google.common.base.Preconditions; +import io.papermc.paper.adventure.PaperAdventure; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.Map; +import net.kyori.adventure.text.Component; +import net.minecraft.network.protocol.common.ClientboundCustomReportDetailsPacket; +import net.minecraft.network.protocol.common.ClientboundServerLinksPacket; +import net.minecraft.network.protocol.common.ClientboundStoreCookiePacket; +import net.minecraft.network.protocol.common.ClientboundTransferPacket; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.network.ServerCommonPacketListenerImpl; +import org.bukkit.NamespacedKey; +import org.bukkit.ServerLinks; +import org.bukkit.craftbukkit.CraftServerLinks; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.jspecify.annotations.Nullable; + +public abstract class PaperCommonConnection extends ReadablePlayerCookieConnectionImpl implements PlayerCommonConnection { + + protected final T handle; + + public PaperCommonConnection(final T serverConfigurationPacketListenerImpl) { + super(serverConfigurationPacketListenerImpl.connection); + this.handle = serverConfigurationPacketListenerImpl; + } + + @Override + public void sendReportDetails(final Map details) { + this.handle.send(new ClientboundCustomReportDetailsPacket(details)); + } + + @Override + public void sendLinks(final ServerLinks links) { + this.handle.send(new ClientboundServerLinksPacket(((CraftServerLinks) links).getServerLinks().untrust())); + } + + @Override + public void transfer(final String host, final int port) { + this.handle.send(new ClientboundTransferPacket(host, port)); + } + + @Override + public T getClientOption(ClientOption type) { + ClientInformation information = this.getClientInformation(); + + if (ClientOption.SKIN_PARTS == type) { + return type.getType().cast(new PaperSkinParts(information.modelCustomisation())); + } else if (ClientOption.CHAT_COLORS_ENABLED == type) { + return type.getType().cast(information.chatColors()); + } else if (ClientOption.CHAT_VISIBILITY == type) { + return type.getType().cast(ClientOption.ChatVisibility.valueOf(information.chatVisibility().name())); + } else if (ClientOption.LOCALE == type) { + return type.getType().cast(information.language()); + } else if (ClientOption.MAIN_HAND == type) { + return type.getType().cast(information.mainHand()); + } else if (ClientOption.VIEW_DISTANCE == type) { + return type.getType().cast(information.viewDistance()); + } else if (ClientOption.TEXT_FILTERING_ENABLED == type) { + return type.getType().cast(information.textFilteringEnabled()); + } else if (ClientOption.ALLOW_SERVER_LISTINGS == type) { + return type.getType().cast(information.allowsListing()); + } else if (ClientOption.PARTICLE_VISIBILITY == type) { + return type.getType().cast(ClientOption.ParticleVisibility.valueOf(information.particleStatus().name())); + } + throw new RuntimeException("Unknown settings type"); + } + + @Override + public void disconnect(final Component component) { + this.handle.disconnect(PaperAdventure.asVanilla(component), DisconnectionReason.UNKNOWN); + } + + @Override + public boolean isTransferred() { + return this.handle.isTransferred(); + } + + @Override + public SocketAddress getAddress() { + return this.handle.connection.getRemoteAddress(); + } + + @Override + public InetSocketAddress getClientAddress() { + return (InetSocketAddress) this.handle.connection.channel.remoteAddress(); + } + + @Override + public @Nullable InetSocketAddress getVirtualHost() { + return this.handle.connection.virtualHost; + } + + @Override + public @Nullable InetSocketAddress getHAProxyAddress() { + return this.handle.connection.haProxyAddress instanceof final InetSocketAddress inetSocketAddress ? inetSocketAddress : null; + } + + @Override + public void storeCookie(final NamespacedKey key, final byte[] value) { + Preconditions.checkArgument(key != null, "Cookie key cannot be null"); + Preconditions.checkArgument(value != null, "Cookie value cannot be null"); + Preconditions.checkArgument(value.length <= 5120, "Cookie value too large, must be smaller than 5120 bytes"); + Preconditions.checkState(this.canStoreCookie(), "Can only store cookie in CONFIGURATION or PLAY protocol."); + + this.handle.send(new ClientboundStoreCookiePacket(CraftNamespacedKey.toMinecraft(key), value)); + } + + public abstract ClientInformation getClientInformation(); +} diff --git a/paper-server/src/main/java/io/papermc/paper/connection/PaperConfigurationTask.java b/paper-server/src/main/java/io/papermc/paper/connection/PaperConfigurationTask.java new file mode 100644 index 0000000000..439ae39cc4 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/connection/PaperConfigurationTask.java @@ -0,0 +1,46 @@ +package io.papermc.paper.connection; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.mojang.logging.LogUtils; +import io.papermc.paper.event.connection.configuration.AsyncPlayerConnectionConfigureEvent; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; +import net.minecraft.DefaultUncaughtExceptionHandler; +import net.minecraft.network.protocol.Packet; +import net.minecraft.server.network.ConfigurationTask; +import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; +import org.slf4j.Logger; + +public class PaperConfigurationTask implements ConfigurationTask { + private static final Logger LOGGER = LogUtils.getClassLogger(); + + private static final ExecutorService CONFIGURATION_POOL = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("Configuration Thread #%d") + .setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); + + public static final ConfigurationTask.Type TYPE = new ConfigurationTask.Type("paper_event_handling"); + + private final ServerConfigurationPacketListenerImpl packetListener; + + public PaperConfigurationTask(ServerConfigurationPacketListenerImpl packetListener) { + this.packetListener = packetListener; + } + + @Override + public void start(final Consumer> task) { + if (AsyncPlayerConnectionConfigureEvent.getHandlerList().getRegisteredListeners().length == 0) { + this.packetListener.finishCurrentTask(TYPE); + return; + } + CONFIGURATION_POOL.execute(() -> { + AsyncPlayerConnectionConfigureEvent event = new AsyncPlayerConnectionConfigureEvent(this.packetListener.paperConnection); + event.callEvent(); + this.packetListener.finishCurrentTask(TYPE); + }); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java new file mode 100644 index 0000000000..b71fbf68de --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java @@ -0,0 +1,104 @@ +package io.papermc.paper.connection; + +import com.destroystokyo.paper.ClientOption; +import com.destroystokyo.paper.PaperSkinParts; +import com.destroystokyo.paper.profile.CraftPlayerProfile; +import com.destroystokyo.paper.profile.PlayerProfile; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.pointer.Pointers; +import net.kyori.adventure.resource.ResourcePackCallback; +import net.kyori.adventure.resource.ResourcePackInfo; +import net.kyori.adventure.resource.ResourcePackRequest; +import net.minecraft.network.protocol.common.ClientboundResourcePackPopPacket; +import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket; +import net.minecraft.network.protocol.configuration.ClientboundResetChatPacket; +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.network.ConfigurationTask; +import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +public class PaperPlayerConfigurationConnection extends PaperCommonConnection implements PlayerConfigurationConnection, Audience { + + private @Nullable Pointers adventurePointers; + + public PaperPlayerConfigurationConnection(final ServerConfigurationPacketListenerImpl packetListener) { + super(packetListener); + } + + @Override + public ClientInformation getClientInformation() { + return this.handle.clientInformation; + } + + @Override + public void sendResourcePacks(ResourcePackRequest request) { + final List packs = new java.util.ArrayList<>(request.packs().size()); + if (request.replace()) { + this.clearResourcePacks(); + } + final net.minecraft.network.chat.Component prompt = PaperAdventure.asVanilla(request.prompt()); + for (final java.util.Iterator iter = request.packs().iterator(); iter.hasNext(); ) { + final ResourcePackInfo pack = iter.next(); + packs.add(new ClientboundResourcePackPushPacket(pack.id(), pack.uri().toASCIIString(), pack.hash(), request.required(), iter.hasNext() ? Optional.empty() : Optional.ofNullable(prompt))); + if (request.callback() != ResourcePackCallback.noOp()) { + this.handle.packCallbacks.put(pack.id(), request.callback()); // just override if there is a previously existing callback + } + } + packs.forEach(this.handle::send); + } + + @Override + public void removeResourcePacks(UUID id, UUID... others) { + net.kyori.adventure.util.MonkeyBars.nonEmptyArrayToList(pack -> new ClientboundResourcePackPopPacket(Optional.of(pack)), id, others).forEach(this.handle::send); + } + + @Override + public void clearResourcePacks() { + this.handle.send(new ClientboundResourcePackPopPacket(Optional.empty())); + } + + @Override + public Pointers pointers() { + if (this.adventurePointers == null) { + this.adventurePointers = Pointers.builder() + .withDynamic(Identity.NAME, () -> this.handle.getOwner().getName()) + .withDynamic(Identity.UUID, () -> this.handle.getOwner().getId()) + .build(); + } + + return this.adventurePointers; + } + + @Override + public Audience getAudience() { + return this; + } + + @Override + public PlayerProfile getProfile() { + return CraftPlayerProfile.asBukkitCopy(this.handle.getOwner()); + } + + @Override + public void clearChat() { + this.handle.send(ClientboundResetChatPacket.INSTANCE); + } + + @Override + public void completeReconfiguration() { + ConfigurationTask task = this.handle.currentTask; + if (task != null) { + // This means that the player is going through the normal configuration process, or is already returning to the game phase. + // Be safe and just ignore, as many plugins may call this. + return; + } + + this.handle.returnToWorld(); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerGameConnection.java b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerGameConnection.java new file mode 100644 index 0000000000..452d7229b1 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerGameConnection.java @@ -0,0 +1,31 @@ +package io.papermc.paper.connection; + +import net.minecraft.server.level.ClientInformation; +import net.minecraft.server.network.ServerGamePacketListenerImpl; +import org.bukkit.entity.Player; + +public class PaperPlayerGameConnection extends PaperCommonConnection implements PlayerGameConnection { + + public PaperPlayerGameConnection(final ServerGamePacketListenerImpl serverConfigurationPacketListenerImpl) { + super(serverConfigurationPacketListenerImpl); + } + + @Override + public ClientInformation getClientInformation() { + return this.handle.player.clientInformation(); + } + + @Override + public void reenterConfiguration() { + if (this.handle.connection.savedPlayerForLoginEventLegacy != null) { + HorriblePlayerLoginEventHack.warnReenterConfiguration(); + return; + } + this.handle.switchToConfig(); + } + + @Override + public Player getPlayer() { + return this.handle.getCraftPlayer(); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerLoginConnection.java b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerLoginConnection.java new file mode 100644 index 0000000000..7269a08f77 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerLoginConnection.java @@ -0,0 +1,64 @@ +package io.papermc.paper.connection; + +import com.destroystokyo.paper.profile.CraftPlayerProfile; +import com.destroystokyo.paper.profile.PlayerProfile; +import io.papermc.paper.adventure.PaperAdventure; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import net.kyori.adventure.text.Component; +import net.minecraft.server.network.ServerLoginPacketListenerImpl; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +@NullMarked +public class PaperPlayerLoginConnection extends ReadablePlayerCookieConnectionImpl implements PlayerLoginConnection { + + private final ServerLoginPacketListenerImpl packetListener; + + public PaperPlayerLoginConnection(final ServerLoginPacketListenerImpl packetListener) { + super(packetListener.connection); + this.packetListener = packetListener; + } + + @Override + public @Nullable PlayerProfile getAuthenticatedProfile() { + return this.packetListener.authenticatedProfile == null ? null : CraftPlayerProfile.asBukkitCopy(this.packetListener.authenticatedProfile); + } + + @Override + public @Nullable PlayerProfile getUnsafeProfile() { + // This can possibly error as requestedUsername can have wacky stuff. But its fine I doubt mojang will support this + // much longer. + return new CraftPlayerProfile(this.packetListener.requestedUuid, this.packetListener.requestedUsername); + } + + @Override + public SocketAddress getAddress() { + return this.packetListener.connection.getRemoteAddress(); + } + + @Override + public InetSocketAddress getClientAddress() { + return (InetSocketAddress) this.packetListener.connection.channel.remoteAddress(); + } + + @Override + public @org.jspecify.annotations.Nullable InetSocketAddress getVirtualHost() { + return this.packetListener.connection.virtualHost; + } + + @Override + public @org.jspecify.annotations.Nullable InetSocketAddress getHAProxyAddress() { + return this.packetListener.connection.haProxyAddress instanceof final InetSocketAddress inetSocketAddress ? inetSocketAddress : null; + } + + @Override + public boolean isTransferred() { + return this.packetListener.transferred; + } + + @Override + public void disconnect(final Component component) { + this.packetListener.disconnect(PaperAdventure.asVanilla(component)); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/connection/ReadablePlayerCookieConnectionImpl.java b/paper-server/src/main/java/io/papermc/paper/connection/ReadablePlayerCookieConnectionImpl.java new file mode 100644 index 0000000000..a537efef8a --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/connection/ReadablePlayerCookieConnectionImpl.java @@ -0,0 +1,60 @@ +package io.papermc.paper.connection; + +import com.google.common.base.Preconditions; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.cookie.ClientboundCookieRequestPacket; +import net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket; +import net.minecraft.resources.ResourceLocation; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.util.CraftNamespacedKey; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public abstract class ReadablePlayerCookieConnectionImpl implements ReadablePlayerCookieConnection { + + // Because we support async cookies, order is not promised. + private final Map requestedCookies = new ConcurrentHashMap<>(); + private final Connection connection; + + public ReadablePlayerCookieConnectionImpl(final Connection connection) { + this.connection = connection; + } + + @Override + public CompletableFuture retrieveCookie(final NamespacedKey key) { + Preconditions.checkArgument(key != null, "Cookie key cannot be null"); + + CompletableFuture future = new CompletableFuture<>(); + ResourceLocation resourceLocation = CraftNamespacedKey.toMinecraft(key); + this.requestedCookies.put(resourceLocation, new CookieFuture(resourceLocation, future)); + + this.connection.send(new ClientboundCookieRequestPacket(resourceLocation)); + + return future; + } + + public boolean canStoreCookie() { + return true; + } + + public boolean handleCookieResponse(ServerboundCookieResponsePacket packet) { + CookieFuture future = this.requestedCookies.get(packet.key()); + if (future != null) { + future.future().complete(packet.payload()); + this.requestedCookies.remove(packet.key()); + return true; + } + + return false; + } + + public boolean isAwaitingCookies() { + return !this.requestedCookies.isEmpty(); + } + + public record CookieFuture(ResourceLocation key, CompletableFuture future) { + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 3e18819ae2..3ea6d207b2 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -9,6 +9,7 @@ import com.mojang.authlib.GameProfile; import com.mojang.datafixers.util.Pair; import io.papermc.paper.FeatureHooks; import io.papermc.paper.configuration.GlobalConfiguration; +import io.papermc.paper.connection.PlayerGameConnection; import io.papermc.paper.entity.LookAnchor; import io.papermc.paper.entity.PaperPlayerGiveResult; import io.papermc.paper.entity.PlayerGiveResult; @@ -331,75 +332,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this.getHandle().connection.connection.haProxyAddress instanceof final InetSocketAddress inetSocketAddress ? inetSocketAddress : null; } // Paper end - Add API to get player's proxy address - - public interface TransferCookieConnection { - - boolean isTransferred(); - - ConnectionProtocol getProtocol(); - - void sendPacket(Packet packet); - - void kickPlayer(Component reason, org.bukkit.event.player.PlayerKickEvent.Cause cause); // Paper - kick event causes - } - - public record CookieFuture(ResourceLocation key, CompletableFuture future) { - - } - private final Queue requestedCookies = new LinkedList<>(); - - public boolean isAwaitingCookies() { - return !this.requestedCookies.isEmpty(); - } - - public boolean handleCookieResponse(ServerboundCookieResponsePacket response) { - CookieFuture future = this.requestedCookies.peek(); - if (future != null) { - if (future.key.equals(response.key())) { - Preconditions.checkState(future == this.requestedCookies.poll(), "requestedCookies queue mismatch"); - - future.future().complete(response.payload()); - return true; - } - } - - return false; - } - @Override public boolean isTransferred() { - return this.getHandle().transferCookieConnection.isTransferred(); + return this.getHandle().connection.playerGameConnection.isTransferred(); } @Override public CompletableFuture retrieveCookie(final NamespacedKey key) { - Preconditions.checkArgument(key != null, "Cookie key cannot be null"); - - CompletableFuture future = new CompletableFuture<>(); - ResourceLocation nms = CraftNamespacedKey.toMinecraft(key); - this.requestedCookies.add(new CookieFuture(nms, future)); - - this.getHandle().transferCookieConnection.sendPacket(new ClientboundCookieRequestPacket(nms)); - - return future; + return this.getHandle().connection.playerGameConnection.retrieveCookie(key); } @Override public void storeCookie(NamespacedKey key, byte[] value) { - Preconditions.checkArgument(key != null, "Cookie key cannot be null"); - Preconditions.checkArgument(value != null, "Cookie value cannot be null"); - Preconditions.checkArgument(value.length <= 5120, "Cookie value too large, must be smaller than 5120 bytes"); - Preconditions.checkState(this.getHandle().transferCookieConnection.getProtocol() == ConnectionProtocol.CONFIGURATION || this.getHandle().transferCookieConnection.getProtocol() == ConnectionProtocol.PLAY, "Can only store cookie in CONFIGURATION or PLAY protocol."); - - this.getHandle().transferCookieConnection.sendPacket(new ClientboundStoreCookiePacket(CraftNamespacedKey.toMinecraft(key), value)); + this.getHandle().connection.playerGameConnection.storeCookie(key, value); } @Override public void transfer(String host, int port) { - Preconditions.checkArgument(host != null, "Host cannot be null"); - Preconditions.checkState(this.getHandle().transferCookieConnection.getProtocol() == ConnectionProtocol.CONFIGURATION || this.getHandle().transferCookieConnection.getProtocol() == ConnectionProtocol.PLAY, "Can only transfer in CONFIGURATION or PLAY protocol."); - - this.getHandle().transferCookieConnection.sendPacket(new ClientboundTransferPacket(host, port)); + this.getHandle().connection.playerGameConnection.transfer(host, port); } // Paper start - Implement NetworkClient @@ -699,7 +649,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public void kickPlayer(String message) { org.spigotmc.AsyncCatcher.catchOp("player kick"); // Spigot - this.getHandle().transferCookieConnection.kickPlayer(CraftChatMessage.fromStringOrEmpty(message, true), org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause + this.getHandle().connection.disconnect(CraftChatMessage.fromStringOrEmpty(message, true), org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause } private static final net.kyori.adventure.text.Component DEFAULT_KICK_COMPONENT = net.kyori.adventure.text.Component.translatable("multiplayer.disconnect.kicked"); @@ -3389,7 +3339,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { // Paper start - brand support @Override public String getClientBrandName() { - return getHandle().clientBrandName; + return getHandle().connection.playerBrand; } // Paper end @@ -3569,4 +3519,9 @@ public class CraftPlayer extends CraftHumanEntity implements Player { public void setDeathScreenScore(final int score) { getHandle().setScore(score); } + + @Override + public PlayerGameConnection getConnection() { + return this.getHandle().connection.playerGameConnection; + } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index c5daefa565..1d11251009 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -4,6 +4,7 @@ import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; +import com.mojang.authlib.GameProfile; import com.mojang.datafixers.util.Either; import java.util.ArrayList; import java.util.Collections; @@ -13,12 +14,20 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.connection.HorriblePlayerLoginEventHack; +import io.papermc.paper.connection.PlayerConnection; +import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundContainerClosePacket; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.players.PlayerList; import net.minecraft.tags.DamageTypeTags; import net.minecraft.util.Unit; import net.minecraft.world.Container; @@ -225,6 +234,7 @@ import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.event.player.PlayerItemBreakEvent; import org.bukkit.event.player.PlayerItemMendEvent; import org.bukkit.event.player.PlayerLevelChangeEvent; +import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerRecipeBookClickEvent; import org.bukkit.event.player.PlayerRecipeBookSettingsChangeEvent; import org.bukkit.event.player.PlayerRecipeDiscoverEvent; @@ -2085,4 +2095,27 @@ public class CraftEventFactory { return event; } + + @SuppressWarnings("OptionalAssignedToNull") + public static Component handleLoginResult(PlayerList.LoginResult result, PlayerConnection paperConnection, Connection connection, GameProfile profile, MinecraftServer server, boolean loginPhase) { + PlayerConnectionValidateLoginEvent event = new PlayerConnectionValidateLoginEvent( + paperConnection, result.isAllowed() ? null : PaperAdventure.asAdventure(result.message()) + ); + event.callEvent(); + + Component disconnectReason = PaperAdventure.asVanilla(event.getKickMessage()); + + // For the login event it normally was never fired during configuration phase. In order to make this deprecation less + // breaky we will cache result and use it next time. + if (loginPhase) { + disconnectReason = HorriblePlayerLoginEventHack.execute(connection, server, profile, + disconnectReason == null ? PlayerList.LoginResult.ALLOW : new PlayerList.LoginResult(disconnectReason, disconnectReason == null ? PlayerLoginEvent.Result.KICK_OTHER : result.result()) + ); + } else if (connection.legacySavedLoginEventResultOverride != null) { + // If the override is set, use it. + disconnectReason = connection.legacySavedLoginEventResultOverride.orElse(null); + } + + return disconnectReason; + } }