From 5336a6b6bb3601a7996550a62bd9cbc45d381a30 Mon Sep 17 00:00:00 2001 From: Minecrell Date: Fri, 23 Mar 2018 04:19:59 +0100 Subject: [PATCH] Add extended PaperServerListPingEvent (#980) * Drop original implementation for old player sample API * Add extended PaperServerListPingEvent Add a new event that extends the original ServerListPingEvent and allows full control of the response sent to the client. * Implement deprecated player sample API --- ...dd-extended-PaperServerListPingEvent.patch | 368 ++++++++++++++++++ ...plement-deprecated-player-sample-API.patch | 76 ++++ .../Add-PlayerJumpEvent.patch | 2 +- ...f-Player-Sample-in-ServerListPingEve.patch | 66 ---- .../AsyncTabCompleteEvent.patch | 4 +- ...le-Explicit-Network-Manager-Flushing.patch | 2 +- ...nt-extended-PaperServerListPingEvent.patch | 253 ++++++++++++ Spigot-Server-Patches/MC-Dev-fixes.patch | 118 +++++- .../ProfileWhitelistVerifyEvent.patch | 2 +- ...r-crits-helps-mitigate-hacked-client.patch | 4 +- .../handle-PacketPlayInKeepAlive-async.patch | 2 +- ...rt-serverside-behavior-of-keepalives.patch | 2 +- scripts/importmcdev.sh | 1 + 13 files changed, 819 insertions(+), 81 deletions(-) create mode 100644 Spigot-API-Patches/Add-extended-PaperServerListPingEvent.patch create mode 100644 Spigot-API-Patches/Implement-deprecated-player-sample-API.patch delete mode 100644 Spigot-Server-Patches/Allow-Changing-of-Player-Sample-in-ServerListPingEve.patch create mode 100644 Spigot-Server-Patches/Implement-extended-PaperServerListPingEvent.patch diff --git a/Spigot-API-Patches/Add-extended-PaperServerListPingEvent.patch b/Spigot-API-Patches/Add-extended-PaperServerListPingEvent.patch new file mode 100644 index 0000000000..0f93504d9c --- /dev/null +++ b/Spigot-API-Patches/Add-extended-PaperServerListPingEvent.patch @@ -0,0 +1,368 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 15:55:38 +0200 +Subject: [PATCH] Add extended PaperServerListPingEvent + +Add a new event that extends the original ServerListPingEvent +and allows full control of the response sent to the client. + +diff --git a/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java b/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java +new file mode 100644 +index 00000000..dd1deafd +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper.event.server; ++ ++import static java.util.Objects.requireNonNull; ++ ++import com.destroystokyo.paper.network.StatusClient; ++import com.destroystokyo.paper.profile.PlayerProfile; ++import org.bukkit.Bukkit; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.server.ServerListPingEvent; ++import org.bukkit.util.CachedServerIcon; ++ ++import java.util.ArrayList; ++import java.util.Iterator; ++import java.util.List; ++import java.util.NoSuchElementException; ++import java.util.UUID; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++ ++/** ++ * Extended version of {@link ServerListPingEvent} that allows full control ++ * of the response sent to the client. ++ */ ++public class PaperServerListPingEvent extends ServerListPingEvent implements Cancellable { ++ ++ @Nonnull private final StatusClient client; ++ ++ private int numPlayers; ++ private boolean hidePlayers; ++ @Nonnull private final List playerSample = new ArrayList<>(); ++ ++ @Nonnull private String version; ++ private int protocolVersion; ++ ++ @Nullable private CachedServerIcon favicon; ++ ++ private boolean cancelled; ++ ++ private boolean originalPlayerCount = true; ++ private Object[] players; ++ ++ public PaperServerListPingEvent(@Nonnull StatusClient client, String motd, int numPlayers, int maxPlayers, ++ @Nonnull String version, int protocolVersion, @Nullable CachedServerIcon favicon) { ++ super(client.getAddress().getAddress(), motd, numPlayers, maxPlayers); ++ this.client = client; ++ this.numPlayers = numPlayers; ++ this.version = version; ++ this.protocolVersion = protocolVersion; ++ setServerIcon(favicon); ++ } ++ ++ /** ++ * Returns the {@link StatusClient} pinging the server. ++ * ++ * @return The client ++ */ ++ @Nonnull ++ public StatusClient getClient() { ++ return this.client; ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ *

Returns {@code -1} if players are hidden using ++ * {@link #shouldHidePlayers()}.

++ */ ++ @Override ++ public int getNumPlayers() { ++ if (this.hidePlayers) { ++ return -1; ++ } ++ ++ return this.numPlayers; ++ } ++ ++ /** ++ * Sets the number of players displayed in the server list. ++ * ++ *

Note that this won't have any effect if {@link #shouldHidePlayers()} ++ * is enabled.

++ * ++ * @param numPlayers The number of online players ++ */ ++ public void setNumPlayers(int numPlayers) { ++ if (this.numPlayers != numPlayers) { ++ this.numPlayers = numPlayers; ++ this.originalPlayerCount = false; ++ } ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ *

Returns {@code -1} if players are hidden using ++ * {@link #shouldHidePlayers()}.

++ */ ++ @Override ++ public int getMaxPlayers() { ++ if (this.hidePlayers) { ++ return -1; ++ } ++ ++ return super.getMaxPlayers(); ++ } ++ ++ /** ++ * Returns whether all player related information is hidden in the server ++ * list. This will cause {@link #getNumPlayers()}, {@link #getMaxPlayers()} ++ * and {@link #getPlayerSample()} to be skipped in the response. ++ * ++ *

The Vanilla Minecraft client will display the player count as {@code ???} ++ * when this option is enabled.

++ * ++ * @return {@code true} if the player count is hidden ++ */ ++ public boolean shouldHidePlayers() { ++ return hidePlayers; ++ } ++ ++ /** ++ * Sets whether all player related information is hidden in the server ++ * list. This will cause {@link #getNumPlayers()}, {@link #getMaxPlayers()} ++ * and {@link #getPlayerSample()} to be skipped in the response. ++ * ++ *

The Vanilla Minecraft client will display the player count as {@code ???} ++ * when this option is enabled.

++ * ++ * @param hidePlayers {@code true} if the player count should be hidden ++ */ ++ public void setHidePlayers(boolean hidePlayers) { ++ this.hidePlayers = hidePlayers; ++ } ++ ++ /** ++ * Returns a mutable list of {@link PlayerProfile} that will be displayed ++ * as online players on the client. ++ * ++ *

The Vanilla Minecraft client will display them when hovering the ++ * player count with the mouse.

++ * ++ * @return The mutable player sample list ++ */ ++ @Nonnull ++ public List getPlayerSample() { ++ return this.playerSample; ++ } ++ ++ /** ++ * Returns the version that will be sent as server version on the client. ++ * ++ * @return The server version ++ */ ++ @Nonnull ++ public String getVersion() { ++ return version; ++ } ++ ++ /** ++ * Sets the version that will be sent as server version to the client. ++ * ++ * @param version The server version ++ */ ++ public void setVersion(@Nonnull String version) { ++ this.version = requireNonNull(version, "version"); ++ } ++ ++ /** ++ * Returns the protocol version that will be sent as the protocol version ++ * of the server to the client. ++ * ++ * @return The protocol version of the server ++ */ ++ public int getProtocolVersion() { ++ return protocolVersion; ++ } ++ ++ /** ++ * Sets the protocol version that will be sent as the protocol version ++ * of the server to the client. ++ * ++ * @param protocolVersion The protocol version of the server ++ */ ++ public void setProtocolVersion(int protocolVersion) { ++ this.protocolVersion = protocolVersion; ++ } ++ ++ /** ++ * Gets the server icon sent to the client. ++ * ++ * @return The icon to send to the client, or {@code null} for none ++ */ ++ @Nullable ++ public CachedServerIcon getServerIcon() { ++ return this.favicon; ++ } ++ ++ /** ++ * Sets the server icon sent to the client. ++ * ++ * @param icon The icon to send to the client, or {@code null} for none ++ */ ++ @Override ++ public void setServerIcon(@Nullable CachedServerIcon icon) { ++ if (icon != null && icon.isEmpty()) { ++ // Represent empty icons as null ++ icon = null; ++ } ++ ++ this.favicon = icon; ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ *

Cancelling this event will cause the connection to be closed immediately, ++ * without sending a response to the client.

++ */ ++ @Override ++ public boolean isCancelled() { ++ return this.cancelled; ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ *

Cancelling this event will cause the connection to be closed immediately, ++ * without sending a response to the client.

++ */ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.cancelled = cancel; ++ } ++ ++ /** ++ * {@inheritDoc} ++ * ++ *

Note: For compatibility reasons, this method will return all ++ * online players, not just the ones referenced in {@link #getPlayerSample()}. ++ * Removing a player will:

++ * ++ *
    ++ *
  • Decrement the online player count (if and only if) the player ++ * count wasn't changed by another plugin before.
  • ++ *
  • Remove all entries from {@link #getPlayerSample()} that refer to ++ * the removed player (based on their {@link UUID}).
  • ++ *
++ */ ++ @Nonnull ++ @Override ++ public Iterator iterator() { ++ if (this.players == null) { ++ this.players = getOnlinePlayers(); ++ } ++ ++ return new PlayerIterator(); ++ } ++ ++ protected Object[] getOnlinePlayers() { ++ return Bukkit.getOnlinePlayers().toArray(); ++ } ++ ++ protected Player getBukkitPlayer(Object player) { ++ return (Player) player; ++ } ++ ++ private final class PlayerIterator implements Iterator { ++ ++ private int next; ++ private int current; ++ @Nullable private Player player; ++ ++ @Override ++ public boolean hasNext() { ++ for (; this.next < players.length; this.next++) { ++ if (players[this.next] != null) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public Player next() { ++ if (!hasNext()) { ++ this.player = null; ++ throw new NoSuchElementException(); ++ } ++ ++ this.current = this.next++; ++ return this.player = getBukkitPlayer(players[this.current]); ++ } ++ ++ @Override ++ public void remove() { ++ if (this.player == null) { ++ throw new IllegalStateException(); ++ } ++ ++ UUID uniqueId = this.player.getUniqueId(); ++ this.player = null; ++ ++ // Remove player from iterator ++ players[this.current] = null; ++ ++ // Remove player from sample ++ getPlayerSample().removeIf(p -> uniqueId.equals(p.getId())); ++ ++ // Decrement player count ++ if (originalPlayerCount) { ++ numPlayers--; ++ } ++ } ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/network/StatusClient.java b/src/main/java/com/destroystokyo/paper/network/StatusClient.java +new file mode 100644 +index 00000000..517d1523 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/StatusClient.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++ ++/** ++ * Represents a client requesting the current status from the server (e.g. from ++ * the server list). ++ * ++ * @see PaperServerListPingEvent ++ */ ++public interface StatusClient extends NetworkClient { ++ ++} +diff --git a/src/main/java/org/bukkit/util/CachedServerIcon.java b/src/main/java/org/bukkit/util/CachedServerIcon.java +index 04804706..44563482 100644 +--- a/src/main/java/org/bukkit/util/CachedServerIcon.java ++++ b/src/main/java/org/bukkit/util/CachedServerIcon.java +@@ -0,0 +0,0 @@ import org.bukkit.event.server.ServerListPingEvent; + */ + public interface CachedServerIcon { + public String getData(); // Spigot ++ ++ // Paper start ++ default boolean isEmpty() { ++ return getData() == null; ++ } ++ // Paper end + } +-- \ No newline at end of file diff --git a/Spigot-API-Patches/Implement-deprecated-player-sample-API.patch b/Spigot-API-Patches/Implement-deprecated-player-sample-API.patch new file mode 100644 index 0000000000..55a3cbd68a --- /dev/null +++ b/Spigot-API-Patches/Implement-deprecated-player-sample-API.patch @@ -0,0 +1,76 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Mon, 27 Nov 2017 16:21:19 +0100 +Subject: [PATCH] Implement deprecated player sample API + + +diff --git a/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java b/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java +index a6576f99..41899698 100644 +--- a/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java ++++ b/src/main/java/com/destroystokyo/paper/event/server/PaperServerListPingEvent.java +@@ -0,0 +0,0 @@ import static java.util.Objects.requireNonNull; + + import com.destroystokyo.paper.network.StatusClient; + import com.destroystokyo.paper.profile.PlayerProfile; ++import com.google.common.base.Strings; ++import com.google.common.collect.ImmutableList; + import org.bukkit.Bukkit; + import org.bukkit.entity.Player; + import org.bukkit.event.Cancellable; +@@ -0,0 +0,0 @@ public class PaperServerListPingEvent extends ServerListPingEvent implements Can + } + } + ++ // TODO: Remove in 1.13 ++ ++ @Override ++ @Deprecated ++ public List getSampleText() { ++ ImmutableList.Builder sampleText = ImmutableList.builder(); ++ for (PlayerProfile profile : getPlayerSample()) { ++ sampleText.add(Strings.nullToEmpty(profile.getName())); ++ } ++ return sampleText.build(); ++ } ++ ++ @Override ++ @Deprecated ++ public void setSampleText(List sample) { ++ getPlayerSample().clear(); ++ for (String name : sample) { ++ getPlayerSample().add(Bukkit.createProfile(name)); ++ } ++ } ++ + } +diff --git a/src/main/java/org/bukkit/event/server/ServerListPingEvent.java b/src/main/java/org/bukkit/event/server/ServerListPingEvent.java +index cb8d0fcd..116d7c7a 100644 +--- a/src/main/java/org/bukkit/event/server/ServerListPingEvent.java ++++ b/src/main/java/org/bukkit/event/server/ServerListPingEvent.java +@@ -0,0 +0,0 @@ import java.net.InetAddress; + import java.util.Iterator; + import java.util.List; + ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; + import org.apache.commons.lang.Validate; + import org.bukkit.entity.Player; + import org.bukkit.event.HandlerList; +@@ -0,0 +0,0 @@ public class ServerListPingEvent extends ServerEvent implements Iterable + private java.util.List sample; + + /** +- * @deprecated Will be replaced in 1.13 ++ * @deprecated Will be removed in 1.13, use {@link PaperServerListPingEvent#getPlayerSample()} + */ + @Deprecated + public void setSampleText(java.util.List sample) { +@@ -0,0 +0,0 @@ public class ServerListPingEvent extends ServerEvent implements Iterable + } + + /** +- * @deprecated Will be replaced in 1.13 ++ * @deprecated Will be removed in 1.13, use {@link PaperServerListPingEvent#getPlayerSample()} + */ + @Deprecated + public java.util.List getSampleText() { +-- \ No newline at end of file diff --git a/Spigot-Server-Patches/Add-PlayerJumpEvent.patch b/Spigot-Server-Patches/Add-PlayerJumpEvent.patch index 57a48bebca..baa6f7838c 100644 --- a/Spigot-Server-Patches/Add-PlayerJumpEvent.patch +++ b/Spigot-Server-Patches/Add-PlayerJumpEvent.patch @@ -17,7 +17,7 @@ index deb0f4a9c..579996d1e 100644 super.cu(); this.b(StatisticList.w); diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java -index 9c8828ebd..cc58a4a93 100644 +index 3104fc0ea..aa57ff8ed 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -0,0 +0,0 @@ import org.bukkit.inventory.EquipmentSlot; diff --git a/Spigot-Server-Patches/Allow-Changing-of-Player-Sample-in-ServerListPingEve.patch b/Spigot-Server-Patches/Allow-Changing-of-Player-Sample-in-ServerListPingEve.patch deleted file mode 100644 index ce9f168944..0000000000 --- a/Spigot-Server-Patches/Allow-Changing-of-Player-Sample-in-ServerListPingEve.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: willies952002 -Date: Fri, 5 May 2017 18:59:22 -0400 -Subject: [PATCH] Allow Changing of Player Sample in ServerListPingEvent - - -diff --git a/src/main/java/net/minecraft/server/PacketStatusListener.java b/src/main/java/net/minecraft/server/PacketStatusListener.java -index 313bb0007..45d6984f7 100644 ---- a/src/main/java/net/minecraft/server/PacketStatusListener.java -+++ b/src/main/java/net/minecraft/server/PacketStatusListener.java -@@ -0,0 +0,0 @@ import com.mojang.authlib.GameProfile; - import io.netty.channel.ChannelFutureListener; - import java.net.InetSocketAddress; - import java.util.Iterator; -+import java.util.UUID; - - import org.bukkit.craftbukkit.util.CraftIconCache; - import org.bukkit.entity.Player; -@@ -0,0 +0,0 @@ public class PacketStatusListener implements PacketStatusInListener { - } - } - -+ // Paper start -+ java.util.List sample = new java.util.ArrayList<>(players.length); -+ for (Object player : players) { -+ if (player != null) sample.add(((EntityPlayer) player).getName()); -+ } -+ if (!sample.isEmpty()) { -+ java.util.Collections.shuffle(sample); -+ sample = sample.subList(0, Math.min(sample.size(), org.spigotmc.SpigotConfig.playerSample)); -+ } -+ // Paper end -+ - ServerListPingEvent event = new ServerListPingEvent(); -+ event.setSampleText(sample); // Paper - this.minecraftServer.server.getPluginManager().callEvent(event); -- - java.util.List profiles = new java.util.ArrayList(players.length); -+ // Paper start -+ if (event.getSampleText() != sample) sample = event.getSampleText(); -+ sample.forEach(line -> profiles.add(new GameProfile(UUID.randomUUID(), line))); -+ /* - for (Object player : players) { - if (player != null) { - profiles.add(((EntityPlayer) player).getProfile()); - } - } -+ */ -+ // Paper end - -- ServerPing.ServerPingPlayerSample playerSample = new ServerPing.ServerPingPlayerSample(event.getMaxPlayers(), profiles.size()); -+ ServerPing.ServerPingPlayerSample playerSample = new ServerPing.ServerPingPlayerSample(event.getMaxPlayers(), minecraftServer.getPlayerList().getPlayerCount()); // Paper - // Spigot Start -+ // Paper start - Move up -+ /* - if ( !profiles.isEmpty() ) - { - java.util.Collections.shuffle( profiles ); // This sucks, its inefficient but we have no simple way of doing it differently - profiles = profiles.subList( 0, Math.min( profiles.size(), org.spigotmc.SpigotConfig.playerSample ) ); // Cap the sample to n (or less) displayed players, ie: Vanilla behaviour - } -+ */ -+ // Paper end - // Spigot End - playerSample.a(profiles.toArray(new GameProfile[profiles.size()])); - --- \ No newline at end of file diff --git a/Spigot-Server-Patches/AsyncTabCompleteEvent.patch b/Spigot-Server-Patches/AsyncTabCompleteEvent.patch index 25b78c107a..82d5a350cd 100644 --- a/Spigot-Server-Patches/AsyncTabCompleteEvent.patch +++ b/Spigot-Server-Patches/AsyncTabCompleteEvent.patch @@ -14,7 +14,7 @@ completion, such as offline players. Also adds isCommand and getLocation to the sync TabCompleteEvent diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java -index d0ab87d0f..ca054afcf 100644 +index 167e386c8..3fbf51e52 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn, ITickable { @@ -80,7 +80,7 @@ index d0ab87d0f..ca054afcf 100644 public void a(PacketPlayInSettings packetplayinsettings) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index f0ae65f08..f01408d55 100644 +index aca5ea7c0..090a4d2f2 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -0,0 +0,0 @@ public final class CraftServer implements Server { diff --git a/Spigot-Server-Patches/Disable-Explicit-Network-Manager-Flushing.patch b/Spigot-Server-Patches/Disable-Explicit-Network-Manager-Flushing.patch index e6492591ef..66a592d83c 100644 --- a/Spigot-Server-Patches/Disable-Explicit-Network-Manager-Flushing.patch +++ b/Spigot-Server-Patches/Disable-Explicit-Network-Manager-Flushing.patch @@ -12,7 +12,7 @@ flushing on the netty event loop, so it won't do the flush on the main thread. Renable flushing by passing -Dpaper.explicit-flush=true diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java -index b93a26e8f..da7c45697 100644 +index b93a26e8f..3d32e0056 100644 --- a/src/main/java/net/minecraft/server/NetworkManager.java +++ b/src/main/java/net/minecraft/server/NetworkManager.java @@ -0,0 +0,0 @@ public class NetworkManager extends SimpleChannelInboundHandler> { diff --git a/Spigot-Server-Patches/Implement-extended-PaperServerListPingEvent.patch b/Spigot-Server-Patches/Implement-extended-PaperServerListPingEvent.patch new file mode 100644 index 0000000000..e3dc99026d --- /dev/null +++ b/Spigot-Server-Patches/Implement-extended-PaperServerListPingEvent.patch @@ -0,0 +1,253 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 15:56:26 +0200 +Subject: [PATCH] Implement extended PaperServerListPingEvent + + +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java +new file mode 100644 +index 000000000..c1a8e295b +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++import net.minecraft.server.EntityPlayer; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.entity.Player; ++import org.bukkit.util.CachedServerIcon; ++ ++import javax.annotation.Nullable; ++ ++class PaperServerListPingEventImpl extends PaperServerListPingEvent { ++ ++ private final MinecraftServer server; ++ ++ PaperServerListPingEventImpl(MinecraftServer server, StatusClient client, int protocolVersion, @Nullable CachedServerIcon icon) { ++ super(client, server.getMotd(), server.getPlayerCount(), server.getMaxPlayers(), ++ server.getServerModName() + ' ' + server.getVersion(), protocolVersion, icon); ++ this.server = server; ++ } ++ ++ @Override ++ protected final Object[] getOnlinePlayers() { ++ return this.server.getPlayerList().players.toArray(); ++ } ++ ++ @Override ++ protected final Player getBukkitPlayer(Object player) { ++ return ((EntityPlayer) player).getBukkitEntity(); ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java +new file mode 100644 +index 000000000..a2a409e63 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper.network; ++ ++import net.minecraft.server.NetworkManager; ++ ++class PaperStatusClient extends PaperNetworkClient implements StatusClient { ++ ++ PaperStatusClient(NetworkManager networkManager) { ++ super(networkManager); ++ } ++ ++} +diff --git a/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java +new file mode 100644 +index 000000000..33dd196fb +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.profile.CraftPlayerProfile; ++import com.destroystokyo.paper.profile.PlayerProfile; ++import com.google.common.base.MoreObjects; ++import com.google.common.base.Strings; ++import com.mojang.authlib.GameProfile; ++import net.minecraft.server.ChatComponentText; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.NetworkManager; ++import net.minecraft.server.PacketStatusOutServerInfo; ++import net.minecraft.server.ServerPing; ++ ++import java.util.List; ++import java.util.UUID; ++ ++import javax.annotation.Nonnull; ++ ++public final class StandardPaperServerListPingEventImpl extends PaperServerListPingEventImpl { ++ ++ private static final GameProfile[] EMPTY_PROFILES = new GameProfile[0]; ++ private static final UUID FAKE_UUID = new UUID(0, 0); ++ ++ private GameProfile[] originalSample; ++ ++ private StandardPaperServerListPingEventImpl(MinecraftServer server, NetworkManager networkManager, ServerPing ping) { ++ super(server, new PaperStatusClient(networkManager), ping.getServerData().getProtocolVersion(), server.server.getServerIcon()); ++ this.originalSample = ping.getPlayers().getSample(); ++ } ++ ++ @Nonnull ++ @Override ++ public List getPlayerSample() { ++ List sample = super.getPlayerSample(); ++ ++ if (this.originalSample != null) { ++ for (GameProfile profile : this.originalSample) { ++ sample.add(CraftPlayerProfile.asBukkitMirror(profile)); ++ } ++ this.originalSample = null; ++ } ++ ++ return sample; ++ } ++ ++ private GameProfile[] getPlayerSampleHandle() { ++ if (this.originalSample != null) { ++ return this.originalSample; ++ } ++ ++ List entries = super.getPlayerSample(); ++ if (entries.isEmpty()) { ++ return EMPTY_PROFILES; ++ } ++ ++ GameProfile[] profiles = new GameProfile[entries.size()]; ++ for (int i = 0; i < profiles.length; i++) { ++ /* ++ * Avoid null UUIDs/names since that will make the response invalid ++ * on the client. ++ * Instead, fall back to a fake/empty UUID and an empty string as name. ++ * This can be used to create custom lines in the player list that do not ++ * refer to a specific player. ++ */ ++ ++ PlayerProfile profile = entries.get(i); ++ if (profile.getId() != null && profile.getName() != null) { ++ profiles[i] = CraftPlayerProfile.asAuthlib(profile); ++ } else { ++ profiles[i] = new GameProfile(MoreObjects.firstNonNull(profile.getId(), FAKE_UUID), Strings.nullToEmpty(profile.getName())); ++ } ++ } ++ ++ return profiles; ++ } ++ ++ @SuppressWarnings("deprecation") ++ public static void processRequest(MinecraftServer server, NetworkManager networkManager) { ++ StandardPaperServerListPingEventImpl event = new StandardPaperServerListPingEventImpl(server, networkManager, server.getServerPing()); ++ server.server.getPluginManager().callEvent(event); ++ ++ // Close connection immediately if event is cancelled ++ if (event.isCancelled()) { ++ networkManager.close(null); ++ return; ++ } ++ ++ // Setup response ++ ServerPing ping = new ServerPing(); ++ ++ // Description ++ ping.setMOTD(new ChatComponentText(event.getMotd())); ++ ++ // Players ++ if (!event.shouldHidePlayers()) { ++ ping.setPlayerSample(new ServerPing.ServerPingPlayerSample(event.getMaxPlayers(), event.getNumPlayers())); ++ ping.getPlayers().setSample(event.getPlayerSampleHandle()); ++ } ++ ++ // Version ++ ping.setServerInfo(new ServerPing.ServerData(event.getVersion(), event.getProtocolVersion())); ++ ++ // Favicon ++ if (event.getServerIcon() != null) { ++ ping.setFavicon(event.getServerIcon().getData()); ++ } ++ ++ // Send response ++ networkManager.sendPacket(new PacketStatusOutServerInfo(ping)); ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index e8c72db96..9da09d53b 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -0,0 +0,0 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs + if (i - this.Z >= 5000000000L) { + this.Z = i; + this.q.setPlayerSample(new ServerPing.ServerPingPlayerSample(this.I(), this.H())); +- GameProfile[] agameprofile = new GameProfile[Math.min(this.H(), 12)]; ++ GameProfile[] agameprofile = new GameProfile[Math.min(this.H(), org.spigotmc.SpigotConfig.playerSample)]; // Paper + int j = MathHelper.nextInt(this.r, 0, this.H() - agameprofile.length); + + for (int k = 0; k < agameprofile.length; ++k) { +@@ -0,0 +0,0 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IAs + return "1.12.2"; + } + ++ public int getPlayerCount() { return H(); } // Paper - OBFHELPER + public int H() { + return this.v.getPlayerCount(); + } + ++ public int getMaxPlayers() { return I(); } // Paper - OBFHELPER + public int I() { + return this.v.getMaxPlayers(); + } +diff --git a/src/main/java/net/minecraft/server/PacketStatusListener.java b/src/main/java/net/minecraft/server/PacketStatusListener.java +index 313bb0007..f3c25367d 100644 +--- a/src/main/java/net/minecraft/server/PacketStatusListener.java ++++ b/src/main/java/net/minecraft/server/PacketStatusListener.java +@@ -0,0 +0,0 @@ public class PacketStatusListener implements PacketStatusInListener { + this.networkManager.close(PacketStatusListener.a); + } else { + this.d = true; ++ // Paper start - Replace everything ++ /* + // CraftBukkit start + // this.networkManager.sendPacket(new PacketStatusOutServerInfo(this.minecraftServer.getServerPing())); + final Object[] players = minecraftServer.getPlayerList().players.toArray(); +@@ -0,0 +0,0 @@ public class PacketStatusListener implements PacketStatusInListener { + ping.setServerInfo(new ServerPing.ServerData(minecraftServer.getServerModName() + " " + minecraftServer.getVersion(), version)); + + this.networkManager.sendPacket(new PacketStatusOutServerInfo(ping)); ++ */ ++ com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(this.minecraftServer, this.networkManager); ++ // Paper end + } + // CraftBukkit end + } +diff --git a/src/main/java/net/minecraft/server/ServerPing.java b/src/main/java/net/minecraft/server/ServerPing.java +index 981582212..ac161f505 100644 +--- a/src/main/java/net/minecraft/server/ServerPing.java ++++ b/src/main/java/net/minecraft/server/ServerPing.java +@@ -0,0 +0,0 @@ public class ServerPing { + this.a = ichatbasecomponent; + } + ++ public ServerPingPlayerSample getPlayers() { return b(); } // Paper - OBFHELPER + public ServerPing.ServerPingPlayerSample b() { + return this.b; + } +@@ -0,0 +0,0 @@ public class ServerPing { + return this.b; + } + ++ public GameProfile[] getSample() { return c(); } // Paper - OBFHELPER + public GameProfile[] c() { + return this.c; + } + ++ public void setSample(GameProfile[] sample) { a(sample); } // Paper - OBFHELPER + public void a(GameProfile[] agameprofile) { + this.c = agameprofile; + } +-- \ No newline at end of file diff --git a/Spigot-Server-Patches/MC-Dev-fixes.patch b/Spigot-Server-Patches/MC-Dev-fixes.patch index ae3b6eee3f..fcfa99be42 100644 --- a/Spigot-Server-Patches/MC-Dev-fixes.patch +++ b/Spigot-Server-Patches/MC-Dev-fixes.patch @@ -5,7 +5,7 @@ Subject: [PATCH] MC Dev fixes diff --git a/src/main/java/net/minecraft/server/BaseBlockPosition.java b/src/main/java/net/minecraft/server/BaseBlockPosition.java -index d4f41274..d55e180d 100644 +index d4f412742..d55e180d7 100644 --- a/src/main/java/net/minecraft/server/BaseBlockPosition.java +++ b/src/main/java/net/minecraft/server/BaseBlockPosition.java @@ -0,0 +0,0 @@ public class BaseBlockPosition implements Comparable { @@ -18,7 +18,7 @@ index d4f41274..d55e180d 100644 } } diff --git a/src/main/java/net/minecraft/server/BiomeBase.java b/src/main/java/net/minecraft/server/BiomeBase.java -index 62a9c92f..1b759976 100644 +index 62a9c92f8..1b7599769 100644 --- a/src/main/java/net/minecraft/server/BiomeBase.java +++ b/src/main/java/net/minecraft/server/BiomeBase.java @@ -0,0 +0,0 @@ public abstract class BiomeBase { @@ -31,7 +31,7 @@ index 62a9c92f..1b759976 100644 @Nullable diff --git a/src/main/java/net/minecraft/server/CommandAbstract.java b/src/main/java/net/minecraft/server/CommandAbstract.java -index 76bf04f5..a99d0f87 100644 +index 76bf04f56..a99d0f870 100644 --- a/src/main/java/net/minecraft/server/CommandAbstract.java +++ b/src/main/java/net/minecraft/server/CommandAbstract.java @@ -0,0 +0,0 @@ public abstract class CommandAbstract implements ICommand { @@ -71,7 +71,7 @@ index 76bf04f5..a99d0f87 100644 } diff --git a/src/main/java/net/minecraft/server/EntityTypes.java b/src/main/java/net/minecraft/server/EntityTypes.java -index 77b81a57..ba461ad4 100644 +index 77b81a575..ba461ad48 100644 --- a/src/main/java/net/minecraft/server/EntityTypes.java +++ b/src/main/java/net/minecraft/server/EntityTypes.java @@ -0,0 +0,0 @@ public class EntityTypes { @@ -93,7 +93,7 @@ index 77b81a57..ba461ad4 100644 EntityTypes.g.set(i, s1); diff --git a/src/main/java/net/minecraft/server/RegistryBlockID.java b/src/main/java/net/minecraft/server/RegistryBlockID.java -index 58f47d0d..8860a012 100644 +index 58f47d0de..8860a0129 100644 --- a/src/main/java/net/minecraft/server/RegistryBlockID.java +++ b/src/main/java/net/minecraft/server/RegistryBlockID.java @@ -0,0 +0,0 @@ import java.util.Iterator; @@ -114,8 +114,114 @@ index 58f47d0d..8860a012 100644 } this.b.set(i, t0); +diff --git a/src/main/java/net/minecraft/server/ServerPing.java b/src/main/java/net/minecraft/server/ServerPing.java +index 2179664a0..981582212 100644 +--- a/src/main/java/net/minecraft/server/ServerPing.java ++++ b/src/main/java/net/minecraft/server/ServerPing.java +@@ -0,0 +0,0 @@ public class ServerPing { + + public Serializer() {} + +- public ServerPing a(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { ++ // Paper - decompile fix ++ public ServerPing deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { + JsonObject jsonobject = ChatDeserializer.m(jsonelement, "status"); + ServerPing serverping = new ServerPing(); + +@@ -0,0 +0,0 @@ public class ServerPing { + return serverping; + } + +- public JsonElement a(ServerPing serverping, Type type, JsonSerializationContext jsonserializationcontext) { ++ // Paper - decompile fix ++ public JsonElement serialize(ServerPing serverping, Type type, JsonSerializationContext jsonserializationcontext) { + JsonObject jsonobject = new JsonObject(); + + if (serverping.a() != null) { +@@ -0,0 +0,0 @@ public class ServerPing { + + return jsonobject; + } +- +- public JsonElement serialize(Object object, Type type, JsonSerializationContext jsonserializationcontext) { +- return this.a((ServerPing) object, type, jsonserializationcontext); +- } +- +- public Object deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { +- return this.a(jsonelement, type, jsondeserializationcontext); +- } + } + + public static class ServerData { +@@ -0,0 +0,0 @@ public class ServerPing { + + public Serializer() {} + +- public ServerPing.ServerData a(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { ++ // Paper - decompile fix ++ public ServerPing.ServerData deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { + JsonObject jsonobject = ChatDeserializer.m(jsonelement, "version"); + + return new ServerPing.ServerData(ChatDeserializer.h(jsonobject, "name"), ChatDeserializer.n(jsonobject, "protocol")); + } + +- public JsonElement a(ServerPing.ServerData serverping_serverdata, Type type, JsonSerializationContext jsonserializationcontext) { ++ // Paper - decompile fix ++ public JsonElement serialize(ServerPing.ServerData serverping_serverdata, Type type, JsonSerializationContext jsonserializationcontext) { + JsonObject jsonobject = new JsonObject(); + + jsonobject.addProperty("name", serverping_serverdata.a()); + jsonobject.addProperty("protocol", Integer.valueOf(serverping_serverdata.getProtocolVersion())); + return jsonobject; + } +- +- public JsonElement serialize(Object object, Type type, JsonSerializationContext jsonserializationcontext) { +- return this.a((ServerPing.ServerData) object, type, jsonserializationcontext); +- } +- +- public Object deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { +- return this.a(jsonelement, type, jsondeserializationcontext); +- } + } + } + +@@ -0,0 +0,0 @@ public class ServerPing { + + public Serializer() {} + +- public ServerPing.ServerPingPlayerSample a(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { ++ // Paper - decompile fix ++ public ServerPing.ServerPingPlayerSample deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { + JsonObject jsonobject = ChatDeserializer.m(jsonelement, "players"); + ServerPing.ServerPingPlayerSample serverping_serverpingplayersample = new ServerPing.ServerPingPlayerSample(ChatDeserializer.n(jsonobject, "max"), ChatDeserializer.n(jsonobject, "online")); + +@@ -0,0 +0,0 @@ public class ServerPing { + return serverping_serverpingplayersample; + } + +- public JsonElement a(ServerPing.ServerPingPlayerSample serverping_serverpingplayersample, Type type, JsonSerializationContext jsonserializationcontext) { ++ // Paper - decompile fix ++ public JsonElement serialize(ServerPing.ServerPingPlayerSample serverping_serverpingplayersample, Type type, JsonSerializationContext jsonserializationcontext) { + JsonObject jsonobject = new JsonObject(); + + jsonobject.addProperty("max", Integer.valueOf(serverping_serverpingplayersample.a())); +@@ -0,0 +0,0 @@ public class ServerPing { + + return jsonobject; + } +- +- public JsonElement serialize(Object object, Type type, JsonSerializationContext jsonserializationcontext) { +- return this.a((ServerPing.ServerPingPlayerSample) object, type, jsonserializationcontext); +- } +- +- public Object deserialize(JsonElement jsonelement, Type type, JsonDeserializationContext jsondeserializationcontext) throws JsonParseException { +- return this.a(jsonelement, type, jsondeserializationcontext); +- } + } + } + } diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemFactoryTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemFactoryTest.java -index f5bcbdbe..3190cadf 100644 +index f5bcbdbe1..3190cadfc 100644 --- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemFactoryTest.java +++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemFactoryTest.java @@ -0,0 +0,0 @@ public class ItemFactoryTest extends AbstractTestingBase { diff --git a/Spigot-Server-Patches/ProfileWhitelistVerifyEvent.patch b/Spigot-Server-Patches/ProfileWhitelistVerifyEvent.patch index 8313523429..7a400e7ec9 100644 --- a/Spigot-Server-Patches/ProfileWhitelistVerifyEvent.patch +++ b/Spigot-Server-Patches/ProfileWhitelistVerifyEvent.patch @@ -5,7 +5,7 @@ Subject: [PATCH] ProfileWhitelistVerifyEvent diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java -index 311c0b86f..06a5b6d02 100644 +index 563b0fb12..d28838627 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -0,0 +0,0 @@ public abstract class PlayerList { diff --git a/Spigot-Server-Patches/Toggleable-player-crits-helps-mitigate-hacked-client.patch b/Spigot-Server-Patches/Toggleable-player-crits-helps-mitigate-hacked-client.patch index 731aa70a0c..6c33270f98 100644 --- a/Spigot-Server-Patches/Toggleable-player-crits-helps-mitigate-hacked-client.patch +++ b/Spigot-Server-Patches/Toggleable-player-crits-helps-mitigate-hacked-client.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Toggleable player crits, helps mitigate hacked clients. diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +index 61cc1d4e6..038f874b3 100644 --- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java @@ -0,0 +0,0 @@ public class PaperWorldConfig { @@ -21,7 +21,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private void allChunksAreSlimeChunks() { allChunksAreSlimeChunks = getBoolean("all-chunks-are-slime-chunks", false); diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +index 347237055..4b82e43a8 100644 --- a/src/main/java/net/minecraft/server/EntityHuman.java +++ b/src/main/java/net/minecraft/server/EntityHuman.java @@ -0,0 +0,0 @@ public abstract class EntityHuman extends EntityLiving { diff --git a/Spigot-Server-Patches/handle-PacketPlayInKeepAlive-async.patch b/Spigot-Server-Patches/handle-PacketPlayInKeepAlive-async.patch index d89ea7f31f..223a52b4de 100644 --- a/Spigot-Server-Patches/handle-PacketPlayInKeepAlive-async.patch +++ b/Spigot-Server-Patches/handle-PacketPlayInKeepAlive-async.patch @@ -15,7 +15,7 @@ also adding some additional logging in order to help work out what is causing random disconnections for clients. diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java -index cc58a4a93..a92bf8967 100644 +index aa57ff8ed..869a2b402 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn, ITickable { diff --git a/Spigot-Server-Patches/revert-serverside-behavior-of-keepalives.patch b/Spigot-Server-Patches/revert-serverside-behavior-of-keepalives.patch index 8e0523f65e..4d2898803a 100644 --- a/Spigot-Server-Patches/revert-serverside-behavior-of-keepalives.patch +++ b/Spigot-Server-Patches/revert-serverside-behavior-of-keepalives.patch @@ -17,7 +17,7 @@ from networking or during connections flood of chunk packets on slower clients, at the cost of dead connections being kept open for longer. diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java -index a92bf8967..d0ab87d0f 100644 +index 869a2b402..167e386c8 100644 --- a/src/main/java/net/minecraft/server/PlayerConnection.java +++ b/src/main/java/net/minecraft/server/PlayerConnection.java @@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn, ITickable { diff --git a/scripts/importmcdev.sh b/scripts/importmcdev.sh index d97288abf9..c992a4b5f6 100755 --- a/scripts/importmcdev.sh +++ b/scripts/importmcdev.sh @@ -97,6 +97,7 @@ import PlayerConnectionUtils import RegionFile import RegistryBlockID import RemoteControlListener +import ServerPing import StructureBoundingBox import StructurePiece import StructureStart