diff --git a/patches/api/Add-Git-information-to-version-command-on-startup.patch b/patches/api/Add-Git-information-to-version-command-on-startup.patch deleted file mode 100644 index 485b44fc47..0000000000 --- a/patches/api/Add-Git-information-to-version-command-on-startup.patch +++ /dev/null @@ -1,101 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Professor Bloodstone -Date: Sun, 20 Jun 2021 01:48:31 +0200 -Subject: [PATCH] Add Git information to version command/on startup - - -diff --git a/src/main/java/io/papermc/paper/util/JarManifests.java b/src/main/java/io/papermc/paper/util/JarManifests.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/JarManifests.java -@@ -0,0 +0,0 @@ -+package io.papermc.paper.util; -+ -+import java.io.IOException; -+import java.io.InputStream; -+import java.net.URL; -+import java.util.Collections; -+import java.util.Map; -+import java.util.WeakHashMap; -+import java.util.jar.Manifest; -+import org.jetbrains.annotations.ApiStatus; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+@ApiStatus.Internal -+public final class JarManifests { -+ private JarManifests() { -+ } -+ -+ private static final Map MANIFESTS = Collections.synchronizedMap(new WeakHashMap<>()); -+ -+ public static @Nullable Manifest manifest(final @NotNull Class clazz) { -+ return MANIFESTS.computeIfAbsent(clazz.getClassLoader(), classLoader -> { -+ final String classLocation = "/" + clazz.getName().replace(".", "/") + ".class"; -+ final URL resource = clazz.getResource(classLocation); -+ if (resource == null) { -+ return null; -+ } -+ final String classFilePath = resource.toString().replace("\\", "/"); -+ final String archivePath = classFilePath.substring(0, classFilePath.length() - classLocation.length()); -+ try (final InputStream stream = new URL(archivePath + "/META-INF/MANIFEST.MF").openStream()) { -+ return new Manifest(stream); -+ } catch (final IOException ex) { -+ return null; -+ } -+ }); -+ } -+} -diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/Bukkit.java -+++ b/src/main/java/org/bukkit/Bukkit.java -@@ -0,0 +0,0 @@ import org.bukkit.util.CachedServerIcon; - import org.jetbrains.annotations.Contract; - import org.jetbrains.annotations.NotNull; - import org.jetbrains.annotations.Nullable; -+import io.papermc.paper.util.JarManifests; // Paper - - /** - * Represents the Bukkit core, for version and Server singleton handling -@@ -0,0 +0,0 @@ public final class Bukkit { - } - - Bukkit.server = server; -- server.getLogger().info("This server is running " + getName() + " version " + getVersion() + " (Implementing API version " + getBukkitVersion() + ")"); -+ // Paper start - add git information -+ server.getLogger().info(getVersionMessage()); -+ } -+ /** -+ * Gets message describing the version server is running. -+ * -+ * @return message describing the version server is running -+ */ -+ @NotNull -+ public static String getVersionMessage() { -+ final var manifest = JarManifests.manifest(Bukkit.getServer().getClass()); -+ final String gitBranch = manifest == null ? null : manifest.getMainAttributes().getValue("Git-Branch"); -+ final String gitCommit = manifest == null ? null : manifest.getMainAttributes().getValue("Git-Commit"); -+ String branchMsg = " on " + gitBranch; -+ if ("master".equals(gitBranch) || "main".equals(gitBranch)) { -+ branchMsg = ""; // Don't show branch on main/master -+ } -+ return "This server is running " + getName() + " version " + getVersion() + " (Implementing API version " + getBukkitVersion() + ") (Git: " + gitCommit + branchMsg + ")"; -+ // Paper end - } - - /** -diff --git a/src/main/java/org/bukkit/command/defaults/VersionCommand.java b/src/main/java/org/bukkit/command/defaults/VersionCommand.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/command/defaults/VersionCommand.java -+++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java -@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { - private void setVersionMessage(final @NotNull Component msg) { - lastCheck = System.currentTimeMillis(); - final Component message = Component.textOfChildren( -- Component.text("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")", NamedTextColor.WHITE), -+ Component.text(Bukkit.getVersionMessage(), NamedTextColor.WHITE), - Component.newline(), - msg - ); diff --git a/patches/api/Expose-game-version.patch b/patches/api/Expose-game-version.patch deleted file mode 100644 index eadd8c70bd..0000000000 --- a/patches/api/Expose-game-version.patch +++ /dev/null @@ -1,50 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mark Vainomaa -Date: Fri, 1 May 2020 17:39:02 +0300 -Subject: [PATCH] Expose game version - - -diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/Bukkit.java -+++ b/src/main/java/org/bukkit/Bukkit.java -@@ -0,0 +0,0 @@ public final class Bukkit { - return server.getBukkitVersion(); - } - -+ // Paper start - expose game version -+ /** -+ * Gets the version of game this server implements -+ * -+ * @return version of game -+ */ -+ @NotNull -+ public static String getMinecraftVersion() { -+ return server.getMinecraftVersion(); -+ } -+ // Paper end -+ - /** - * Gets a view of all currently logged in players. This {@linkplain - * Collections#unmodifiableCollection(Collection) view} is a reused -diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/Server.java -+++ b/src/main/java/org/bukkit/Server.java -@@ -0,0 +0,0 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi - @NotNull - public String getBukkitVersion(); - -+ // Paper start - expose game version -+ /** -+ * Gets the version of game this server implements -+ * -+ * @return version of game -+ */ -+ @NotNull -+ String getMinecraftVersion(); -+ // Paper end -+ - /** - * Gets a view of all currently logged in players. This {@linkplain - * Collections#unmodifiableCollection(Collection) view} is a reused diff --git a/patches/api/Expose-server-build-information.patch b/patches/api/Expose-server-build-information.patch new file mode 100644 index 0000000000..45068509fe --- /dev/null +++ b/patches/api/Expose-server-build-information.patch @@ -0,0 +1,528 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown <1254957+zachbr@users.noreply.github.com> +Date: Mon, 27 May 2019 01:10:06 -0500 +Subject: [PATCH] Expose server build information + +Co-authored-by: Professor Bloodstone +Co-authored-by: Mark Vainomaa +Co-authored-by: masmc05 +Co-authored-by: Riley Park + +diff --git a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper.util; ++ ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.format.NamedTextColor; ++import org.bukkit.Bukkit; ++import org.jetbrains.annotations.NotNull; ++ ++public interface VersionFetcher { ++ /** ++ * Amount of time to cache results for in milliseconds ++ *

