Configuration API (#12301)

This implements a solution that preemptively exits the tick loop if we have already marked the connection as disconnecting. This avoids changing the result of Connection#isConnected in order to avoid other possibly unintentional changes. Fundamentally it should be investigated if closing the connection async is really still needed.

This also additionally removes the login disconnecting logic for server stopping, as this was also a possible issue in the config stage but also shouldn't be an issue as connections are closed on server stop very early.

Additionally, do not check for isConnecting() on VERIFYING, as that seemed to be an old diff originally trying to resolve this code, however isConnected is not updated at this point so it pretty much was useless.
This commit is contained in:
Owen
2025-06-26 15:55:03 -04:00
committed by Nassim Jahnke
parent dda39a0f05
commit 7f60924390
48 changed files with 1808 additions and 639 deletions

View File

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

View File

@@ -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.
* <p>
* 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<String, String> 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> T getClientOption(ClientOption<T> type);
}

View File

@@ -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.
* <p>
* Note, this should be only be called if you are reconfiguring the player.
*/
void completeReconfiguration();
}

View File

@@ -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.
* <p>
* 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.
* <p>
* 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 players IP address extracted from the proxy handshake.
*
* @return the client {@link InetSocketAddress}
*/
InetSocketAddress getClientAddress();
/**
* Returns the virtual host the client is connected to.
*
* <p>The virtual host refers to the hostname/port the client used to
* connect to the server.</p>
*
* @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();
}

View File

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

View File

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

View File

@@ -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<byte[]> retrieveCookie(NamespacedKey key);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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() {

View File

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

View File

@@ -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 <i>early</i> in the player initialization
* process. It is recommended that most options involving the Player
* <i>entity</i> 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();

View File

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

View File

@@ -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<Packet<?>> {
@@ -40,10 +40,10 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141
public Channel channel;
public SocketAddress address;
// Spigot start
@@ -145,6 +145,10 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
// Paper end - packet limiter
@@ -146,6 +146,10 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@Nullable public SocketAddress haProxyAddress; // Paper - Add API to get player's proxy address
public @Nullable java.util.Optional<net.minecraft.network.chat.Component> 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<Packet<?>> {
@@ -420,11 +424,38 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
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<Packet<?>> {
@@ -433,7 +464,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
this.flushQueue();
action.accept(this);
} else {
@@ -102,7 +102,7 @@ index 2252c7c3e78c1d6dc9b56c0f6f01aec04699f072..235a076f982247552fef6d86d7e1b141
}
}
@@ -446,21 +477,41 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -447,21 +478,41 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
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<Packet<?>> {
@@ -473,16 +524,57 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
}
@@ -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<Packet<?>> {
@@ -552,6 +644,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
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<Packet<?>> {
@@ -740,7 +833,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
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<Packet<?>> {
@@ -751,7 +844,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
);
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<Packet<?>> {
@@ -786,4 +879,97 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
public void setBandwidthLogger(LocalSampleLogger bandwithLogger) {
this.bandwidthDebugMonitor = new BandwidthDebugMonitor(bandwithLogger);
}

View File

@@ -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<Packet<?>> {
@@ -763,11 +763,22 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
return connection;
}
@@ -299,7 +299,7 @@ index 235a076f982247552fef6d86d7e1b141eb8c72b5..34524dc5a503bebcec99ada0d9560d6f
public boolean isEncrypted() {
return this.encrypted;
@@ -805,16 +816,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
@@ -806,16 +817,17 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
// 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);

View File

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

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

@@ -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<net.minecraft.server.level.ServerPlayer.PendingKeepAlive> itr = this.player.pendingKeepAlives.iterator(); itr.hasNext();) {
+ net.minecraft.server.level.ServerPlayer.PendingKeepAlive ka = itr.next();
+ for (java.util.Iterator<io.papermc.paper.util.KeepAlive.PendingKeepAlive> 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

View File

@@ -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<net.minecraft.network.chat.Component> legacySavedLoginEventResultOverride; // Paper - playerloginevent
+ public @Nullable net.minecraft.server.level.ServerPlayer savedPlayerForLoginEventLegacy; // Paper - playerloginevent
public Connection(PacketFlow receiving) {
this.receiving = receiving;

View File

@@ -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<Path> report, Optional<URI> bugReportLink) {
+// Paper start - Configuration API: add support for enhanced disconnection causes
+public record DisconnectionDetails(Component reason, Optional<Path> report, Optional<URI> bugReportLink, Optional<Component> quitMessage, Optional<io.papermc.paper.connection.DisconnectionReason> disconnectionReason) {
+ public DisconnectionDetails(Component reason, Optional<Path> report, Optional<URI> 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());
}

View File

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

View File

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

View File

@@ -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<java.util.UUID, net.kyori.adventure.resource.ResourcePackCallback> 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,9 +165,19 @@
+ 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);
@@ -315,58 +188,63 @@
+ 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!", this.player.getName().getString());
+ case FLYING_VEHICLE -> LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString());
+ 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
}
}

View File

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

View File

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

View File

@@ -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,41 +138,14 @@
}
}
}
@@ -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
} 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;
+ 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 {
@@ -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;

View File

@@ -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,41 +474,19 @@
+ 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<ServerPlayer> 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);
+ }
+ }
+
+ 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
+ // 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;
+ }
+
+ // 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) {
@@ -510,98 +494,56 @@
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<ServerPlayer> 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<ServerPlayer> 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<ServerPlayer> 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 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 false;
+
+ return net.minecraft.server.players.PlayerList.LoginResult.ALLOW;
+ }
+ return true;
+ // Paper end - ProfileWhitelistVerifyEvent
+ // 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

View File

@@ -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<PlayerKickEvent.Cause> game();
static DisconnectionReason game(PlayerKickEvent.Cause cause) {
return new GameEntry(cause);
}
record GameEntry(PlayerKickEvent.Cause gameQuitEvent) implements DisconnectionReason {
@Override
public Optional<PlayerKickEvent.Cause> game() {
return Optional.ofNullable(this.gameQuitEvent);
}
}
}

View File

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

View File

@@ -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<T extends ServerCommonPacketListenerImpl> 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<String, String> 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> T getClientOption(ClientOption<T> 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();
}

View File

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

View File

@@ -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<ServerConfigurationPacketListenerImpl> 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<ClientboundResourcePackPushPacket> 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<ResourcePackInfo> 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();
}
}

View File

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

View File

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

View File

@@ -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<ResourceLocation, CookieFuture> requestedCookies = new ConcurrentHashMap<>();
private final Connection connection;
public ReadablePlayerCookieConnectionImpl(final Connection connection) {
this.connection = connection;
}
@Override
public CompletableFuture<byte[]> retrieveCookie(final NamespacedKey key) {
Preconditions.checkArgument(key != null, "Cookie key cannot be null");
CompletableFuture<byte[]> 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<byte[]> future) {
}
}

View File

@@ -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<byte @Nullable []> future) {
}
private final Queue<CookieFuture> 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<byte @Nullable []> retrieveCookie(final NamespacedKey key) {
Preconditions.checkArgument(key != null, "Cookie key cannot be null");
CompletableFuture<byte @Nullable []> 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;
}
}

View File

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