diff --git a/Spigot-API-Patches/Add-legacy-ping-support-to-PaperServerListPingEvent.patch b/Spigot-API-Patches/Add-legacy-ping-support-to-PaperServerListPingEvent.patch new file mode 100644 index 0000000000..94314d6491 --- /dev/null +++ b/Spigot-API-Patches/Add-legacy-ping-support-to-PaperServerListPingEvent.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 19:30:20 +0200 +Subject: [PATCH] Add legacy ping support to PaperServerListPingEvent + +Add a new method to StatusClient check if the client is a legacy +client that does not support all of the features provided in the +event. + +diff --git a/src/main/java/com/destroystokyo/paper/network/StatusClient.java b/src/main/java/com/destroystokyo/paper/network/StatusClient.java +index 517d1523..ffda9f6a 100644 +--- a/src/main/java/com/destroystokyo/paper/network/StatusClient.java ++++ b/src/main/java/com/destroystokyo/paper/network/StatusClient.java +@@ -0,0 +0,0 @@ import com.destroystokyo.paper.event.server.PaperServerListPingEvent; + */ + public interface StatusClient extends NetworkClient { + ++ /** ++ * Returns whether the client is using an older version that doesn't ++ * support all of the features in {@link PaperServerListPingEvent}. ++ * ++ *

For Vanilla, this returns {@code true} for all clients older than 1.7.

++ * ++ * @return {@code true} if the client is using legacy ping ++ */ ++ default boolean isLegacy() { ++ return false; ++ } ++ + } +-- \ No newline at end of file diff --git a/Spigot-Server-Patches/Call-PaperServerListPingEvent-for-legacy-pings.patch b/Spigot-Server-Patches/Call-PaperServerListPingEvent-for-legacy-pings.patch new file mode 100644 index 0000000000..60c89673d1 --- /dev/null +++ b/Spigot-Server-Patches/Call-PaperServerListPingEvent-for-legacy-pings.patch @@ -0,0 +1,155 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 19:30:51 +0200 +Subject: [PATCH] Call PaperServerListPingEvent for legacy pings + + +diff --git a/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java b/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java +new file mode 100644 +index 000000000..74c012fd4 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/network/PaperLegacyStatusClient.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper.network; ++ ++import com.destroystokyo.paper.event.server.PaperServerListPingEvent; ++import net.minecraft.server.MinecraftServer; ++import org.apache.commons.lang3.StringUtils; ++import org.bukkit.ChatColor; ++ ++import java.net.InetSocketAddress; ++ ++import javax.annotation.Nullable; ++ ++public final class PaperLegacyStatusClient implements StatusClient { ++ ++ private final InetSocketAddress address; ++ private final int protocolVersion; ++ @Nullable private final InetSocketAddress virtualHost; ++ ++ private PaperLegacyStatusClient(InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) { ++ this.address = address; ++ this.protocolVersion = protocolVersion; ++ this.virtualHost = virtualHost; ++ } ++ ++ @Override ++ public InetSocketAddress getAddress() { ++ return this.address; ++ } ++ ++ @Override ++ public int getProtocolVersion() { ++ return this.protocolVersion; ++ } ++ ++ @Nullable ++ @Override ++ public InetSocketAddress getVirtualHost() { ++ return this.virtualHost; ++ } ++ ++ @Override ++ public boolean isLegacy() { ++ return true; ++ } ++ ++ public static PaperServerListPingEvent processRequest(MinecraftServer server, ++ InetSocketAddress address, int protocolVersion, @Nullable InetSocketAddress virtualHost) { ++ ++ PaperServerListPingEvent event = new PaperServerListPingEventImpl(server, ++ new PaperLegacyStatusClient(address, protocolVersion, virtualHost), Byte.MAX_VALUE, null); ++ server.server.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return null; ++ } ++ ++ return event; ++ } ++ ++ public static String getMotd(PaperServerListPingEvent event) { ++ return getFirstLine(event.getMotd()); ++ } ++ ++ public static String getUnformattedMotd(PaperServerListPingEvent event) { ++ // Strip color codes and all other occurrences of the color char (because it's used as delimiter) ++ return getFirstLine(StringUtils.remove(ChatColor.stripColor(event.getMotd()), ChatColor.COLOR_CHAR)); ++ } ++ ++ private static String getFirstLine(String s) { ++ int pos = s.indexOf('\n'); ++ return pos >= 0 ? s.substring(0, pos) : s; ++ } ++ ++} +diff --git a/src/main/java/net/minecraft/server/LegacyPingHandler.java b/src/main/java/net/minecraft/server/LegacyPingHandler.java +index f084a653a..39d19e91b 100644 +--- a/src/main/java/net/minecraft/server/LegacyPingHandler.java ++++ b/src/main/java/net/minecraft/server/LegacyPingHandler.java +@@ -0,0 +0,0 @@ import java.net.InetSocketAddress; + import java.nio.charset.StandardCharsets; + import org.apache.logging.log4j.LogManager; + import org.apache.logging.log4j.Logger; ++import com.destroystokyo.paper.network.PaperLegacyStatusClient; // Paper + + public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + +@@ -0,0 +0,0 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + MinecraftServer minecraftserver = this.b.d(); + int i = bytebuf.readableBytes(); + String s; ++ com.destroystokyo.paper.event.server.PaperServerListPingEvent event; // Paper + + switch (i) { + case 0: + LegacyPingHandler.a.debug("Ping: (<1.3.x) from {}:{}", inetsocketaddress.getAddress(), Integer.valueOf(inetsocketaddress.getPort())); +- s = String.format("%s\u00a7%d\u00a7%d", new Object[] { minecraftserver.getMotd(), Integer.valueOf(minecraftserver.H()), Integer.valueOf(minecraftserver.I())}); ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = PaperLegacyStatusClient.processRequest(minecraftserver, inetsocketaddress, 39, null); ++ if (event == null) { ++ channelhandlercontext.close(); ++ break; ++ } ++ s = String.format("%s\u00a7%d\u00a7%d", PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers()); ++ // Paper end + this.a(channelhandlercontext, this.a(s)); + break; + +@@ -0,0 +0,0 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + } + + LegacyPingHandler.a.debug("Ping: (1.4-1.5.x) from {}:{}", inetsocketaddress.getAddress(), Integer.valueOf(inetsocketaddress.getPort())); +- s = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", new Object[] { Integer.valueOf(127), minecraftserver.getVersion(), minecraftserver.getMotd(), Integer.valueOf(minecraftserver.H()), Integer.valueOf(minecraftserver.I())}); ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = PaperLegacyStatusClient.processRequest(minecraftserver, inetsocketaddress, 61, null); ++ if (event == null) { ++ channelhandlercontext.close(); ++ break; ++ } ++ s = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(), ++ PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers()); ++ // Paper end + this.a(channelhandlercontext, this.a(s)); + break; + +@@ -0,0 +0,0 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + a.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress()); + +- String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", +- Byte.MAX_VALUE, server.getVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port); ++ com.destroystokyo.paper.event.server.PaperServerListPingEvent event = PaperLegacyStatusClient.processRequest( ++ server, (InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost); ++ if (event == null) { ++ ctx.close(); ++ return; ++ } ++ ++ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(), ++ PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers()); + this.a(ctx, this.a(response)); + } + +-- \ No newline at end of file diff --git a/Spigot-Server-Patches/Make-the-legacy-ping-handler-more-reliable.patch b/Spigot-Server-Patches/Make-the-legacy-ping-handler-more-reliable.patch new file mode 100644 index 0000000000..cc6f768f8d --- /dev/null +++ b/Spigot-Server-Patches/Make-the-legacy-ping-handler-more-reliable.patch @@ -0,0 +1,155 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Minecrell +Date: Wed, 11 Oct 2017 18:22:50 +0200 +Subject: [PATCH] Make the legacy ping handler more reliable + +The Minecraft server often fails to respond to old ("legacy") pings +from old Minecraft versions using the protocol used before the switch +to Netty in Minecraft 1.7. + +Due to packet fragmentation[1], we might not have all needed bytes +available when the LegacyPingHandler is called. In this case, it will +run into an error, remove the handler and continue using the modern +protocol. + +This is unlikely to happen for the first two revisions of the legacy +ping protocol (used in Minecraft 1.5.x and older) since the request +consists of only one or two bytes, but happens frequently for the +last/third revision introduced in Minecraft 1.6. + +It has much larger, variable packet sizes due to the inclusion of +the virtual host (the hostname/port used to connect to the server). + +The solution[2] is simple: If we find more than two matching bytes, +we buffer the remaining bytes until we have enough to fully read and +respond to the request. + +[1]: https://netty.io/wiki/user-guide-for-4.x.html#wiki-h3-11 +[2]: https://netty.io/wiki/user-guide-for-4.x.html#wiki-h4-13 + +diff --git a/src/main/java/net/minecraft/server/LegacyPingHandler.java b/src/main/java/net/minecraft/server/LegacyPingHandler.java +index 4c1a0181a..f084a653a 100644 +--- a/src/main/java/net/minecraft/server/LegacyPingHandler.java ++++ b/src/main/java/net/minecraft/server/LegacyPingHandler.java +@@ -0,0 +0,0 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + private static final Logger a = LogManager.getLogger(); + private final ServerConnection b; ++ private ByteBuf buf; // Paper + + public LegacyPingHandler(ServerConnection serverconnection) { + this.b = serverconnection; +@@ -0,0 +0,0 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) throws Exception { + ByteBuf bytebuf = (ByteBuf) object; +- ++ // Paper start - Make legacy ping handler more reliable ++ if (this.buf != null) { ++ try { ++ readLegacy1_6(channelhandlercontext, bytebuf); ++ } finally { ++ bytebuf.release(); ++ } ++ return; ++ } ++ // Paper end + bytebuf.markReaderIndex(); + boolean flag = true; + +@@ -0,0 +0,0 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + break; + + default: ++ // Paper start - Replace with improved version below ++ if (bytebuf.readUnsignedByte() != 0x01 || bytebuf.readUnsignedByte() != 0xFA) return; ++ readLegacy1_6(channelhandlercontext, bytebuf); ++ /* + boolean flag1 = bytebuf.readUnsignedByte() == 1; + + flag1 &= bytebuf.readUnsignedByte() == 250; +@@ -0,0 +0,0 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + } finally { + bytebuf1.release(); + } ++ */ ++ // Paper end + } + + bytebuf.release(); +@@ -0,0 +0,0 @@ public class LegacyPingHandler extends ChannelInboundHandlerAdapter { + + } + ++ // Paper start ++ private void readLegacy1_6(ChannelHandlerContext ctx, ByteBuf part) { ++ ByteBuf buf = this.buf; ++ ++ if (buf == null) { ++ this.buf = buf = ctx.alloc().buffer(); ++ buf.markReaderIndex(); ++ } else { ++ buf.resetReaderIndex(); ++ } ++ ++ buf.writeBytes(part); ++ ++ // Short + Short + Byte + Short + Int ++ if (!buf.isReadable(2 + 2 + 1 + 2 + 4)) { ++ return; ++ } ++ ++ short length = buf.readShort(); ++ if (!buf.isReadable(length * 2)) { ++ return; ++ } ++ ++ if (!buf.readBytes(length * 2).toString(StandardCharsets.UTF_16BE).equals("MC|PingHost")) { ++ removeHandler(ctx); ++ return; ++ } ++ ++ if (!buf.isReadable(2)) { ++ return; ++ } ++ ++ length = buf.readShort(); ++ if (!buf.isReadable(length)) { ++ return; ++ } ++ ++ MinecraftServer server = this.b.d(); ++ int protocolVersion = buf.readByte(); ++ length = buf.readShort(); ++ String host = buf.readBytes(length * 2).toString(StandardCharsets.UTF_16BE); ++ int port = buf.readInt(); ++ ++ if (buf.isReadable()) { ++ removeHandler(ctx); ++ return; ++ } ++ ++ buf.release(); ++ this.buf = null; ++ ++ a.debug("Ping: (1.6) from {}", ctx.channel().remoteAddress()); ++ ++ String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", ++ Byte.MAX_VALUE, server.getVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ this.a(ctx, this.a(response)); ++ } ++ ++ private void removeHandler(ChannelHandlerContext ctx) { ++ ByteBuf buf = this.buf; ++ this.buf = null; ++ ++ buf.resetReaderIndex(); ++ ctx.pipeline().remove("legacy_query"); ++ ctx.fireChannelRead(buf); ++ } ++ // Paper end ++ + private void a(ChannelHandlerContext channelhandlercontext, ByteBuf bytebuf) { + channelhandlercontext.pipeline().firstContext().writeAndFlush(bytebuf).addListener(ChannelFutureListener.CLOSE); + } +-- \ No newline at end of file diff --git a/scripts/importmcdev.sh b/scripts/importmcdev.sh index c992a4b5f6..40162024ed 100755 --- a/scripts/importmcdev.sh +++ b/scripts/importmcdev.sh @@ -77,6 +77,7 @@ import IHopper import ItemBlock import ItemFireworks import ItemMonsterEgg +import LegacyPingHandler import NavigationAbstract import NBTTagCompound import NBTTagList