++ * Negative values will never cache. ++ * ++ * @return cache time ++ */ ++ long getCacheTime(); ++ ++ /** ++ * Gets the version message to cache and show to command senders. ++ * ++ *

NOTE: This is run in a new thread separate from that of the command processing thread

++ * ++ * @param serverVersion the current version of the server (will match {@link Bukkit#getVersion()}) ++ * @return the message to show when requesting a version ++ */ ++ @NotNull ++ Component getVersionMessage(@NotNull String serverVersion); ++ ++ class DummyVersionFetcher implements VersionFetcher { ++ ++ @Override ++ public long getCacheTime() { ++ return -1; ++ } ++ ++ @NotNull ++ @Override ++ public Component getVersionMessage(@NotNull String serverVersion) { ++ Bukkit.getLogger().warning("Version provider has not been set, cannot check for updates!"); ++ Bukkit.getLogger().info("Override the default implementation of org.bukkit.UnsafeValues#getVersionFetcher()"); ++ new Throwable().printStackTrace(); ++ return Component.text("Unable to check for updates. No version provider set.", NamedTextColor.RED); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/ServerBuildInfo.java b/src/main/java/io/papermc/paper/ServerBuildInfo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/ServerBuildInfo.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper; ++ ++import java.time.Instant; ++import java.util.Optional; ++import java.util.OptionalInt; ++import net.kyori.adventure.key.Key; ++import net.kyori.adventure.util.Services; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Information about the current server build. ++ */ ++@ApiStatus.NonExtendable ++public interface ServerBuildInfo { ++ /** ++ * The brand id for Paper. ++ */ ++ Key BRAND_PAPER_ID = Key.key("papermc", "paper"); ++ ++ /** ++ * Gets the {@code ServerBuildInfo}. ++ * ++ * @return the {@code ServerBuildInfo} ++ */ ++ static @NotNull ServerBuildInfo buildInfo() { ++ // ++ final class Holder { ++ static final Optional INSTANCE = Services.service(ServerBuildInfo.class); ++ } ++ // ++ return Holder.INSTANCE.orElseThrow(); ++ } ++ ++ /** ++ * Gets the brand id of the server. ++ * ++ * @return the brand id of the server (e.g. "papermc:paper") ++ */ ++ @NotNull Key brandId(); ++ ++ /** ++ * Checks if the current server supports the specified brand. ++ * ++ * @param brandId the brand to check (e.g. "papermc:folia") ++ * @return {@code true} if the server supports the specified brand ++ */ ++ @ApiStatus.Experimental ++ boolean isBrandCompatible(final @NotNull Key brandId); ++ ++ /** ++ * Gets the brand name of the server. ++ * ++ * @return the brand name of the server (e.g. "Paper") ++ */ ++ @NotNull String brandName(); ++ ++ /** ++ * Gets the Minecraft version id. ++ * ++ * @return the Minecraft version id (e.g. "1.20.4", "1.20.2-pre2", "23w31a") ++ */ ++ @NotNull String minecraftVersionId(); ++ ++ /** ++ * Gets the Minecraft version name. ++ * ++ * @return the Minecraft version name (e.g. "1.20.4", "1.20.2 Pre-release 2", "23w31a") ++ */ ++ @NotNull String minecraftVersionName(); ++ ++ /** ++ * Gets the build number. ++ * ++ * @return the build number ++ */ ++ @NotNull OptionalInt buildNumber(); ++ ++ /** ++ * Gets the build time. ++ * ++ * @return the build time ++ */ ++ @NotNull Instant buildTime(); ++ ++ /** ++ * Gets the git commit branch. ++ * ++ * @return the git commit branch ++ */ ++ @NotNull Optional gitBranch(); ++ ++ /** ++ * Gets the git commit hash. ++ * ++ * @return the git commit hash ++ */ ++ @NotNull Optional gitCommit(); ++ ++ /** ++ * Creates a string representation of the server build information. ++ * ++ * @param representation the type of representation ++ * @return a string ++ */ ++ @NotNull String asString(final @NotNull StringRepresentation representation); ++ ++ /** ++ * String representation types. ++ */ ++ enum StringRepresentation { ++ /** ++ * A simple version string, in format {@code --}. ++ */ ++ VERSION_SIMPLE, ++ /** ++ * A simple version string, in format {@code --@ ()}. ++ */ ++ VERSION_FULL, ++ } ++} +diff --git a/src/main/java/io/papermc/paper/util/JarManifests.java b/src/main/java/io/papermc/paper/util/JarManifests.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/util/JarManifests.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.util; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.net.URL; ++import java.util.Collections; ++import java.util.Map; ++import java.util.WeakHashMap; ++import java.util.jar.Manifest; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++@ApiStatus.Internal ++public final class JarManifests { ++ private JarManifests() { ++ } ++ ++ private static final Map MANIFESTS = Collections.synchronizedMap(new WeakHashMap<>()); ++ ++ public static @Nullable Manifest manifest(final @NotNull Class clazz) { ++ return MANIFESTS.computeIfAbsent(clazz.getClassLoader(), classLoader -> { ++ final String classLocation = "/" + clazz.getName().replace(".", "/") + ".class"; ++ final URL resource = clazz.getResource(classLocation); ++ if (resource == null) { ++ return null; ++ } ++ final String classFilePath = resource.toString().replace("\\", "/"); ++ final String archivePath = classFilePath.substring(0, classFilePath.length() - classLocation.length()); ++ try (final InputStream stream = new URL(archivePath + "/META-INF/MANIFEST.MF").openStream()) { ++ return new Manifest(stream); ++ } catch (final IOException ex) { ++ return null; ++ } ++ }); ++ } ++} +diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/Bukkit.java ++++ b/src/main/java/org/bukkit/Bukkit.java +@@ -0,0 +0,0 @@ public final class Bukkit { + } + + Bukkit.server = server; +- server.getLogger().info("This server is running " + getName() + " version " + getVersion() + " (Implementing API version " + getBukkitVersion() + ")"); ++ // Paper start - add git information ++ server.getLogger().info(getVersionMessage()); ++ } ++ /** ++ * Gets message describing the version server is running. ++ * ++ * @return message describing the version server is running ++ */ ++ @NotNull ++ public static String getVersionMessage() { ++ final io.papermc.paper.ServerBuildInfo version = io.papermc.paper.ServerBuildInfo.buildInfo(); ++ return "This server is running " + getName() + " version " + version.asString(io.papermc.paper.ServerBuildInfo.StringRepresentation.VERSION_FULL) + " (Implementing API version " + getBukkitVersion() + ")"; ++ // Paper end + } + + /** + * Gets the name of this server implementation. + * + * @return name of this server implementation ++ * @see io.papermc.paper.ServerBuildInfo#brandName() + */ + @NotNull + public static String getName() { +@@ -0,0 +0,0 @@ public final class Bukkit { + * Gets the version string of this server implementation. + * + * @return version of this server implementation ++ * @see io.papermc.paper.ServerBuildInfo + */ + @NotNull + public static String getVersion() { +@@ -0,0 +0,0 @@ public final class Bukkit { + return server.getBukkitVersion(); + } + ++ // Paper start - expose game version ++ /** ++ * Gets the version of game this server implements ++ * ++ * @return version of game ++ * @see io.papermc.paper.ServerBuildInfo#minecraftVersionId() ++ * @see io.papermc.paper.ServerBuildInfo#minecraftVersionName() ++ */ ++ @NotNull ++ public static String getMinecraftVersion() { ++ return server.getMinecraftVersion(); ++ } ++ // Paper end ++ + /** + * Gets a view of all currently logged in players. This {@linkplain + * Collections#unmodifiableCollection(Collection) view} is a reused +diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/Server.java ++++ b/src/main/java/org/bukkit/Server.java +@@ -0,0 +0,0 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi + @NotNull + public String getBukkitVersion(); + ++ // Paper start - expose game version ++ /** ++ * Gets the version of game this server implements ++ * ++ * @return version of game ++ */ ++ @NotNull ++ String getMinecraftVersion(); ++ // Paper end ++ + /** + * Gets a view of all currently logged in players. This {@linkplain + * Collections#unmodifiableCollection(Collection) view} is a reused +diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/UnsafeValues.java ++++ b/src/main/java/org/bukkit/UnsafeValues.java +@@ -0,0 +0,0 @@ public interface UnsafeValues { + * @return name + */ + String getTimingsServerName(); ++ ++ /** ++ * Called once by the version command on first use, then cached. ++ */ ++ default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { ++ return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher(); ++ } + // Paper end + } +diff --git a/src/main/java/org/bukkit/command/defaults/VersionCommand.java b/src/main/java/org/bukkit/command/defaults/VersionCommand.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/command/defaults/VersionCommand.java ++++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java +@@ -0,0 +0,0 @@ import org.bukkit.plugin.Plugin; + import org.bukkit.plugin.PluginDescriptionFile; + import org.bukkit.util.StringUtil; + import org.jetbrains.annotations.NotNull; ++// Paper start - version command 2.0 ++import com.destroystokyo.paper.util.VersionFetcher; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.event.ClickEvent; ++import net.kyori.adventure.text.format.TextDecoration; ++import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; ++// Paper end - version command 2.0 + + public class VersionCommand extends BukkitCommand { ++ private VersionFetcher versionFetcher; // Paper - version command 2.0 ++ private VersionFetcher getVersionFetcher() { // lazy load because unsafe isn't available at command registration ++ if (versionFetcher == null) { ++ versionFetcher = Bukkit.getUnsafe().getVersionFetcher(); ++ } ++ ++ return versionFetcher; ++ } ++ + public VersionCommand(@NotNull String name) { + super(name); + +@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { + if (!testPermission(sender)) return true; + + if (args.length == 0) { +- sender.sendMessage("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")"); ++ //sender.sendMessage("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")"); // Paper - moved to setVersionMessage + sendVersion(sender); + } else { + StringBuilder name = new StringBuilder(); +@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { + + private void describeToSender(@NotNull Plugin plugin, @NotNull CommandSender sender) { + PluginDescriptionFile desc = plugin.getDescription(); +- sender.sendMessage(ChatColor.GREEN + desc.getName() + ChatColor.WHITE + " version " + ChatColor.GREEN + desc.getVersion()); +- ++ // Paper start - version command 2.0 ++ sender.sendMessage( ++ Component.text() ++ .append(Component.text(desc.getName(), NamedTextColor.GREEN)) ++ .append(Component.text(" version ")) ++ .append(Component.text(desc.getVersion(), NamedTextColor.GREEN) ++ .hoverEvent(Component.text("Click to copy to clipboard", NamedTextColor.WHITE)) ++ .clickEvent(ClickEvent.copyToClipboard(desc.getVersion())) ++ ) ++ ); ++ // Paper end - version command 2.0 + if (desc.getDescription() != null) { + sender.sendMessage(desc.getDescription()); + } +@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { + + private final ReentrantLock versionLock = new ReentrantLock(); + private boolean hasVersion = false; +- private String versionMessage = null; ++ private Component versionMessage = null; // Paper + private final Set versionWaiters = new HashSet(); + private boolean versionTaskStarted = false; + private long lastCheck = 0; + + private void sendVersion(@NotNull CommandSender sender) { + if (hasVersion) { +- if (System.currentTimeMillis() - lastCheck > 21600000) { ++ if (System.currentTimeMillis() - lastCheck > getVersionFetcher().getCacheTime()) { // Paper - use version supplier + lastCheck = System.currentTimeMillis(); + hasVersion = false; + } else { +@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { + return; + } + versionWaiters.add(sender); +- sender.sendMessage("Checking version, please wait..."); ++ sender.sendMessage(Component.text("Checking version, please wait...", NamedTextColor.WHITE, TextDecoration.ITALIC)); // Paper + if (!versionTaskStarted) { + versionTaskStarted = true; + new Thread(new Runnable() { +@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { + + private void obtainVersion() { + String version = Bukkit.getVersion(); ++ // Paper start ++ if (version.startsWith("null")) { // running from ide? ++ setVersionMessage(Component.text("Unknown version, custom build?", NamedTextColor.YELLOW)); ++ return; ++ } ++ setVersionMessage(getVersionFetcher().getVersionMessage(version)); ++ /* + if (version == null) version = "Custom"; + String[] parts = version.substring(0, version.indexOf(' ')).split("-"); + if (parts.length == 4) { +@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { + } else { + setVersionMessage("Unknown version, custom build?"); + } ++ */ ++ // Paper end + } + +- private void setVersionMessage(@NotNull String msg) { ++ // Paper start ++ private void setVersionMessage(final @NotNull Component msg) { + lastCheck = System.currentTimeMillis(); +- versionMessage = msg; ++ final Component message = Component.textOfChildren( ++ Component.text(Bukkit.getVersionMessage(), NamedTextColor.WHITE), ++ Component.newline(), ++ msg ++ ); ++ this.versionMessage = Component.text() ++ .append(message) ++ .hoverEvent(Component.text("Click to copy to clipboard", NamedTextColor.WHITE)) ++ .clickEvent(ClickEvent.copyToClipboard(PlainTextComponentSerializer.plainText().serialize(message))) ++ .build(); ++ // Paper end + versionLock.lock(); + try { + hasVersion = true; +diff --git a/src/test/java/io/papermc/paper/TestServerBuildInfo.java b/src/test/java/io/papermc/paper/TestServerBuildInfo.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/TestServerBuildInfo.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper; ++ ++import java.time.Instant; ++import java.util.Optional; ++import java.util.OptionalInt; ++import net.kyori.adventure.key.Key; ++import org.jetbrains.annotations.NotNull; ++ ++public class TestServerBuildInfo implements ServerBuildInfo { ++ @Override ++ public @NotNull Key brandId() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public boolean isBrandCompatible(final @NotNull Key brandId) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull String brandName() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull String minecraftVersionId() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull String minecraftVersionName() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull OptionalInt buildNumber() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull Instant buildTime() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull Optional gitBranch() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull Optional gitCommit() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull String asString(final @NotNull StringRepresentation representation) { ++ return ""; ++ } ++} +diff --git a/src/test/resources/META-INF/services/io.papermc.paper.ServerBuildInfo b/src/test/resources/META-INF/services/io.papermc.paper.ServerBuildInfo +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/test/resources/META-INF/services/io.papermc.paper.ServerBuildInfo +@@ -0,0 +1 @@ ++io.papermc.paper.TestServerBuildInfo diff --git a/patches/api/Version-Command-2.0.patch b/patches/api/Version-Command-2.0.patch deleted file mode 100644 index c4e7cb3025..0000000000 --- a/patches/api/Version-Command-2.0.patch +++ /dev/null @@ -1,200 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown <1254957+zachbr@users.noreply.github.com> -Date: Mon, 27 May 2019 01:10:06 -0500 -Subject: [PATCH] Version Command 2.0 - - -diff --git a/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/util/VersionFetcher.java -@@ -0,0 +0,0 @@ -+package com.destroystokyo.paper.util; -+ -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.format.NamedTextColor; -+import org.bukkit.Bukkit; -+import org.jetbrains.annotations.NotNull; -+ -+public interface VersionFetcher { -+ /** -+ * Amount of time to cache results for in milliseconds -+ *

-+ * Negative values will never cache. -+ * -+ * @return cache time -+ */ -+ long getCacheTime(); -+ -+ /** -+ * Gets the version message to cache and show to command senders. -+ * -+ *

NOTE: This is run in a new thread separate from that of the command processing thread

-+ * -+ * @param serverVersion the current version of the server (will match {@link Bukkit#getVersion()}) -+ * @return the message to show when requesting a version -+ */ -+ @NotNull -+ Component getVersionMessage(@NotNull String serverVersion); -+ -+ class DummyVersionFetcher implements VersionFetcher { -+ -+ @Override -+ public long getCacheTime() { -+ return -1; -+ } -+ -+ @NotNull -+ @Override -+ public Component getVersionMessage(@NotNull String serverVersion) { -+ Bukkit.getLogger().warning("Version provider has not been set, cannot check for updates!"); -+ Bukkit.getLogger().info("Override the default implementation of org.bukkit.UnsafeValues#getVersionFetcher()"); -+ new Throwable().printStackTrace(); -+ return Component.text("Unable to check for updates. No version provider set.", NamedTextColor.RED); -+ } -+ } -+} -diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/UnsafeValues.java -+++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -0,0 +0,0 @@ public interface UnsafeValues { - * @return name - */ - String getTimingsServerName(); -+ -+ /** -+ * Called once by the version command on first use, then cached. -+ */ -+ default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { -+ return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher(); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/command/defaults/VersionCommand.java b/src/main/java/org/bukkit/command/defaults/VersionCommand.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/command/defaults/VersionCommand.java -+++ b/src/main/java/org/bukkit/command/defaults/VersionCommand.java -@@ -0,0 +0,0 @@ import org.bukkit.plugin.Plugin; - import org.bukkit.plugin.PluginDescriptionFile; - import org.bukkit.util.StringUtil; - import org.jetbrains.annotations.NotNull; -+// Paper start - version command 2.0 -+import com.destroystokyo.paper.util.VersionFetcher; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.event.ClickEvent; -+import net.kyori.adventure.text.format.TextDecoration; -+import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -+// Paper end - version command 2.0 - - public class VersionCommand extends BukkitCommand { -+ private VersionFetcher versionFetcher; // Paper - version command 2.0 -+ private VersionFetcher getVersionFetcher() { // lazy load because unsafe isn't available at command registration -+ if (versionFetcher == null) { -+ versionFetcher = Bukkit.getUnsafe().getVersionFetcher(); -+ } -+ -+ return versionFetcher; -+ } -+ - public VersionCommand(@NotNull String name) { - super(name); - -@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { - if (!testPermission(sender)) return true; - - if (args.length == 0) { -- sender.sendMessage("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")"); -+ //sender.sendMessage("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")"); // Paper - moved to setVersionMessage - sendVersion(sender); - } else { - StringBuilder name = new StringBuilder(); -@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { - - private void describeToSender(@NotNull Plugin plugin, @NotNull CommandSender sender) { - PluginDescriptionFile desc = plugin.getDescription(); -- sender.sendMessage(ChatColor.GREEN + desc.getName() + ChatColor.WHITE + " version " + ChatColor.GREEN + desc.getVersion()); -- -+ // Paper start - version command 2.0 -+ sender.sendMessage( -+ Component.text() -+ .append(Component.text(desc.getName(), NamedTextColor.GREEN)) -+ .append(Component.text(" version ")) -+ .append(Component.text(desc.getVersion(), NamedTextColor.GREEN) -+ .hoverEvent(Component.text("Click to copy to clipboard", NamedTextColor.WHITE)) -+ .clickEvent(ClickEvent.copyToClipboard(desc.getVersion())) -+ ) -+ ); -+ // Paper end - version command 2.0 - if (desc.getDescription() != null) { - sender.sendMessage(desc.getDescription()); - } -@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { - - private final ReentrantLock versionLock = new ReentrantLock(); - private boolean hasVersion = false; -- private String versionMessage = null; -+ private Component versionMessage = null; // Paper - private final Set versionWaiters = new HashSet(); - private boolean versionTaskStarted = false; - private long lastCheck = 0; - - private void sendVersion(@NotNull CommandSender sender) { - if (hasVersion) { -- if (System.currentTimeMillis() - lastCheck > 21600000) { -+ if (System.currentTimeMillis() - lastCheck > getVersionFetcher().getCacheTime()) { // Paper - use version supplier - lastCheck = System.currentTimeMillis(); - hasVersion = false; - } else { -@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { - return; - } - versionWaiters.add(sender); -- sender.sendMessage("Checking version, please wait..."); -+ sender.sendMessage(Component.text("Checking version, please wait...", NamedTextColor.WHITE, TextDecoration.ITALIC)); // Paper - if (!versionTaskStarted) { - versionTaskStarted = true; - new Thread(new Runnable() { -@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { - - private void obtainVersion() { - String version = Bukkit.getVersion(); -+ // Paper start -+ if (version.startsWith("null")) { // running from ide? -+ setVersionMessage(Component.text("Unknown version, custom build?", NamedTextColor.YELLOW)); -+ return; -+ } -+ setVersionMessage(getVersionFetcher().getVersionMessage(version)); -+ /* - if (version == null) version = "Custom"; - String[] parts = version.substring(0, version.indexOf(' ')).split("-"); - if (parts.length == 4) { -@@ -0,0 +0,0 @@ public class VersionCommand extends BukkitCommand { - } else { - setVersionMessage("Unknown version, custom build?"); - } -+ */ -+ // Paper end - } - -- private void setVersionMessage(@NotNull String msg) { -+ // Paper start -+ private void setVersionMessage(final @NotNull Component msg) { - lastCheck = System.currentTimeMillis(); -- versionMessage = msg; -+ final Component message = Component.textOfChildren( -+ Component.text("This server is running " + Bukkit.getName() + " version " + Bukkit.getVersion() + " (Implementing API version " + Bukkit.getBukkitVersion() + ")", NamedTextColor.WHITE), -+ Component.newline(), -+ msg -+ ); -+ this.versionMessage = Component.text() -+ .append(message) -+ .hoverEvent(Component.text("Click to copy to clipboard", NamedTextColor.WHITE)) -+ .clickEvent(ClickEvent.copyToClipboard(PlainTextComponentSerializer.plainText().serialize(message))) -+ .build(); -+ // Paper end - versionLock.lock(); - try { - hasVersion = true; diff --git a/patches/server/Add-version-history-to-version-command.patch b/patches/server/Add-version-history-to-version-command.patch deleted file mode 100644 index aa4bdedabe..0000000000 --- a/patches/server/Add-version-history-to-version-command.patch +++ /dev/null @@ -1,222 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Kyle Wood -Date: Thu, 1 Mar 2018 19:37:52 -0600 -Subject: [PATCH] Add version history to version command - - -diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -0,0 +0,0 @@ import com.google.gson.*; - import net.kyori.adventure.text.Component; - import net.kyori.adventure.text.event.ClickEvent; - import net.kyori.adventure.text.format.NamedTextColor; -+import net.kyori.adventure.text.format.TextDecoration; -+import net.kyori.adventure.text.TextComponent; - - import javax.annotation.Nonnull; - import javax.annotation.Nullable; -@@ -0,0 +0,0 @@ public class PaperVersionFetcher implements VersionFetcher { - @Override - public Component getVersionMessage(@Nonnull String serverVersion) { - String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); -- return getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); -+ final Component updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); -+ final Component history = getHistory(); -+ -+ return history != null ? TextComponent.ofChildren(updateMessage, Component.newline(), history) : updateMessage; - } - - private static @Nullable String getMinecraftVersion() { -@@ -0,0 +0,0 @@ public class PaperVersionFetcher implements VersionFetcher { - return -1; - } - } -+ -+ @Nullable -+ private Component getHistory() { -+ final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData(); -+ if (data == null) { -+ return null; -+ } -+ -+ final String oldVersion = data.getOldVersion(); -+ if (oldVersion == null) { -+ return null; -+ } -+ -+ return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); -+ } - } -diff --git a/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java -@@ -0,0 +0,0 @@ -+package com.destroystokyo.paper; -+ -+import com.google.common.base.MoreObjects; -+import com.google.gson.Gson; -+import com.google.gson.JsonSyntaxException; -+import java.io.BufferedReader; -+import java.io.BufferedWriter; -+import java.io.IOException; -+import java.nio.charset.StandardCharsets; -+import java.nio.file.Files; -+import java.nio.file.Path; -+import java.nio.file.Paths; -+import java.nio.file.StandardOpenOption; -+import java.util.Objects; -+import java.util.logging.Level; -+import java.util.logging.Logger; -+import org.bukkit.Bukkit; -+ -+import javax.annotation.Nonnull; -+import javax.annotation.Nullable; -+ -+public enum VersionHistoryManager { -+ INSTANCE; -+ -+ private final Gson gson = new Gson(); -+ -+ private final Logger logger = Bukkit.getLogger(); -+ -+ private VersionData currentData = null; -+ -+ VersionHistoryManager() { -+ final Path path = Paths.get("version_history.json"); -+ -+ if (Files.exists(path)) { -+ // Basic file santiy checks -+ if (!Files.isRegularFile(path)) { -+ if (Files.isDirectory(path)) { -+ logger.severe(path + " is a directory, cannot be used for version history"); -+ } else { -+ logger.severe(path + " is not a regular file, cannot be used for version history"); -+ } -+ // We can't continue -+ return; -+ } -+ -+ try (final BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { -+ currentData = gson.fromJson(reader, VersionData.class); -+ } catch (final IOException e) { -+ logger.log(Level.SEVERE, "Failed to read version history file '" + path + "'", e); -+ return; -+ } catch (final JsonSyntaxException e) { -+ logger.log(Level.SEVERE, "Invalid json syntax for file '" + path + "'", e); -+ return; -+ } -+ -+ final String version = Bukkit.getVersion(); -+ if (version == null) { -+ logger.severe("Failed to retrieve current version"); -+ return; -+ } -+ -+ if (currentData == null) { -+ // Empty file -+ currentData = new VersionData(); -+ currentData.setCurrentVersion(version); -+ writeFile(path); -+ return; -+ } -+ -+ if (!version.equals(currentData.getCurrentVersion())) { -+ // The version appears to have changed -+ currentData.setOldVersion(currentData.getCurrentVersion()); -+ currentData.setCurrentVersion(version); -+ writeFile(path); -+ } -+ } else { -+ // File doesn't exist, start fresh -+ currentData = new VersionData(); -+ // oldVersion is null -+ currentData.setCurrentVersion(Bukkit.getVersion()); -+ writeFile(path); -+ } -+ } -+ -+ private void writeFile(@Nonnull final Path path) { -+ try (final BufferedWriter writer = Files.newBufferedWriter( -+ path, -+ StandardCharsets.UTF_8, -+ StandardOpenOption.WRITE, -+ StandardOpenOption.CREATE, -+ StandardOpenOption.TRUNCATE_EXISTING -+ )) { -+ gson.toJson(currentData, writer); -+ } catch (final IOException e) { -+ logger.log(Level.SEVERE, "Failed to write to version history file", e); -+ } -+ } -+ -+ @Nullable -+ public VersionData getVersionData() { -+ return currentData; -+ } -+ -+ public static class VersionData { -+ private String oldVersion; -+ -+ private String currentVersion; -+ -+ @Nullable -+ public String getOldVersion() { -+ return oldVersion; -+ } -+ -+ public void setOldVersion(@Nullable String oldVersion) { -+ this.oldVersion = oldVersion; -+ } -+ -+ @Nullable -+ public String getCurrentVersion() { -+ return currentVersion; -+ } -+ -+ public void setCurrentVersion(@Nullable String currentVersion) { -+ this.currentVersion = currentVersion; -+ } -+ -+ @Override -+ public String toString() { -+ return MoreObjects.toStringHelper(this) -+ .add("oldVersion", oldVersion) -+ .add("currentVersion", currentVersion) -+ .toString(); -+ } -+ -+ @Override -+ public boolean equals(@Nullable Object o) { -+ if (this == o) { -+ return true; -+ } -+ if (o == null || getClass() != o.getClass()) { -+ return false; -+ } -+ final VersionData versionData = (VersionData) o; -+ return Objects.equals(oldVersion, versionData.oldVersion) && -+ Objects.equals(currentVersion, versionData.currentVersion); -+ } -+ -+ @Override -+ public int hashCode() { -+ return Objects.hash(oldVersion, currentVersion); -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -0,0 +0,0 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - // Paper end - initialize global and world-defaults configuration - io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command - com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics -+ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now - - this.setPvpAllowed(dedicatedserverproperties.pvp); - this.setFlightAllowed(dedicatedserverproperties.allowFlight); diff --git a/patches/server/Basic-PlayerProfile-API.patch b/patches/server/Basic-PlayerProfile-API.patch index bdaad058b4..26f6ad0e2f 100644 --- a/patches/server/Basic-PlayerProfile-API.patch +++ b/patches/server/Basic-PlayerProfile-API.patch @@ -634,7 +634,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import javax.annotation.Nonnull; // Paper + public final class CraftServer implements Server { - private final String serverName = "Paper"; // Paper + private final String serverName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper private final String serverVersion; @@ -0,0 +0,0 @@ public final class CraftServer implements Server { static { diff --git a/patches/server/Expose-game-version.patch b/patches/server/Expose-game-version.patch deleted file mode 100644 index 4203a49b18..0000000000 --- a/patches/server/Expose-game-version.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mark Vainomaa -Date: Fri, 1 May 2020 17:39:26 +0300 -Subject: [PATCH] Expose game version - - -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 { - return this.bukkitVersion; - } - -+ // Paper start - expose game version -+ @Override -+ public String getMinecraftVersion() { -+ return console.getServerVersion(); -+ } -+ // Paper end -+ - @Override - public List getOnlinePlayers() { - return this.playerView; diff --git a/patches/server/Expose-server-build-information.patch b/patches/server/Expose-server-build-information.patch new file mode 100644 index 0000000000..d8e55aad30 --- /dev/null +++ b/patches/server/Expose-server-build-information.patch @@ -0,0 +1,702 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Zach Brown <1254957+zachbr@users.noreply.github.com> +Date: Tue, 1 Mar 2016 14:32:43 -0600 +Subject: [PATCH] Expose server build information + +Co-authored-by: Zach Brown <1254957+zachbr@users.noreply.github.com> +Co-authored-by: Kyle Wood +Co-authored-by: Mark Vainomaa +Co-authored-by: Riley Park +Co-authored-by: Jake Potrebic +Co-authored-by: masmc05 + +diff --git a/build.gradle.kts b/build.gradle.kts +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -0,0 +0,0 @@ + import io.papermc.paperweight.util.* ++import java.time.Instant + + plugins { + java +@@ -0,0 +0,0 @@ tasks.jar { + + manifest { + val git = Git(rootProject.layout.projectDirectory.path) ++ val mcVersion = rootProject.providers.gradleProperty("mcVersion").get() ++ val build = System.getenv("BUILD_NUMBER") ?: null + val gitHash = git("rev-parse", "--short=7", "HEAD").getText().trim() +- val implementationVersion = System.getenv("BUILD_NUMBER") ?: "\"$gitHash\"" ++ val implementationVersion = "$mcVersion-${build ?: "DEV"}-$gitHash" + val date = git("show", "-s", "--format=%ci", gitHash).getText().trim() // Paper + val gitBranch = git("rev-parse", "--abbrev-ref", "HEAD").getText().trim() // Paper + attributes( + "Main-Class" to "org.bukkit.craftbukkit.Main", +- "Implementation-Title" to "CraftBukkit", +- "Implementation-Version" to "git-Paper-$implementationVersion", ++ "Implementation-Title" to "Paper", ++ "Implementation-Version" to implementationVersion, + "Implementation-Vendor" to date, // Paper +- "Specification-Title" to "Bukkit", ++ "Specification-Title" to "Paper", + "Specification-Version" to project.version, +- "Specification-Vendor" to "Bukkit Team", ++ "Specification-Vendor" to "Paper Team", ++ "Brand-Id" to "papermc:paper", ++ "Brand-Name" to "Paper", ++ "Build-Number" to (build ?: ""), ++ "Build-Time" to Instant.now().toString(), + "Git-Branch" to gitBranch, // Paper + "Git-Commit" to gitHash, // Paper + "CraftBukkit-Package-Version" to paperweight.craftBukkitPackageVersion.get(), // Paper +diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper; ++ ++import com.destroystokyo.paper.util.VersionFetcher; ++import com.google.common.base.Charsets; ++import com.google.common.io.Resources; ++import com.google.gson.Gson; ++import com.google.gson.JsonArray; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonSyntaxException; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.ServerBuildInfo; ++import java.io.BufferedReader; ++import java.io.IOException; ++import java.io.InputStreamReader; ++import java.net.HttpURLConnection; ++import java.net.URI; ++import java.util.Optional; ++import java.util.OptionalInt; ++import java.util.stream.StreamSupport; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.event.ClickEvent; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextDecoration; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.checkerframework.framework.qual.DefaultQualifier; ++import org.slf4j.Logger; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.TextColor.color; ++ ++@DefaultQualifier(NonNull.class) ++public class PaperVersionFetcher implements VersionFetcher { ++ private static final Logger LOGGER = LogUtils.getClassLogger(); ++ private static final int DISTANCE_ERROR = -1; ++ private static final int DISTANCE_UNKNOWN = -2; ++ private static final String DOWNLOAD_PAGE = "https://papermc.io/downloads/paper"; ++ ++ @Override ++ public long getCacheTime() { ++ return 720000; ++ } ++ ++ @Override ++ public Component getVersionMessage(final String serverVersion) { ++ final Component updateMessage; ++ final ServerBuildInfo build = ServerBuildInfo.buildInfo(); ++ if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) { ++ updateMessage = text("You are running a development version without access to version information", color(0xFF5300)); ++ } else { ++ updateMessage = getUpdateStatusMessage("PaperMC/Paper", build); ++ } ++ final @Nullable Component history = this.getHistory(); ++ ++ return history != null ? Component.textOfChildren(updateMessage, Component.newline(), history) : updateMessage; ++ } ++ ++ private static Component getUpdateStatusMessage(final String repo, final ServerBuildInfo build) { ++ int distance = DISTANCE_ERROR; ++ ++ final OptionalInt buildNumber = build.buildNumber(); ++ if (buildNumber.isPresent()) { ++ distance = fetchDistanceFromSiteApi(build, buildNumber.getAsInt()); ++ } else { ++ final Optional gitBranch = build.gitBranch(); ++ final Optional gitCommit = build.gitCommit(); ++ if (gitBranch.isPresent() && gitCommit.isPresent()) { ++ distance = fetchDistanceFromGitHub(repo, gitBranch.get(), gitCommit.get()); ++ } ++ } ++ ++ return switch (distance) { ++ case DISTANCE_ERROR -> text("Error obtaining version information", NamedTextColor.YELLOW); ++ case 0 -> text("You are running the latest version", NamedTextColor.GREEN); ++ case DISTANCE_UNKNOWN -> text("Unknown version", NamedTextColor.YELLOW); ++ default -> text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW) ++ .append(Component.newline()) ++ .append(text("Download the new version at: ") ++ .append(text(DOWNLOAD_PAGE, NamedTextColor.GOLD) ++ .hoverEvent(text("Click to open", NamedTextColor.WHITE)) ++ .clickEvent(ClickEvent.openUrl(DOWNLOAD_PAGE)))); ++ }; ++ } ++ ++ private static int fetchDistanceFromSiteApi(final ServerBuildInfo build, final int jenkinsBuild) { ++ try { ++ try (final BufferedReader reader = Resources.asCharSource( ++ URI.create("https://api.papermc.io/v2/projects/paper/versions/" + build.minecraftVersionId()).toURL(), ++ Charsets.UTF_8 ++ ).openBufferedStream()) { ++ final JsonObject json = new Gson().fromJson(reader, JsonObject.class); ++ final JsonArray builds = json.getAsJsonArray("builds"); ++ final int latest = StreamSupport.stream(builds.spliterator(), false) ++ .mapToInt(JsonElement::getAsInt) ++ .max() ++ .orElseThrow(); ++ return latest - jenkinsBuild; ++ } catch (final JsonSyntaxException ex) { ++ LOGGER.error("Error parsing json from Paper's downloads API", ex); ++ return DISTANCE_ERROR; ++ } ++ } catch (final IOException e) { ++ LOGGER.error("Error while parsing version", e); ++ return DISTANCE_ERROR; ++ } ++ } ++ ++ // Contributed by Techcable in GH-65 ++ private static int fetchDistanceFromGitHub(final String repo, final String branch, final String hash) { ++ try { ++ final HttpURLConnection connection = (HttpURLConnection) URI.create("https://api.github.com/repos/%s/compare/%s...%s".formatted(repo, branch, hash)).toURL().openConnection(); ++ connection.connect(); ++ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return DISTANCE_UNKNOWN; // Unknown commit ++ try (final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) { ++ final JsonObject obj = new Gson().fromJson(reader, JsonObject.class); ++ final String status = obj.get("status").getAsString(); ++ return switch (status) { ++ case "identical" -> 0; ++ case "behind" -> obj.get("behind_by").getAsInt(); ++ default -> DISTANCE_ERROR; ++ }; ++ } catch (final JsonSyntaxException | NumberFormatException e) { ++ LOGGER.error("Error parsing json from GitHub's API", e); ++ return DISTANCE_ERROR; ++ } ++ } catch (final IOException e) { ++ LOGGER.error("Error while parsing version", e); ++ return DISTANCE_ERROR; ++ } ++ } ++ ++ private @Nullable Component getHistory() { ++ final VersionHistoryManager.@Nullable VersionData data = VersionHistoryManager.INSTANCE.getVersionData(); ++ if (data == null) { ++ return null; ++ } ++ ++ final @Nullable String oldVersion = data.getOldVersion(); ++ if (oldVersion == null) { ++ return null; ++ } ++ ++ return text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC); ++ } ++} +diff --git a/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java +@@ -0,0 +0,0 @@ ++package com.destroystokyo.paper; ++ ++import com.google.common.base.MoreObjects; ++import com.google.gson.Gson; ++import com.google.gson.JsonSyntaxException; ++import java.io.BufferedReader; ++import java.io.BufferedWriter; ++import java.io.IOException; ++import java.nio.charset.StandardCharsets; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.nio.file.Paths; ++import java.nio.file.StandardOpenOption; ++import java.util.Objects; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++import org.bukkit.Bukkit; ++ ++import javax.annotation.Nonnull; ++import javax.annotation.Nullable; ++ ++public enum VersionHistoryManager { ++ INSTANCE; ++ ++ private final Gson gson = new Gson(); ++ ++ private final Logger logger = Bukkit.getLogger(); ++ ++ private VersionData currentData = null; ++ ++ VersionHistoryManager() { ++ final Path path = Paths.get("version_history.json"); ++ ++ if (Files.exists(path)) { ++ // Basic file santiy checks ++ if (!Files.isRegularFile(path)) { ++ if (Files.isDirectory(path)) { ++ logger.severe(path + " is a directory, cannot be used for version history"); ++ } else { ++ logger.severe(path + " is not a regular file, cannot be used for version history"); ++ } ++ // We can't continue ++ return; ++ } ++ ++ try (final BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { ++ currentData = gson.fromJson(reader, VersionData.class); ++ } catch (final IOException e) { ++ logger.log(Level.SEVERE, "Failed to read version history file '" + path + "'", e); ++ return; ++ } catch (final JsonSyntaxException e) { ++ logger.log(Level.SEVERE, "Invalid json syntax for file '" + path + "'", e); ++ return; ++ } ++ ++ final String version = Bukkit.getVersion(); ++ if (version == null) { ++ logger.severe("Failed to retrieve current version"); ++ return; ++ } ++ ++ if (currentData == null) { ++ // Empty file ++ currentData = new VersionData(); ++ currentData.setCurrentVersion(version); ++ writeFile(path); ++ return; ++ } ++ ++ if (!version.equals(currentData.getCurrentVersion())) { ++ // The version appears to have changed ++ currentData.setOldVersion(currentData.getCurrentVersion()); ++ currentData.setCurrentVersion(version); ++ writeFile(path); ++ } ++ } else { ++ // File doesn't exist, start fresh ++ currentData = new VersionData(); ++ // oldVersion is null ++ currentData.setCurrentVersion(Bukkit.getVersion()); ++ writeFile(path); ++ } ++ } ++ ++ private void writeFile(@Nonnull final Path path) { ++ try (final BufferedWriter writer = Files.newBufferedWriter( ++ path, ++ StandardCharsets.UTF_8, ++ StandardOpenOption.WRITE, ++ StandardOpenOption.CREATE, ++ StandardOpenOption.TRUNCATE_EXISTING ++ )) { ++ gson.toJson(currentData, writer); ++ } catch (final IOException e) { ++ logger.log(Level.SEVERE, "Failed to write to version history file", e); ++ } ++ } ++ ++ @Nullable ++ public VersionData getVersionData() { ++ return currentData; ++ } ++ ++ public static class VersionData { ++ private String oldVersion; ++ ++ private String currentVersion; ++ ++ @Nullable ++ public String getOldVersion() { ++ return oldVersion; ++ } ++ ++ public void setOldVersion(@Nullable String oldVersion) { ++ this.oldVersion = oldVersion; ++ } ++ ++ @Nullable ++ public String getCurrentVersion() { ++ return currentVersion; ++ } ++ ++ public void setCurrentVersion(@Nullable String currentVersion) { ++ this.currentVersion = currentVersion; ++ } ++ ++ @Override ++ public String toString() { ++ return MoreObjects.toStringHelper(this) ++ .add("oldVersion", oldVersion) ++ .add("currentVersion", currentVersion) ++ .toString(); ++ } ++ ++ @Override ++ public boolean equals(@Nullable Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (o == null || getClass() != o.getClass()) { ++ return false; ++ } ++ final VersionData versionData = (VersionData) o; ++ return Objects.equals(oldVersion, versionData.oldVersion) && ++ Objects.equals(currentVersion, versionData.currentVersion); ++ } ++ ++ @Override ++ public int hashCode() { ++ return Objects.hash(oldVersion, currentVersion); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper; ++ ++import com.google.common.base.Strings; ++import io.papermc.paper.util.JarManifests; ++import java.time.Instant; ++import java.time.temporal.ChronoUnit; ++import java.util.Optional; ++import java.util.OptionalInt; ++import java.util.jar.Manifest; ++import net.kyori.adventure.key.Key; ++import net.minecraft.SharedConstants; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.Main; ++import org.jetbrains.annotations.NotNull; ++ ++public record ServerBuildInfoImpl( ++ Key brandId, ++ String brandName, ++ String minecraftVersionId, ++ String minecraftVersionName, ++ OptionalInt buildNumber, ++ Instant buildTime, ++ Optional gitBranch, ++ Optional gitCommit ++) implements ServerBuildInfo { ++ private static final String ATTRIBUTE_BRAND_ID = "Brand-Id"; ++ private static final String ATTRIBUTE_BRAND_NAME = "Brand-Name"; ++ private static final String ATTRIBUTE_BUILD_TIME = "Build-Time"; ++ private static final String ATTRIBUTE_BUILD_NUMBER = "Build-Number"; ++ private static final String ATTRIBUTE_GIT_BRANCH = "Git-Branch"; ++ private static final String ATTRIBUTE_GIT_COMMIT = "Git-Commit"; ++ ++ private static final String BRAND_PAPER_NAME = "Paper"; ++ ++ private static final String BUILD_DEV = "DEV"; ++ ++ public ServerBuildInfoImpl() { ++ this(JarManifests.manifest(CraftServer.class)); ++ } ++ ++ private ServerBuildInfoImpl(final Manifest manifest) { ++ this( ++ getManifestAttribute(manifest, ATTRIBUTE_BRAND_ID) ++ .map(Key::key) ++ .orElse(BRAND_PAPER_ID), ++ getManifestAttribute(manifest, ATTRIBUTE_BRAND_NAME) ++ .orElse(BRAND_PAPER_NAME), ++ SharedConstants.getCurrentVersion().getId(), ++ SharedConstants.getCurrentVersion().getName(), ++ getManifestAttribute(manifest, ATTRIBUTE_BUILD_NUMBER) ++ .map(Integer::parseInt) ++ .map(OptionalInt::of) ++ .orElse(OptionalInt.empty()), ++ getManifestAttribute(manifest, ATTRIBUTE_BUILD_TIME) ++ .map(Instant::parse) ++ .orElse(Main.BOOT_TIME), ++ getManifestAttribute(manifest, ATTRIBUTE_GIT_BRANCH), ++ getManifestAttribute(manifest, ATTRIBUTE_GIT_COMMIT) ++ ); ++ } ++ ++ @Override ++ public boolean isBrandCompatible(final @NotNull Key brandId) { ++ return brandId.equals(this.brandId); ++ } ++ ++ @Override ++ public @NotNull String asString(final @NotNull StringRepresentation representation) { ++ final StringBuilder sb = new StringBuilder(); ++ sb.append(this.minecraftVersionId); ++ sb.append('-'); ++ if (this.buildNumber.isPresent()) { ++ sb.append(this.buildNumber.getAsInt()); ++ } else { ++ sb.append(BUILD_DEV); ++ } ++ final boolean hasGitBranch = this.gitBranch.isPresent(); ++ final boolean hasGitCommit = this.gitCommit.isPresent(); ++ if (hasGitBranch || hasGitCommit) { ++ sb.append('-'); ++ } ++ if (hasGitBranch && representation == StringRepresentation.VERSION_FULL) { ++ sb.append(this.gitBranch.get()); ++ if (hasGitCommit) { ++ sb.append('@'); ++ } ++ } ++ if (hasGitCommit) { ++ sb.append(this.gitCommit.get()); ++ } ++ if (representation == StringRepresentation.VERSION_FULL) { ++ sb.append(' '); ++ sb.append('('); ++ sb.append(this.buildTime.truncatedTo(ChronoUnit.SECONDS)); ++ sb.append(')'); ++ } ++ return sb.toString(); ++ } ++ ++ private static Optional getManifestAttribute(final Manifest manifest, final String name) { ++ final String value = manifest != null ? manifest.getMainAttributes().getValue(name) : null; ++ return Optional.ofNullable(Strings.emptyToNull(value)); ++ } ++} +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -0,0 +0,0 @@ import java.util.Set; + import java.util.UUID; + import java.util.concurrent.CompletableFuture; + import java.util.concurrent.Executor; +-import java.util.concurrent.RejectedExecutionException; + import java.util.concurrent.atomic.AtomicReference; + import java.util.function.BooleanSupplier; + import java.util.function.Consumer; +@@ -0,0 +0,0 @@ import net.minecraft.world.phys.Vec2; + import net.minecraft.world.phys.Vec3; + import org.bukkit.Bukkit; + import org.bukkit.craftbukkit.CraftRegistry; +-import org.bukkit.craftbukkit.CraftServer; +-import org.bukkit.craftbukkit.Main; + import org.bukkit.event.server.ServerLoadEvent; + // CraftBukkit end + +@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop // CraftBukkit - cb > vanilla! ++ return io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper + } + + public SystemReport fillSystemReport(SystemReport details) { +diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java ++++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +@@ -0,0 +0,0 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface + // Paper end - initialize global and world-defaults configuration + io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command + com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics ++ com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now + + this.setPvpAllowed(dedicatedserverproperties.pvp); + this.setFlightAllowed(dedicatedserverproperties.allowFlight); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java b/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java +@@ -0,0 +0,0 @@ public class CraftCrashReport implements Supplier { + + @Override + public String get() { ++ final io.papermc.paper.ServerBuildInfo build = io.papermc.paper.ServerBuildInfo.buildInfo(); // Paper + StringWriter value = new StringWriter(); + try { ++ value.append("\n BrandInfo: ").append(String.format("%s (%s) version %s", build.brandName(), build.brandId(), build.asString(io.papermc.paper.ServerBuildInfo.StringRepresentation.VERSION_FULL))); // Paper + value.append("\n Running: ").append(Bukkit.getName()).append(" version ").append(Bukkit.getVersion()).append(" (Implementing API version ").append(Bukkit.getBukkitVersion()).append(") ").append(String.valueOf(MinecraftServer.getServer().usesAuthentication())); + value.append("\n Plugins: {"); + for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -0,0 +0,0 @@ import com.google.common.collect.MapMaker; + import com.mojang.authlib.GameProfile; + import com.mojang.brigadier.StringReader; + import com.mojang.brigadier.exceptions.CommandSyntaxException; +-import com.mojang.brigadier.tree.CommandNode; +-import com.mojang.brigadier.tree.LiteralCommandNode; + import com.mojang.serialization.Dynamic; + import com.mojang.serialization.Lifecycle; + import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +@@ -0,0 +0,0 @@ import java.net.InetAddress; + import java.util.ArrayList; + import java.util.Collections; + import java.util.Date; +-import java.util.HashMap; + import java.util.HashSet; + import java.util.Iterator; + import java.util.LinkedHashMap; +@@ -0,0 +0,0 @@ import org.bukkit.craftbukkit.ban.CraftProfileBanList; + import org.bukkit.craftbukkit.block.data.CraftBlockData; + import org.bukkit.craftbukkit.boss.CraftBossBar; + import org.bukkit.craftbukkit.boss.CraftKeyedBossbar; +-import org.bukkit.craftbukkit.command.BukkitCommandWrapper; + import org.bukkit.craftbukkit.command.CraftCommandMap; + import org.bukkit.craftbukkit.command.VanillaCommandWrapper; + import org.bukkit.craftbukkit.entity.CraftEntityFactory; +@@ -0,0 +0,0 @@ import org.bukkit.plugin.PluginManager; + import org.bukkit.plugin.ServicesManager; + import org.bukkit.plugin.SimplePluginManager; + import org.bukkit.plugin.SimpleServicesManager; +-import org.bukkit.plugin.java.JavaPluginLoader; + import org.bukkit.plugin.messaging.Messenger; + import org.bukkit.plugin.messaging.StandardMessenger; + import org.bukkit.profile.PlayerProfile; +@@ -0,0 +0,0 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; + import net.md_5.bungee.api.chat.BaseComponent; // Spigot + + public final class CraftServer implements Server { +- private final String serverName = "CraftBukkit"; ++ private final String serverName = io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper + private final String serverVersion; + private final String bukkitVersion = Versioning.getBukkitVersion(); + private final Logger logger = Logger.getLogger("Minecraft"); +@@ -0,0 +0,0 @@ public final class CraftServer implements Server { + return player.getBukkitEntity(); + } + })); +- this.serverVersion = CraftServer.class.getPackage().getImplementationVersion(); ++ this.serverVersion = io.papermc.paper.ServerBuildInfo.buildInfo().asString(io.papermc.paper.ServerBuildInfo.StringRepresentation.VERSION_SIMPLE); // Paper - improve version + this.structureManager = new CraftStructureManager(console.getStructureManager(), console.registryAccess()); + this.dataPackManager = new CraftDataPackManager(this.getServer().getPackRepository()); + this.serverTickManager = new CraftServerTickManager(console.tickRateManager()); +@@ -0,0 +0,0 @@ public final class CraftServer implements Server { + return this.bukkitVersion; + } + ++ // Paper start - expose game version ++ @Override ++ public String getMinecraftVersion() { ++ return console.getServerVersion(); ++ } ++ // Paper end ++ + @Override + public List getOnlinePlayers() { + return this.playerView; +diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/Main.java ++++ b/src/main/java/org/bukkit/craftbukkit/Main.java +@@ -0,0 +0,0 @@ import joptsimple.OptionSet; + import joptsimple.util.PathConverter; + + public class Main { ++ public static final java.time.Instant BOOT_TIME = java.time.Instant.now(); // Paper - track initial start time + public static boolean useJline = true; + public static boolean useConsole = true; + +@@ -0,0 +0,0 @@ public class Main { + deadline.add(Calendar.DAY_OF_YEAR, -3); + if (buildDate.before(deadline.getTime())) { + System.err.println("*** Error, this build is outdated ***"); +- System.err.println("*** Please download a new build as per instructions from https://www.spigotmc.org/go/outdated-spigot ***"); ++ System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads/paper ***"); // Paper + System.err.println("*** Server will start in 20 seconds ***"); + Thread.sleep(TimeUnit.SECONDS.toMillis(20)); + } + } + + System.setProperty("library.jansi.version", "Paper"); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows ++ // Paper start - Log Java and OS versioning to help with debugging plugin issues ++ java.lang.management.RuntimeMXBean runtimeMX = java.lang.management.ManagementFactory.getRuntimeMXBean(); ++ java.lang.management.OperatingSystemMXBean osMX = java.lang.management.ManagementFactory.getOperatingSystemMXBean(); ++ if (runtimeMX != null && osMX != null) { ++ String javaInfo = "Java " + runtimeMX.getSpecVersion() + " (" + runtimeMX.getVmName() + " " + runtimeMX.getVmVersion() + ")"; ++ String osInfo = "Host: " + osMX.getName() + " " + osMX.getVersion() + " (" + osMX.getArch() + ")"; ++ ++ System.out.println("System Info: " + javaInfo + " " + osInfo); ++ } else { ++ System.out.println("Unable to read system info"); ++ } ++ // Paper end - Log Java and OS versioning to help with debugging plugin issues ++ + System.out.println("Loading libraries, please wait..."); + net.minecraft.server.Main.main(options); + } catch (Throwable t) { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues { + public String getTimingsServerName() { + return io.papermc.paper.configuration.GlobalConfiguration.get().timings.serverName; + } ++ ++ @Override ++ public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { ++ return new com.destroystokyo.paper.PaperVersionFetcher(); ++ } + // Paper end + + @Override +diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/spigotmc/WatchdogThread.java ++++ b/src/main/java/org/spigotmc/WatchdogThread.java +@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread + + private WatchdogThread(long timeoutTime, boolean restart) + { +- super( "Spigot Watchdog Thread" ); ++ super( "Paper Watchdog Thread" ); + this.timeoutTime = timeoutTime; + this.restart = restart; + } +@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread + { + Logger log = Bukkit.getServer().getLogger(); + log.log( Level.SEVERE, "------------------------------" ); +- log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Spigot bug." ); ++ log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper + log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); + log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" ); + log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" ); + log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" ); +- log.log( Level.SEVERE, "If you are unsure or still think this is a Spigot bug, please report to https://www.spigotmc.org/" ); ++ log.log( Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" ); + log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" ); +- log.log( Level.SEVERE, "Spigot version: " + Bukkit.getServer().getVersion() ); ++ log.log( Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion() ); + // + if ( net.minecraft.world.level.Level.lastPhysicsProblem != null ) + { +@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread + } + // + log.log( Level.SEVERE, "------------------------------" ); +- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" ); ++ log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper + WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); + log.log( Level.SEVERE, "------------------------------" ); + // +diff --git a/src/main/resources/META-INF/services/io.papermc.paper.ServerBuildInfo b/src/main/resources/META-INF/services/io.papermc.paper.ServerBuildInfo +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/resources/META-INF/services/io.papermc.paper.ServerBuildInfo +@@ -0,0 +1 @@ ++io.papermc.paper.ServerBuildInfoImpl diff --git a/patches/server/Implement-Paper-VersionChecker.patch b/patches/server/Implement-Paper-VersionChecker.patch deleted file mode 100644 index f56ab4c2b5..0000000000 --- a/patches/server/Implement-Paper-VersionChecker.patch +++ /dev/null @@ -1,157 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown <1254957+zachbr@users.noreply.github.com> -Date: Mon, 27 May 2019 03:40:05 -0500 -Subject: [PATCH] Implement Paper VersionChecker - - -diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -0,0 +0,0 @@ -+package com.destroystokyo.paper; -+ -+import com.destroystokyo.paper.util.VersionFetcher; -+import com.google.common.base.Charsets; -+import com.google.common.io.Resources; -+import com.google.gson.*; -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.event.ClickEvent; -+import net.kyori.adventure.text.format.NamedTextColor; -+ -+import javax.annotation.Nonnull; -+import javax.annotation.Nullable; -+import java.io.*; -+import java.net.HttpURLConnection; -+import java.net.URL; -+import java.util.stream.StreamSupport; -+ -+public class PaperVersionFetcher implements VersionFetcher { -+ private static final java.util.regex.Pattern VER_PATTERN = java.util.regex.Pattern.compile("^([0-9\\.]*)\\-.*R"); // R is an anchor, will always give '-R' at end -+ private static final String GITHUB_BRANCH_NAME = "master"; -+ private static final String DOWNLOAD_PAGE = "https://papermc.io/downloads/paper"; -+ private static @Nullable String mcVer; -+ -+ @Override -+ public long getCacheTime() { -+ return 720000; -+ } -+ -+ @Nonnull -+ @Override -+ public Component getVersionMessage(@Nonnull String serverVersion) { -+ String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); -+ return getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); -+ } -+ -+ private static @Nullable String getMinecraftVersion() { -+ if (mcVer == null) { -+ java.util.regex.Matcher matcher = VER_PATTERN.matcher(org.bukkit.Bukkit.getBukkitVersion()); -+ if (matcher.find()) { -+ String result = matcher.group(); -+ mcVer = result.substring(0, result.length() - 2); // strip 'R' anchor and trailing '-' -+ } else { -+ org.bukkit.Bukkit.getLogger().warning("Unable to match version to pattern! Report to PaperMC!"); -+ org.bukkit.Bukkit.getLogger().warning("Pattern: " + VER_PATTERN.toString()); -+ org.bukkit.Bukkit.getLogger().warning("Version: " + org.bukkit.Bukkit.getBukkitVersion()); -+ } -+ } -+ -+ return mcVer; -+ } -+ -+ private static Component getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) { -+ int distance; -+ try { -+ int jenkinsBuild = Integer.parseInt(versionInfo); -+ distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion()); -+ } catch (NumberFormatException ignored) { -+ versionInfo = versionInfo.replace("\"", ""); -+ distance = fetchDistanceFromGitHub(repo, branch, versionInfo); -+ } -+ -+ switch (distance) { -+ case -1: -+ return Component.text("Error obtaining version information", NamedTextColor.YELLOW); -+ case 0: -+ return Component.text("You are running the latest version", NamedTextColor.GREEN); -+ case -2: -+ return Component.text("Unknown version", NamedTextColor.YELLOW); -+ default: -+ return Component.text("You are " + distance + " version(s) behind", NamedTextColor.YELLOW) -+ .append(Component.newline()) -+ .append(Component.text("Download the new version at: ") -+ .append(Component.text(DOWNLOAD_PAGE, NamedTextColor.GOLD) -+ .hoverEvent(Component.text("Click to open", NamedTextColor.WHITE)) -+ .clickEvent(ClickEvent.openUrl(DOWNLOAD_PAGE)))); -+ } -+ } -+ -+ private static int fetchDistanceFromSiteApi(int jenkinsBuild, @Nullable String siteApiVersion) { -+ if (siteApiVersion == null) { return -1; } -+ try { -+ try (BufferedReader reader = Resources.asCharSource( -+ new URL("https://api.papermc.io/v2/projects/paper/versions/" + siteApiVersion), -+ Charsets.UTF_8 -+ ).openBufferedStream()) { -+ JsonObject json = new Gson().fromJson(reader, JsonObject.class); -+ JsonArray builds = json.getAsJsonArray("builds"); -+ int latest = StreamSupport.stream(builds.spliterator(), false) -+ .mapToInt(e -> e.getAsInt()) -+ .max() -+ .getAsInt(); -+ return latest - jenkinsBuild; -+ } catch (JsonSyntaxException ex) { -+ ex.printStackTrace(); -+ return -1; -+ } -+ } catch (IOException e) { -+ e.printStackTrace(); -+ return -1; -+ } -+ } -+ -+ // Contributed by Techcable in GH-65 -+ private static int fetchDistanceFromGitHub(@Nonnull String repo, @Nonnull String branch, @Nonnull String hash) { -+ try { -+ HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + repo + "/compare/" + branch + "..." + hash).openConnection(); -+ connection.connect(); -+ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return -2; // Unknown commit -+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) { -+ JsonObject obj = new Gson().fromJson(reader, JsonObject.class); -+ String status = obj.get("status").getAsString(); -+ switch (status) { -+ case "identical": -+ return 0; -+ case "behind": -+ return obj.get("behind_by").getAsInt(); -+ default: -+ return -1; -+ } -+ } catch (JsonSyntaxException | NumberFormatException e) { -+ e.printStackTrace(); -+ return -1; -+ } -+ } catch (IOException e) { -+ e.printStackTrace(); -+ return -1; -+ } -+ } -+} -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues { - public String getTimingsServerName() { - return io.papermc.paper.configuration.GlobalConfiguration.get().timings.serverName; - } -+ -+ @Override -+ public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { -+ return new com.destroystokyo.paper.PaperVersionFetcher(); -+ } - // Paper end - - @Override diff --git a/patches/server/Show-Paper-in-client-crashes-server-lists-and-Mojang.patch b/patches/server/Show-Paper-in-client-crashes-server-lists-and-Mojang.patch deleted file mode 100644 index 584d682be8..0000000000 --- a/patches/server/Show-Paper-in-client-crashes-server-lists-and-Mojang.patch +++ /dev/null @@ -1,105 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Zach Brown <1254957+zachbr@users.noreply.github.com> -Date: Tue, 1 Mar 2016 14:32:43 -0600 -Subject: [PATCH] Show 'Paper' in client crashes, server lists, and Mojang - stats - - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 extends ReentrantBlockableEventLoop // CraftBukkit - cb > vanilla! -+ return "Paper"; // Paper - } - - public SystemReport fillSystemReport(SystemReport details) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -0,0 +0,0 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; - import net.md_5.bungee.api.chat.BaseComponent; // Spigot - - public final class CraftServer implements Server { -- private final String serverName = "CraftBukkit"; -+ private final String serverName = "Paper"; // Paper - private final String serverVersion; - private final String bukkitVersion = Versioning.getBukkitVersion(); - private final Logger logger = Logger.getLogger("Minecraft"); -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -0,0 +0,0 @@ public class Main { - deadline.add(Calendar.DAY_OF_YEAR, -3); - if (buildDate.before(deadline.getTime())) { - System.err.println("*** Error, this build is outdated ***"); -- System.err.println("*** Please download a new build as per instructions from https://www.spigotmc.org/go/outdated-spigot ***"); -+ System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads/paper ***"); // Paper - System.err.println("*** Server will start in 20 seconds ***"); - Thread.sleep(TimeUnit.SECONDS.toMillis(20)); - } - } - - System.setProperty("library.jansi.version", "Paper"); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows -+ // Paper start - Log Java and OS versioning to help with debugging plugin issues -+ java.lang.management.RuntimeMXBean runtimeMX = java.lang.management.ManagementFactory.getRuntimeMXBean(); -+ java.lang.management.OperatingSystemMXBean osMX = java.lang.management.ManagementFactory.getOperatingSystemMXBean(); -+ if (runtimeMX != null && osMX != null) { -+ String javaInfo = "Java " + runtimeMX.getSpecVersion() + " (" + runtimeMX.getVmName() + " " + runtimeMX.getVmVersion() + ")"; -+ String osInfo = "Host: " + osMX.getName() + " " + osMX.getVersion() + " (" + osMX.getArch() + ")"; -+ -+ System.out.println("System Info: " + javaInfo + " " + osInfo); -+ } else { -+ System.out.println("Unable to read system info"); -+ } -+ // Paper end - Log Java and OS versioning to help with debugging plugin issues -+ - System.out.println("Loading libraries, please wait..."); - net.minecraft.server.Main.main(options); - } catch (Throwable t) { -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread - - private WatchdogThread(long timeoutTime, boolean restart) - { -- super( "Spigot Watchdog Thread" ); -+ super( "Paper Watchdog Thread" ); - this.timeoutTime = timeoutTime; - this.restart = restart; - } -@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread - { - Logger log = Bukkit.getServer().getLogger(); - log.log( Level.SEVERE, "------------------------------" ); -- log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Spigot bug." ); -+ log.log( Level.SEVERE, "The server has stopped responding! This is (probably) not a Paper bug." ); // Paper - log.log( Level.SEVERE, "If you see a plugin in the Server thread dump below, then please report it to that author" ); - log.log( Level.SEVERE, "\t *Especially* if it looks like HTTP or MySQL operations are occurring" ); - log.log( Level.SEVERE, "If you see a world save or edit, then it means you did far more than your server can handle at once" ); - log.log( Level.SEVERE, "\t If this is the case, consider increasing timeout-time in spigot.yml but note that this will replace the crash with LARGE lag spikes" ); -- log.log( Level.SEVERE, "If you are unsure or still think this is a Spigot bug, please report to https://www.spigotmc.org/" ); -+ log.log( Level.SEVERE, "If you are unsure or still think this is a Paper bug, please report this to https://github.com/PaperMC/Paper/issues" ); - log.log( Level.SEVERE, "Be sure to include ALL relevant console errors and Minecraft crash reports" ); -- log.log( Level.SEVERE, "Spigot version: " + Bukkit.getServer().getVersion() ); -+ log.log( Level.SEVERE, "Paper version: " + Bukkit.getServer().getVersion() ); - // - if ( net.minecraft.world.level.Level.lastPhysicsProblem != null ) - { -@@ -0,0 +0,0 @@ public class WatchdogThread extends Thread - } - // - log.log( Level.SEVERE, "------------------------------" ); -- log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Spigot!):" ); -+ log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( MinecraftServer.getServer().serverThread.getId(), Integer.MAX_VALUE ), log ); - log.log( Level.SEVERE, "------------------------------" ); - //