diff --git a/paper-server/build.gradle.kts b/paper-server/build.gradle.kts index 605853668f..d5ad109d2d 100644 --- a/paper-server/build.gradle.kts +++ b/paper-server/build.gradle.kts @@ -1,4 +1,5 @@ import io.papermc.paperweight.util.* +import java.time.Instant plugins { java @@ -80,18 +81,24 @@ 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/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch index a21f9c7f21..d4c482c5e1 100644 --- a/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/MinecraftServer.java.patch @@ -1,6 +1,14 @@ --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java -@@ -84,17 +84,6 @@ +@@ -45,7 +45,6 @@ + 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.concurrent.locks.LockSupport; + import java.util.function.BooleanSupplier; +@@ -84,17 +83,6 @@ import net.minecraft.obfuscate.DontObfuscate; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -18,7 +26,7 @@ import net.minecraft.server.packs.PackType; import net.minecraft.server.packs.repository.Pack; import net.minecraft.server.packs.repository.PackRepository; -@@ -116,6 +105,7 @@ +@@ -116,6 +104,7 @@ import net.minecraft.util.RandomSource; import net.minecraft.util.SignatureValidator; import net.minecraft.util.TimeUtil; @@ -26,7 +34,7 @@ import net.minecraft.util.debugchart.RemoteDebugSampleType; import net.minecraft.util.debugchart.SampleLogger; import net.minecraft.util.debugchart.TpsDebugDimensions; -@@ -156,37 +146,72 @@ +@@ -156,37 +145,70 @@ import net.minecraft.world.level.biome.BiomeManager; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.entity.FuelValues; @@ -89,8 +97,6 @@ -import org.slf4j.Logger; +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 @@ -107,7 +113,7 @@ private static final int OVERLOADED_TICKS_THRESHOLD = 20; private static final long OVERLOADED_WARNING_INTERVAL_NANOS = 10L * TimeUtil.NANOSECONDS_PER_SECOND; private static final int OVERLOADED_TICKS_WARNING_INTERVAL = 100; -@@ -232,8 +257,7 @@ +@@ -232,8 +254,7 @@ private boolean preventProxyConnections; private boolean pvp; private boolean allowFlight; @@ -117,7 +123,7 @@ private int playerIdleTimeout; private final long[] tickTimesNanos; private long aggregatedTickTimesNanos; -@@ -277,6 +301,26 @@ +@@ -277,6 +298,26 @@ private final SuppressedExceptionCollector suppressedExceptions; private final DiscontinuousFrame tickFrame; @@ -144,7 +150,7 @@ public static S spin(Function serverFactory) { AtomicReference atomicreference = new AtomicReference(); Thread thread = new Thread(() -> { -@@ -290,14 +334,14 @@ +@@ -290,14 +331,14 @@ thread.setPriority(8); } @@ -161,7 +167,7 @@ super("Server"); this.metricsRecorder = InactiveMetricsRecorder.INSTANCE; this.onMetricsRecordingStopped = (methodprofilerresults) -> { -@@ -319,36 +363,68 @@ +@@ -319,36 +360,68 @@ this.scoreboard = new ServerScoreboard(this); this.customBossEvents = new CustomBossEvents(); this.suppressedExceptions = new SuppressedExceptionCollector(); @@ -245,7 +251,7 @@ } private void readScoreboard(DimensionDataStorage persistentStateManager) { -@@ -357,7 +433,7 @@ +@@ -357,7 +430,7 @@ protected abstract boolean initServer() throws IOException; @@ -254,7 +260,7 @@ if (!JvmProfiler.INSTANCE.isRunning()) { ; } -@@ -365,12 +441,8 @@ +@@ -365,12 +438,8 @@ boolean flag = false; ProfiledDuration profiledduration = JvmProfiler.INSTANCE.onWorldLoadedStarted(); @@ -268,7 +274,7 @@ if (profiledduration != null) { profiledduration.finish(true); } -@@ -387,23 +459,218 @@ +@@ -387,23 +456,218 @@ protected void forceDifficulty() {} @@ -292,12 +298,12 @@ + Registry dimensions = iregistrycustom_dimension.lookupOrThrow(Registries.LEVEL_STEM); + for (LevelStem worldDimension : dimensions) { + ResourceKey dimensionKey = dimensions.getResourceKey(worldDimension).get(); -+ -+ ServerLevel world; -+ int dimension = 0; - this.readScoreboard(worldpersistentdata); - this.commandStorage = new CommandStorage(worldpersistentdata); ++ ServerLevel world; ++ int dimension = 0; ++ + if (dimensionKey == LevelStem.NETHER) { + if (this.server.getAllowNether()) { + dimension = -1; @@ -501,7 +507,7 @@ if (!iworlddataserver.isInitialized()) { try { -@@ -427,30 +694,8 @@ +@@ -427,30 +691,8 @@ iworlddataserver.setInitialized(true); } @@ -533,7 +539,7 @@ private static void setInitialSpawn(ServerLevel world, ServerLevelData worldProperties, boolean bonusChest, boolean debugWorld) { if (debugWorld) { -@@ -458,6 +703,21 @@ +@@ -458,6 +700,21 @@ } else { ServerChunkCache chunkproviderserver = world.getChunkSource(); ChunkPos chunkcoordintpair = new ChunkPos(chunkproviderserver.randomState().sampler().findSpawnPosition()); @@ -555,7 +561,7 @@ int i = chunkproviderserver.getGenerator().getSpawnHeight(world); if (i < world.getMinY()) { -@@ -516,31 +776,36 @@ +@@ -516,31 +773,36 @@ iworlddataserver.setGameType(GameType.SPECTATOR); } @@ -603,7 +609,7 @@ ForcedChunksSavedData forcedchunk = (ForcedChunksSavedData) worldserver1.getDataStorage().get(ForcedChunksSavedData.factory(), "chunks"); if (forcedchunk != null) { -@@ -555,10 +820,17 @@ +@@ -555,10 +817,17 @@ } } @@ -625,7 +631,7 @@ } public GameType getDefaultGameType() { -@@ -588,12 +860,16 @@ +@@ -588,12 +857,16 @@ worldserver.save((ProgressListener) null, flush, worldserver.noSave && !force); } @@ -644,7 +650,7 @@ if (flush) { Iterator iterator1 = this.getAllLevels().iterator(); -@@ -628,18 +904,41 @@ +@@ -628,18 +901,41 @@ this.stopServer(); } @@ -686,7 +692,7 @@ } MinecraftServer.LOGGER.info("Saving worlds"); -@@ -693,6 +992,15 @@ +@@ -693,6 +989,15 @@ } catch (IOException ioexception1) { MinecraftServer.LOGGER.error("Failed to unlock level {}", this.storageSource.getLevelId(), ioexception1); } @@ -702,15 +708,10 @@ } -@@ -715,10 +1023,68 @@ - this.serverThread.join(); - } catch (InterruptedException interruptedexception) { - MinecraftServer.LOGGER.error("Error while shutting down", interruptedexception); -+ } -+ } -+ -+ } -+ +@@ -720,6 +1025,64 @@ + + } + + // Spigot Start + private static double calcTps(double avg, double exp, double tps) + { @@ -743,9 +744,9 @@ + for (int i = 0; i < size; i++) { + this.samples[i] = dec(TPS); + this.times[i] = SEC_IN_NANO; - } - } - ++ } ++ } ++ + private static java.math.BigDecimal dec(long t) { + return new java.math.BigDecimal(t); + } @@ -764,14 +765,15 @@ + public double getAverage() { + return total.divide(dec(time), 30, java.math.RoundingMode.HALF_UP).doubleValue(); + } - } ++ } + private static final java.math.BigDecimal TPS_BASE = new java.math.BigDecimal(1E9).multiply(new java.math.BigDecimal(SAMPLE_INTERVAL)); + // Paper end + // Spigot End - ++ protected void runServer() { try { -@@ -727,9 +1093,15 @@ + if (!this.initServer()) { +@@ -727,9 +1090,15 @@ } this.nextTickTimeNanos = Util.getNanos(); @@ -788,7 +790,7 @@ while (this.running) { long i; -@@ -744,11 +1116,30 @@ +@@ -744,11 +1113,30 @@ if (j > MinecraftServer.OVERLOADED_THRESHOLD_NANOS + 20L * i && this.nextTickTimeNanos - this.lastOverloadWarningNanos >= MinecraftServer.OVERLOADED_WARNING_INTERVAL_NANOS + 100L * i) { long k = j / i; @@ -819,7 +821,7 @@ boolean flag = i == 0L; -@@ -757,6 +1148,8 @@ +@@ -757,6 +1145,8 @@ this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount); } @@ -828,7 +830,7 @@ this.nextTickTimeNanos += i; try { -@@ -830,6 +1223,13 @@ +@@ -830,6 +1220,13 @@ this.services.profileCache().clearExecutor(); } @@ -842,7 +844,7 @@ this.onServerExit(); } -@@ -889,9 +1289,16 @@ +@@ -889,9 +1286,16 @@ } private boolean haveTime() { @@ -860,7 +862,7 @@ public static boolean throwIfFatalException() { RuntimeException runtimeexception = (RuntimeException) MinecraftServer.fatalException.get(); -@@ -903,7 +1310,7 @@ +@@ -903,7 +1307,7 @@ } public static void setFatalException(RuntimeException exception) { @@ -869,7 +871,7 @@ } @Override -@@ -977,7 +1384,7 @@ +@@ -977,7 +1381,7 @@ } } @@ -878,7 +880,7 @@ Profiler.get().incrementCounter("runTask"); super.doRunTask(ticktask); } -@@ -1025,6 +1432,7 @@ +@@ -1025,6 +1429,7 @@ } public void tickServer(BooleanSupplier shouldKeepTicking) { @@ -886,7 +888,7 @@ long i = Util.getNanos(); int j = this.pauseWhileEmptySeconds() * 20; -@@ -1041,6 +1449,7 @@ +@@ -1041,6 +1446,7 @@ this.autoSave(); } @@ -894,7 +896,7 @@ this.tickConnection(); return; } -@@ -1055,12 +1464,13 @@ +@@ -1055,12 +1461,13 @@ } --this.ticksUntilAutosave; @@ -909,7 +911,7 @@ gameprofilerfiller.push("tallying"); long k = Util.getNanos() - i; int l = this.tickCount % 100; -@@ -1074,7 +1484,7 @@ +@@ -1074,7 +1481,7 @@ } private void autoSave() { @@ -918,7 +920,7 @@ MinecraftServer.LOGGER.debug("Autosave started"); ProfilerFiller gameprofilerfiller = Profiler.get(); -@@ -1123,7 +1533,7 @@ +@@ -1123,7 +1530,7 @@ private ServerStatus buildServerStatus() { ServerStatus.Players serverping_serverpingplayersample = this.buildPlayerStatus(); @@ -927,7 +929,7 @@ } private ServerStatus.Players buildPlayerStatus() { -@@ -1154,11 +1564,27 @@ +@@ -1154,11 +1561,27 @@ this.getPlayerList().getPlayers().forEach((entityplayer) -> { entityplayer.connection.suspendFlushing(); }); @@ -955,7 +957,7 @@ while (iterator.hasNext()) { ServerLevel worldserver = (ServerLevel) iterator.next(); -@@ -1167,11 +1593,13 @@ +@@ -1167,11 +1590,13 @@ return s + " " + String.valueOf(worldserver.dimension().location()); }); @@ -969,7 +971,7 @@ gameprofilerfiller.push("tick"); -@@ -1265,7 +1693,23 @@ +@@ -1265,7 +1690,23 @@ @Nullable public ServerLevel getLevel(ResourceKey key) { return (ServerLevel) this.levels.get(key); @@ -993,16 +995,16 @@ public Set> levelKeys() { return this.levels.keySet(); -@@ -1296,7 +1740,7 @@ +@@ -1296,7 +1737,7 @@ @DontObfuscate public String getServerModName() { - return "vanilla"; -+ return "Spigot"; // Spigot - Spigot > // CraftBukkit - cb > vanilla! ++ return io.papermc.paper.ServerBuildInfo.buildInfo().brandName(); // Paper } public SystemReport fillSystemReport(SystemReport details) { -@@ -1347,7 +1791,7 @@ +@@ -1347,7 +1788,7 @@ @Override public void sendSystemMessage(Component message) { @@ -1011,7 +1013,7 @@ } public KeyPair getKeyPair() { -@@ -1481,10 +1925,20 @@ +@@ -1481,10 +1922,20 @@ @Override public String getMotd() { @@ -1033,7 +1035,7 @@ this.motd = motd; } -@@ -1507,7 +1961,7 @@ +@@ -1507,7 +1958,7 @@ } public ServerConnectionListener getConnection() { @@ -1042,7 +1044,7 @@ } public boolean isReady() { -@@ -1634,11 +2088,11 @@ +@@ -1634,11 +2085,11 @@ public CompletableFuture reloadResources(Collection dataPacks) { CompletableFuture completablefuture = CompletableFuture.supplyAsync(() -> { @@ -1056,7 +1058,7 @@ }, this).thenCompose((immutablelist) -> { MultiPackResourceManager resourcemanager = new MultiPackResourceManager(PackType.SERVER_DATA, immutablelist); List> list = TagLoader.loadTagsForExistingRegistries(resourcemanager, this.registries.compositeAccess()); -@@ -1654,6 +2108,7 @@ +@@ -1654,6 +2105,7 @@ }).thenAcceptAsync((minecraftserver_reloadableresources) -> { this.resources.close(); this.resources = minecraftserver_reloadableresources; @@ -1064,7 +1066,7 @@ this.packRepository.setSelected(dataPacks); WorldDataConfiguration worlddataconfiguration = new WorldDataConfiguration(MinecraftServer.getSelectedPacks(this.packRepository, true), this.worldData.enabledFeatures()); -@@ -1952,7 +2407,7 @@ +@@ -1952,7 +2404,7 @@ final List list = Lists.newArrayList(); final GameRules gamerules = this.getGameRules(); @@ -1073,7 +1075,7 @@ @Override public > void visit(GameRules.Key key, GameRules.Type type) { list.add(String.format(Locale.ROOT, "%s=%s\n", key.getId(), gamerules.getRule(key))); -@@ -2058,7 +2513,7 @@ +@@ -2058,7 +2510,7 @@ try { label51: { @@ -1082,7 +1084,7 @@ try { arraylist = Lists.newArrayList(NativeModuleLister.listModules()); -@@ -2108,6 +2563,22 @@ +@@ -2108,6 +2560,22 @@ } @@ -1105,7 +1107,7 @@ private ProfilerFiller createProfiler() { if (this.willStartRecordingMetrics) { this.metricsRecorder = ActiveMetricsRecorder.createStarted(new ServerMetricsSamplersProvider(Util.timeSource, this.isDedicatedServer()), Util.timeSource, Util.ioPool(), new MetricsPersister("server"), this.onMetricsRecordingStopped, (path) -> { -@@ -2225,18 +2696,24 @@ +@@ -2225,18 +2693,24 @@ } public void logChatMessage(Component message, ChatType.Bound params, @Nullable String prefix) { diff --git a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch index d7f98a278a..9cfcf27676 100644 --- a/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -142,7 +142,7 @@ thread.setDaemon(true); thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(DedicatedServer.LOGGER)); thread.start(); -@@ -126,13 +203,25 @@ +@@ -126,13 +203,26 @@ this.setPreventProxyConnections(dedicatedserverproperties.preventProxyConnections); this.setLocalIp(dedicatedserverproperties.serverIp); } @@ -158,6 +158,7 @@ + // 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); @@ -169,7 +170,7 @@ DedicatedServer.LOGGER.info("Default game type: {}", dedicatedserverproperties.gamemode); InetAddress inetaddress = null; -@@ -156,10 +245,23 @@ +@@ -156,10 +246,23 @@ return false; } @@ -194,7 +195,7 @@ DedicatedServer.LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); } -@@ -170,7 +272,7 @@ +@@ -170,7 +273,7 @@ if (!OldUsersConverter.serverReadyAfterUserconversion(this)) { return false; } else { @@ -203,7 +204,7 @@ this.debugSampleSubscriptionTracker = new DebugSampleSubscriptionTracker(this.getPlayerList()); this.tickTimeLogger = new RemoteSampleLogger(TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME); long i = Util.getNanos(); -@@ -178,13 +280,13 @@ +@@ -178,13 +281,13 @@ SkullBlockEntity.setup(this.services, this); GameProfileCache.setUsesAuthentication(this.usesAuthentication()); DedicatedServer.LOGGER.info("Preparing level \"{}\"", this.getLevelIdName()); @@ -219,7 +220,7 @@ } if (dedicatedserverproperties.enableQuery) { -@@ -197,7 +299,7 @@ +@@ -197,7 +300,7 @@ this.rconThread = RconThread.create(this); } @@ -228,21 +229,20 @@ Thread thread1 = new Thread(new ServerWatchdog(this)); thread1.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(DedicatedServer.LOGGER)); -@@ -213,7 +315,13 @@ - - return true; +@@ -215,6 +318,12 @@ } -+ } -+ + } + + // Paper start + public java.io.File getPluginsFolder() { + return (java.io.File) this.options.valueOf("plugins"); - } ++ } + // Paper end - ++ @Override public boolean isSpawningMonsters() { -@@ -293,6 +401,7 @@ + return this.settings.getProperties().spawnMonsters && super.isSpawningMonsters(); +@@ -293,6 +402,7 @@ this.queryThreadGs4.stop(); } @@ -250,7 +250,7 @@ } @Override -@@ -302,8 +411,8 @@ +@@ -302,8 +412,8 @@ } @Override @@ -261,7 +261,7 @@ } public void handleConsoleInput(String command, CommandSourceStack commandSource) { -@@ -314,7 +423,15 @@ +@@ -314,7 +424,15 @@ while (!this.consoleInput.isEmpty()) { ConsoleInput servercommand = (ConsoleInput) this.consoleInput.remove(0); @@ -278,7 +278,7 @@ } } -@@ -383,7 +500,7 @@ +@@ -383,7 +501,7 @@ @Override public boolean isUnderSpawnProtection(ServerLevel world, BlockPos pos, Player player) { @@ -287,7 +287,7 @@ return false; } else if (this.getPlayerList().getOps().isEmpty()) { return false; -@@ -541,16 +658,52 @@ +@@ -541,16 +659,52 @@ @Override public String getPluginNames() { @@ -344,7 +344,7 @@ } public void storeUsingWhiteList(boolean useWhitelist) { -@@ -660,4 +813,15 @@ +@@ -660,4 +814,15 @@ } } } diff --git a/paper-server/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/paper-server/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java new file mode 100644 index 0000000000..532306cacd --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java @@ -0,0 +1,146 @@ +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/paper-server/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java b/paper-server/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java new file mode 100644 index 0000000000..660b2ec6b6 --- /dev/null +++ b/paper-server/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java @@ -0,0 +1,153 @@ +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/paper-server/src/main/java/io/papermc/paper/PaperBootstrap.java b/paper-server/src/main/java/io/papermc/paper/PaperBootstrap.java new file mode 100644 index 0000000000..d543b1b107 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/PaperBootstrap.java @@ -0,0 +1,55 @@ +package io.papermc.paper; + +import java.util.List; +import joptsimple.OptionSet; +import net.minecraft.SharedConstants; +import net.minecraft.server.Main; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class PaperBootstrap { + private static final Logger LOGGER = LoggerFactory.getLogger("bootstrap"); + + private PaperBootstrap() { + } + + public static void boot(final OptionSet options) { + SharedConstants.tryDetectVersion(); + + getStartupVersionMessages().forEach(LOGGER::info); + + Main.main(options); + } + + private static List getStartupVersionMessages() { + final String javaSpecVersion = System.getProperty("java.specification.version"); + final String javaVmName = System.getProperty("java.vm.name"); + final String javaVmVersion = System.getProperty("java.vm.version"); + final String javaVendor = System.getProperty("java.vendor"); + final String javaVendorVersion = System.getProperty("java.vendor.version"); + final String osName = System.getProperty("os.name"); + final String osVersion = System.getProperty("os.version"); + final String osArch = System.getProperty("os.arch"); + + final ServerBuildInfo bi = ServerBuildInfo.buildInfo(); + return List.of( + String.format( + "Running Java %s (%s %s; %s %s) on %s %s (%s)", + javaSpecVersion, + javaVmName, + javaVmVersion, + javaVendor, + javaVendorVersion, + osName, + osVersion, + osArch + ), + String.format( + "Loading %s %s for Minecraft %s", + bi.brandName(), + bi.asString(ServerBuildInfo.StringRepresentation.VERSION_FULL), + bi.minecraftVersionId() + ) + ); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java b/paper-server/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java new file mode 100644 index 0000000000..790bad0494 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/ServerBuildInfoImpl.java @@ -0,0 +1,104 @@ +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/paper-server/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java index f077b8ff0b..5f11f5b167 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftCrashReport.java @@ -18,8 +18,10 @@ 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/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 16d2b3e59b..e4335bfc98 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -11,8 +11,6 @@ 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; @@ -27,7 +25,6 @@ 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; @@ -155,7 +152,6 @@ 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; @@ -254,7 +250,6 @@ 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; @@ -271,7 +266,7 @@ 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"); @@ -327,7 +322,7 @@ 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()); @@ -609,6 +604,13 @@ 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/paper-server/src/main/java/org/bukkit/craftbukkit/Main.java b/paper-server/src/main/java/org/bukkit/craftbukkit/Main.java index 1c2f232a99..c8171cb146 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/Main.java @@ -15,6 +15,7 @@ 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; @@ -241,7 +242,7 @@ public class Main { deadline.add(Calendar.DAY_OF_YEAR, -2); 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)); } @@ -249,8 +250,9 @@ public class Main { System.setProperty("library.jansi.version", "Paper"); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows System.setProperty("jdk.console", "java.base"); // Paper - revert default console provider back to java.base so we can have our own jline - System.out.println("Loading libraries, please wait..."); - net.minecraft.server.Main.main(options); + //System.out.println("Loading libraries, please wait..."); + //net.minecraft.server.Main.main(options); + io.papermc.paper.PaperBootstrap.boot(options); } catch (Throwable t) { t.printStackTrace(); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java index 07316f0043..e27f10d0d5 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -475,6 +475,13 @@ public final class CraftMagicNumbers implements UnsafeValues { return this.customBiome; } + // Paper start + @Override + public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { + return new com.destroystokyo.paper.PaperVersionFetcher(); + } + // Paper end + /** * This helper class represents the different NBT Tags. *

diff --git a/paper-server/src/main/java/org/spigotmc/WatchdogThread.java b/paper-server/src/main/java/org/spigotmc/WatchdogThread.java index f697d45e0a..e086765dec 100644 --- a/paper-server/src/main/java/org/spigotmc/WatchdogThread.java +++ b/paper-server/src/main/java/org/spigotmc/WatchdogThread.java @@ -19,7 +19,7 @@ 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; } @@ -65,14 +65,14 @@ 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 ) { @@ -82,7 +82,7 @@ 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/paper-server/src/main/resources/META-INF/services/io.papermc.paper.ServerBuildInfo b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.ServerBuildInfo new file mode 100644 index 0000000000..79b4b25784 --- /dev/null +++ b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.ServerBuildInfo @@ -0,0 +1 @@ +io.papermc.paper.ServerBuildInfoImpl