First batch of server patches

This commit is contained in:
Noah van der Aa 2024-10-22 19:28:57 +02:00
parent e080b20c45
commit d280061a1a
No known key found for this signature in database
GPG key ID: 547D90BC6FF753CF
1071 changed files with 730 additions and 652 deletions

View file

@ -1,211 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Kyle Wood <kyle@denwav.dev>
Date: Fri, 11 Jun 2021 05:25:03 -0500
Subject: [PATCH] Remap fixes
diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
index 766006fe7ff965d6ca17df1df457ecc629b3be18..0ef1a7b2a17e81144d594f29f7b5e54d5038dcf4 100644
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -323,9 +323,11 @@ public class BlockPos extends Vec3i {
public static Iterable<BlockPos> withinManhattan(BlockPos center, int rangeX, int rangeY, int rangeZ) {
int i = rangeX + rangeY + rangeZ;
- int j = center.getX();
- int k = center.getY();
- int l = center.getZ();
+ // Paper start - rename variables to fix conflict with anonymous class (remap fix)
+ int centerX = center.getX();
+ int centerY = center.getY();
+ int centerZ = center.getZ();
+ // Paper end
return () -> new AbstractIterator<BlockPos>() {
private final BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
private int currentDepth;
@@ -339,7 +341,7 @@ public class BlockPos extends Vec3i {
protected BlockPos computeNext() {
if (this.zMirror) {
this.zMirror = false;
- this.cursor.setZ(l - (this.cursor.getZ() - l));
+ this.cursor.setZ(centerZ - (this.cursor.getZ() - centerZ)); // Paper - remap fix
return this.cursor;
} else {
BlockPos blockPos;
@@ -365,7 +367,7 @@ public class BlockPos extends Vec3i {
int k = this.currentDepth - Math.abs(i) - Math.abs(j);
if (k <= rangeZ) {
this.zMirror = k != 0;
- blockPos = this.cursor.set(j + i, k + j, l + k);
+ blockPos = this.cursor.set(centerX + i, centerY + j, centerZ + k); // Paper - remap fix
}
}
diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
index 7344cff32fa6fe3dedb74ed98126072c55b0abd2..d98b28e9488a5a7736719cf656736bb026ec8c7e 100644
--- a/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/BehaviorUtils.java
@@ -169,10 +169,10 @@ public class BehaviorUtils {
return optional.map((uuid) -> {
return ((ServerLevel) entity.level()).getEntity(uuid);
- }).map((entity) -> {
+ }).map((entity1) -> { // Paper - remap fix
LivingEntity entityliving1;
- if (entity instanceof LivingEntity entityliving2) {
+ if (entity1 instanceof LivingEntity entityliving2) { // Paper - remap fix
entityliving1 = entityliving2;
} else {
entityliving1 = null;
diff --git a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java
index 9051f0186c09eeb8ecccf62b0116f6da1800a1df..b231f90317fe7df9133674b12d47873520b481cb 100644
--- a/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java
+++ b/src/main/java/net/minecraft/world/level/storage/loot/LootTable.java
@@ -259,8 +259,8 @@ public class LootTable {
public static class Builder implements FunctionUserBuilder<LootTable.Builder> {
- private final Builder<LootPool> pools = ImmutableList.builder();
- private final Builder<LootItemFunction> functions = ImmutableList.builder();
+ private final ImmutableList.Builder<LootPool> pools = ImmutableList.builder();
+ private final ImmutableList.Builder<LootItemFunction> functions = ImmutableList.builder();
private LootContextParamSet paramSet;
private Optional<ResourceLocation> randomSequence;
diff --git a/src/test/java/org/bukkit/DyeColorsTest.java b/src/test/java/org/bukkit/DyeColorsTest.java
index e96d821da0698dd42651500fb97a0856a9e9ce02..fb7d40181abdaa5b2ce607db47c09d0d0a19c86d 100644
--- a/src/test/java/org/bukkit/DyeColorsTest.java
+++ b/src/test/java/org/bukkit/DyeColorsTest.java
@@ -3,7 +3,6 @@ package org.bukkit;
import static org.bukkit.support.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
-import net.minecraft.world.item.DyeColor;
import org.bukkit.support.environment.Normal;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
@@ -15,7 +14,7 @@ public class DyeColorsTest {
@EnumSource(DyeColor.class)
public void checkColor(DyeColor dye) {
Color color = dye.getColor();
- int nmsColorArray = DyeColor.byId(dye.getWoolData()).getTextureDiffuseColor();
+ int nmsColorArray = net.minecraft.world.item.DyeColor.byId(dye.getWoolData()).getTextureDiffuseColor(); // Paper - remap fix
Color nmsColor = Color.fromARGB(nmsColorArray);
assertThat(color, is(nmsColor));
}
@@ -24,7 +23,7 @@ public class DyeColorsTest {
@EnumSource(org.bukkit.DyeColor.class)
public void checkFireworkColor(org.bukkit.DyeColor dye) {
Color color = dye.getFireworkColor();
- int nmsColor = DyeColor.byId(dye.getWoolData()).getFireworkColor();
+ int nmsColor = net.minecraft.world.item.DyeColor.byId(dye.getWoolData()).getFireworkColor(); // Paper - remap fix
assertThat(color, is(Color.fromRGB(nmsColor)));
}
}
diff --git a/src/test/java/org/bukkit/ParticleTest.java b/src/test/java/org/bukkit/ParticleTest.java
index 688c5bae8146f6fc8cddb2632e9cbf304f0745fb..1b0df6220682328b8f1d03416cb8df2f6662a1cf 100644
--- a/src/test/java/org/bukkit/ParticleTest.java
+++ b/src/test/java/org/bukkit/ParticleTest.java
@@ -251,7 +251,7 @@ public class ParticleTest {
Check in CraftParticle if the conversion is still correct.
""", bukkit.getKey()));
- DataResult<Tag> encoded = assertDoesNotThrow(() -> minecraft.codec().codec().encodeStart(DynamicOpsNBT.INSTANCE, particleParam),
+ DataResult<Tag> encoded = assertDoesNotThrow(() -> minecraft.codec().codec().encodeStart(NbtOps.INSTANCE, particleParam), // Paper - remap fix
String.format("""
Could not encoded particle param for particle %s.
This can indicated, that the wrong particle param is created in CraftParticle.
diff --git a/src/test/java/org/bukkit/entity/EntityTypesTest.java b/src/test/java/org/bukkit/entity/EntityTypesTest.java
index 9df52ab0f04758dd04f45f6029fe120ac1a933af..d513d926ddabd61a03172adb846afb7674ed402e 100644
--- a/src/test/java/org/bukkit/entity/EntityTypesTest.java
+++ b/src/test/java/org/bukkit/entity/EntityTypesTest.java
@@ -6,7 +6,6 @@ import java.util.Set;
import java.util.stream.Collectors;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation;
-import net.minecraft.world.entity.EntityType;
import org.bukkit.support.environment.AllFeatures;
import org.junit.jupiter.api.Test;
@@ -17,8 +16,8 @@ public class EntityTypesTest {
public void testMaps() {
Set<EntityType> allBukkit = Arrays.stream(EntityType.values()).filter((b) -> b.getName() != null).collect(Collectors.toSet());
- for (EntityType<?> nms : BuiltInRegistries.ENTITY_TYPE) {
- ResourceLocation key = EntityType.getKey(nms);
+ for (net.minecraft.world.entity.EntityType<?> nms : BuiltInRegistries.ENTITY_TYPE) { // Paper - remap fix
+ ResourceLocation key = net.minecraft.world.entity.EntityType.getKey(nms); // Paper - remap fix
org.bukkit.entity.EntityType bukkit = org.bukkit.entity.EntityType.fromName(key.getPath());
assertNotNull(bukkit, "Missing nms->bukkit " + key);
diff --git a/src/test/java/org/bukkit/entity/PandaGeneTest.java b/src/test/java/org/bukkit/entity/PandaGeneTest.java
index 4a3ac959f0dcc35f80371443383be1f8b42b6d95..e8520f541fda2d1cd9677f3fc7d7d295743f88b2 100644
--- a/src/test/java/org/bukkit/entity/PandaGeneTest.java
+++ b/src/test/java/org/bukkit/entity/PandaGeneTest.java
@@ -2,7 +2,6 @@ package org.bukkit.entity;
import static org.junit.jupiter.api.Assertions.*;
-import net.minecraft.world.entity.animal.Panda;
import org.bukkit.craftbukkit.entity.CraftPanda;
import org.bukkit.support.environment.Normal;
import org.junit.jupiter.api.Test;
@@ -12,8 +11,8 @@ public class PandaGeneTest {
@Test
public void testBukkit() {
- for (Panda.Gene gene : Panda.Gene.values()) {
- Panda.Gene nms = CraftPanda.toNms(gene);
+ for (Panda.Gene gene : Panda.Gene.values()) { // Paper - remap fix
+ net.minecraft.world.entity.animal.Panda.Gene nms = CraftPanda.toNms(gene); // Paper - remap fix
assertNotNull(nms, "NMS gene null for " + gene);
assertEquals(gene.isRecessive(), nms.isRecessive(), "Recessive status did not match " + gene);
@@ -23,7 +22,7 @@ public class PandaGeneTest {
@Test
public void testNMS() {
- for (Panda.Gene gene : Panda.Gene.values()) {
+ for (net.minecraft.world.entity.animal.Panda.Gene gene : net.minecraft.world.entity.animal.Panda.Gene.values()) { // Paper - remap fix
org.bukkit.entity.Panda.Gene bukkit = CraftPanda.fromNms(gene);
assertNotNull(bukkit, "Bukkit gene null for " + gene);
diff --git a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java
index a382ca7c6e0f96e5d8be37fa8139360db13f9c2f..8fb906c77070eedec8cde263154eaf4610ad3197 100644
--- a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java
+++ b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java
@@ -31,17 +31,17 @@ public class RegistryConstantsTest {
@Test
public void testTrimMaterial() {
- this.testExcessConstants(TrimMaterial.class, Registry.TRIM_MATERIAL);
+ this.testExcessConstants(TrimMaterial.class, org.bukkit.Registry.TRIM_MATERIAL); // Paper - remap fix
this.testMissingConstants(TrimMaterial.class, Registries.TRIM_MATERIAL);
}
@Test
public void testTrimPattern() {
- this.testExcessConstants(TrimPattern.class, Registry.TRIM_PATTERN);
+ this.testExcessConstants(TrimPattern.class, org.bukkit.Registry.TRIM_PATTERN); // Paper - remap fix
this.testMissingConstants(TrimPattern.class, Registries.TRIM_PATTERN);
}
- private <T extends Keyed> void testExcessConstants(Class<T> clazz, Registry<T> registry) {
+ private <T extends Keyed> void testExcessConstants(Class<T> clazz, org.bukkit.Registry<T> registry) { // Paper - remap fix
List<NamespacedKey> excessKeys = new ArrayList<>();
for (Field field : clazz.getFields()) {
diff --git a/src/test/java/org/bukkit/registry/RegistryLoadOrderTest.java b/src/test/java/org/bukkit/registry/RegistryLoadOrderTest.java
index 52f057a3b81ae798a8ff354a90e2cc64264c0398..0c704fb9e73229f5632f1f48e59fccc0daf9ec26 100644
--- a/src/test/java/org/bukkit/registry/RegistryLoadOrderTest.java
+++ b/src/test/java/org/bukkit/registry/RegistryLoadOrderTest.java
@@ -25,7 +25,7 @@ public class RegistryLoadOrderTest {
private static boolean initInterface = false;
private static boolean initAbstract = false;
- private static Registry<Keyed> registry;
+ private static org.bukkit.Registry<Keyed> registry; // Paper - remap fix
public static Stream<Arguments> data() {
return Stream.of(

View file

@ -1,185 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zach Brown <zach.brown@destroystokyo.com>
Date: Mon, 29 Feb 2016 20:40:33 -0600
Subject: [PATCH] Build system changes
== AT ==
public net.minecraft.server.packs.VanillaPackResourcesBuilder safeGetPath(Ljava/net/URI;)Ljava/nio/file/Path;
Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>
diff --git a/build.gradle.kts b/build.gradle.kts
index 82b298d454dee6a6d996aa7822dd745e70a8da74..ef88afa5fe914ae3fef9ffb1a971dabf568f75f3 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -8,9 +8,7 @@ plugins {
dependencies {
implementation(project(":paper-api"))
implementation("jline:jline:2.12.1")
- implementation("org.apache.logging.log4j:log4j-iostreams:2.22.1") {
- exclude(group = "org.apache.logging.log4j", module = "log4j-api")
- }
+ implementation("org.apache.logging.log4j:log4j-iostreams:2.22.1") // Paper - remove exclusion
implementation("org.ow2.asm:asm-commons:9.7.1")
implementation("commons-lang:commons-lang:2.6")
runtimeOnly("org.xerial:sqlite-jdbc:3.46.0.0")
@@ -39,6 +37,7 @@ tasks.jar {
val gitHash = git("rev-parse", "--short=7", "HEAD").getText().trim()
val implementationVersion = System.getenv("BUILD_NUMBER") ?: "\"$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",
@@ -47,6 +46,9 @@ tasks.jar {
"Specification-Title" to "Bukkit",
"Specification-Version" to project.version,
"Specification-Vendor" to "Bukkit Team",
+ "Git-Branch" to gitBranch, // Paper
+ "Git-Commit" to gitHash, // Paper
+ "CraftBukkit-Package-Version" to paperweight.craftBukkitPackageVersion.get(), // Paper
)
for (tld in setOf("net", "com", "org")) {
attributes("$tld/bukkit", "Sealed" to true)
@@ -59,6 +61,17 @@ publishing {
}
}
+// Paper start
+val scanJar = tasks.register("scanJarForBadCalls", io.papermc.paperweight.tasks.ScanJarForBadCalls::class) {
+ badAnnotations.add("Lio/papermc/paper/annotation/DoNotUse;")
+ jarToScan.set(tasks.serverJar.flatMap { it.archiveFile })
+ classpath.from(configurations.compileClasspath)
+}
+tasks.check {
+ dependsOn(scanJar)
+}
+// Paper end
+
tasks.test {
include("**/**TestSuite.class")
workingDir = temporaryDir
@@ -128,4 +141,5 @@ tasks.registerRunTask("runReobf") {
tasks.registerRunTask("runDev") {
description = "Spin up a non-relocated Mojang-mapped test server"
classpath(sourceSets.main.map { it.runtimeClasspath })
+ jvmArgs("-DPaper.pushPaperAssetsRoot=true")
}
diff --git a/src/main/java/net/minecraft/resources/ResourceLocation.java b/src/main/java/net/minecraft/resources/ResourceLocation.java
index bfc8f152fa91dff1dcd5fd07fc067e8e5e480002..262660d115a5d5cbecfbae995955a24283e666b0 100644
--- a/src/main/java/net/minecraft/resources/ResourceLocation.java
+++ b/src/main/java/net/minecraft/resources/ResourceLocation.java
@@ -32,6 +32,7 @@ public final class ResourceLocation implements Comparable<ResourceLocation> {
public static final char NAMESPACE_SEPARATOR = ':';
public static final String DEFAULT_NAMESPACE = "minecraft";
public static final String REALMS_NAMESPACE = "realms";
+ public static final String PAPER_NAMESPACE = "paper"; // Paper
private final String namespace;
private final String path;
diff --git a/src/main/java/net/minecraft/server/packs/VanillaPackResourcesBuilder.java b/src/main/java/net/minecraft/server/packs/VanillaPackResourcesBuilder.java
index 14fc03563daea531314c7ceba56dbb47884010ee..fcf95958ef659c7aa8e28026961fa1d6a5f8b28c 100644
--- a/src/main/java/net/minecraft/server/packs/VanillaPackResourcesBuilder.java
+++ b/src/main/java/net/minecraft/server/packs/VanillaPackResourcesBuilder.java
@@ -138,6 +138,15 @@ public class VanillaPackResourcesBuilder {
public VanillaPackResourcesBuilder applyDevelopmentConfig() {
developmentConfig.accept(this);
+ if (Boolean.getBoolean("Paper.pushPaperAssetsRoot")) {
+ try {
+ this.pushAssetPath(net.minecraft.server.packs.PackType.SERVER_DATA, net.minecraft.server.packs.VanillaPackResourcesBuilder.safeGetPath(java.util.Objects.requireNonNull(
+ // Important that this is a patched class
+ VanillaPackResourcesBuilder.class.getResource("/data/.paperassetsroot"), "Missing required .paperassetsroot file").toURI()).getParent());
+ } catch (java.net.URISyntaxException | IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
return this;
}
diff --git a/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java b/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java
index feca36209fd2405fab70f564f63e627b8b78ac18..396ec10a76bdadbf5be2f0e15e88eed47619004d 100644
--- a/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java
+++ b/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java
@@ -48,7 +48,7 @@ public class ServerPacksSource extends BuiltInPackSource {
public static VanillaPackResources createVanillaPackSource() {
return new VanillaPackResourcesBuilder()
.setMetadata(BUILT_IN_METADATA)
- .exposeNamespace("minecraft")
+ .exposeNamespace("minecraft", ResourceLocation.PAPER_NAMESPACE) // Paper
.applyDevelopmentConfig()
.pushJarResources()
.build(VANILLA_PACK_INFO);
@@ -68,7 +68,18 @@ public class ServerPacksSource extends BuiltInPackSource {
@Nullable
@Override
protected Pack createBuiltinPack(String fileName, Pack.ResourcesSupplier packFactory, Component displayName) {
- return Pack.readMetaAndCreate(createBuiltInPackLocation(fileName, displayName), packFactory, PackType.SERVER_DATA, FEATURE_SELECTION_CONFIG);
+ // Paper start - custom built-in pack
+ final PackLocationInfo info;
+ final PackSelectionConfig packConfig;
+ if ("paper".equals(fileName)) {
+ info = new PackLocationInfo(fileName, displayName, PackSource.BUILT_IN, Optional.empty());
+ packConfig = new PackSelectionConfig(true, Pack.Position.TOP, true);
+ } else {
+ info = createBuiltInPackLocation(fileName, displayName);
+ packConfig = FEATURE_SELECTION_CONFIG;
+ }
+ return Pack.readMetaAndCreate(info, packFactory, PackType.SERVER_DATA, packConfig);
+ // Paper end - custom built-in pack
}
public static PackRepository createPackRepository(Path dataPacksPath, DirectoryValidator symlinkFinder) {
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index dd64f5c16560d2ce6255c8319ddd0f5157237f0f..4566237fa1d49a559b2df806f9a318be46a6eade 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -199,7 +199,7 @@ public class Main {
}
if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) {
- Date buildDate = new Date(Integer.parseInt(Main.class.getPackage().getImplementationVendor()) * 1000L);
+ Date buildDate = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(Main.class.getPackage().getImplementationVendor()); // Paper
Calendar deadline = Calendar.getInstance();
deadline.add(Calendar.DAY_OF_YEAR, -28);
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
index 93046379d0cefd5d3236fc59e698809acdc18f80..774556a62eb240da42e84db4502e2ed43495be17 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java
@@ -11,7 +11,7 @@ public final class Versioning {
public static String getBukkitVersion() {
String result = "Unknown-Version";
- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/org.spigotmc/spigot-api/pom.properties");
+ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/io.papermc.paper/paper-api/pom.properties");
Properties properties = new Properties();
if (stream != null) {
diff --git a/src/main/resources/data/.paperassetsroot b/src/main/resources/data/.paperassetsroot
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/src/main/resources/data/minecraft/datapacks/paper/pack.mcmeta b/src/main/resources/data/minecraft/datapacks/paper/pack.mcmeta
new file mode 100644
index 0000000000000000000000000000000000000000..288fbe68c6053f40e72f0feedef0ae0fed10fa67
--- /dev/null
+++ b/src/main/resources/data/minecraft/datapacks/paper/pack.mcmeta
@@ -0,0 +1,6 @@
+{
+ "pack": {
+ "description": "Built-in Paper Datapack",
+ "pack_format": 41
+ }
+}
diff --git a/src/test/java/org/bukkit/support/RegistryHelper.java b/src/test/java/org/bukkit/support/RegistryHelper.java
index e5dbcdcbd4187c724c3c6a3b8141a3796710c7a8..73645ea2e5c17e973ed3b888aab56f733f9c87a3 100644
--- a/src/test/java/org/bukkit/support/RegistryHelper.java
+++ b/src/test/java/org/bukkit/support/RegistryHelper.java
@@ -58,6 +58,7 @@ public final class RegistryHelper {
}
public static void setup(FeatureFlagSet featureFlagSet) {
+ System.setProperty("Paper.pushPaperAssetsRoot", "true"); // Paper - build system changes - push asset root
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();

View file

@ -1,442 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Mon, 13 Feb 2023 14:14:56 -0800
Subject: [PATCH] Test changes
Co-authored-by: yannnicklamprecht <yannicklamprecht@live.de>
diff --git a/build.gradle.kts b/build.gradle.kts
index ef88afa5fe914ae3fef9ffb1a971dabf568f75f3..3c1770864ab3e8708bab6cd50d6ffb5e19af5399 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -23,6 +23,7 @@ dependencies {
testImplementation("org.hamcrest:hamcrest:2.2")
testImplementation("org.mockito:mockito-core:5.14.1")
testImplementation("org.ow2.asm:asm-tree:9.7.1")
+ testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest
}
paperweight {
@@ -56,6 +57,12 @@ tasks.jar {
}
}
+// Paper start - compile tests with -parameters for better junit parameterized test names
+tasks.compileTestJava {
+ options.compilerArgs.add("-parameters")
+}
+// Paper end
+
publishing {
publications.create<MavenPublication>("maven") {
}
diff --git a/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..fe52d229c31526cc32f6422328efe92edf75a7ff
--- /dev/null
+++ b/src/test/java/io/papermc/paper/registry/RegistryKeyTest.java
@@ -0,0 +1,34 @@
+package io.papermc.paper.registry;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+import net.minecraft.core.Registry;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.resources.ResourceLocation;
+import org.bukkit.support.AbstractTestingBase;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@AllFeatures
+class RegistryKeyTest {
+
+ @BeforeAll
+ static void before() throws ClassNotFoundException {
+ Class.forName(RegistryKey.class.getName()); // load all keys so they are found for the test
+ }
+
+ static Stream<RegistryKey<?>> data() {
+ return RegistryKeyImpl.REGISTRY_KEYS.stream();
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ void testApiRegistryKeysExist(final RegistryKey<?> key) {
+ final Optional<Registry<Object>> registry = RegistryHelper.getRegistry().registry(ResourceKey.createRegistryKey(ResourceLocation.parse(key.key().asString())));
+ assertTrue(registry.isPresent(), "Missing vanilla registry for " + key.key().asString());
+
+ }
+}
diff --git a/src/test/java/io/papermc/paper/util/EmptyTag.java b/src/test/java/io/papermc/paper/util/EmptyTag.java
new file mode 100644
index 0000000000000000000000000000000000000000..6eb95a5e2534974c0e52e2b78b04e7c2b2f28525
--- /dev/null
+++ b/src/test/java/io/papermc/paper/util/EmptyTag.java
@@ -0,0 +1,31 @@
+package io.papermc.paper.util;
+
+import java.util.Collections;
+import java.util.Set;
+import org.bukkit.Keyed;
+import org.bukkit.NamespacedKey;
+import org.bukkit.Tag;
+import org.jetbrains.annotations.NotNull;
+
+public record EmptyTag(NamespacedKey key) implements Tag<Keyed> {
+
+ @SuppressWarnings("deprecation")
+ public EmptyTag() {
+ this(NamespacedKey.randomKey());
+ }
+
+ @Override
+ public @NotNull NamespacedKey getKey() {
+ return this.key;
+ }
+
+ @Override
+ public boolean isTagged(@NotNull final Keyed item) {
+ return false;
+ }
+
+ @Override
+ public @NotNull Set<Keyed> getValues() {
+ return Collections.emptySet();
+ }
+}
diff --git a/src/test/java/io/papermc/paper/util/MethodParameterProvider.java b/src/test/java/io/papermc/paper/util/MethodParameterProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..3f58ef36df34cd15fcb72189eeff057654adf0c6
--- /dev/null
+++ b/src/test/java/io/papermc/paper/util/MethodParameterProvider.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2015-2023 the original author or authors of https://github.com/junit-team/junit5/blob/6593317c15fb556febbde11914fa7afe00abf8cd/junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/MethodArgumentsProvider.java
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v2.0 which
+ * accompanies this distribution and is available at
+ *
+ * https://www.eclipse.org/legal/epl-v20.html
+ */
+
+package io.papermc.paper.util;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.api.TestTemplate;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.params.support.AnnotationConsumer;
+import org.junit.platform.commons.JUnitException;
+import org.junit.platform.commons.PreconditionViolationException;
+import org.junit.platform.commons.util.ClassLoaderUtils;
+import org.junit.platform.commons.util.CollectionUtils;
+import org.junit.platform.commons.util.Preconditions;
+import org.junit.platform.commons.util.ReflectionUtils;
+import org.junit.platform.commons.util.StringUtils;
+import org.junitpioneer.jupiter.cartesian.CartesianParameterArgumentsProvider;
+
+import static java.lang.String.format;
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.toList;
+import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated;
+import static org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream;
+
+public class MethodParameterProvider implements CartesianParameterArgumentsProvider<Object>, AnnotationConsumer<MethodParameterSource> {
+ private MethodParameterSource source;
+
+ MethodParameterProvider() {
+ }
+
+ @Override
+ public void accept(final MethodParameterSource source) {
+ this.source = source;
+ }
+
+ @Override
+ public Stream<Object> provideArguments(ExtensionContext context, Parameter parameter) {
+ return this.provideArguments(context, this.source);
+ }
+
+ // Below is mostly copied from MethodArgumentsProvider
+
+ private static final Predicate<Method> isFactoryMethod = //
+ method -> isConvertibleToStream(method.getReturnType()) && !isTestMethod(method);
+
+ protected Stream<Object> provideArguments(ExtensionContext context, MethodParameterSource methodSource) {
+ Class<?> testClass = context.getRequiredTestClass();
+ Method testMethod = context.getRequiredTestMethod();
+ Object testInstance = context.getTestInstance().orElse(null);
+ String[] methodNames = methodSource.value();
+ // @formatter:off
+ return stream(methodNames)
+ .map(factoryMethodName -> findFactoryMethod(testClass, testMethod, factoryMethodName))
+ .map(factoryMethod -> validateFactoryMethod(factoryMethod, testInstance))
+ .map(factoryMethod -> context.getExecutableInvoker().invoke(factoryMethod, testInstance))
+ .flatMap(CollectionUtils::toStream);
+ // @formatter:on
+ }
+
+ private static Method findFactoryMethod(Class<?> testClass, Method testMethod, String factoryMethodName) {
+ String originalFactoryMethodName = factoryMethodName;
+
+ // If the user did not provide a factory method name, find a "default" local
+ // factory method with the same name as the parameterized test method.
+ if (StringUtils.isBlank(factoryMethodName)) {
+ factoryMethodName = testMethod.getName();
+ return findFactoryMethodBySimpleName(testClass, testMethod, factoryMethodName);
+ }
+
+ // Convert local factory method name to fully-qualified method name.
+ if (!looksLikeAFullyQualifiedMethodName(factoryMethodName)) {
+ factoryMethodName = testClass.getName() + "#" + factoryMethodName;
+ }
+
+ // Find factory method using fully-qualified name.
+ Method factoryMethod = findFactoryMethodByFullyQualifiedName(testClass, testMethod, factoryMethodName);
+
+ // Ensure factory method has a valid return type and is not a test method.
+ Preconditions.condition(isFactoryMethod.test(factoryMethod), () -> format(
+ "Could not find valid factory method [%s] for test class [%s] but found the following invalid candidate: %s",
+ originalFactoryMethodName, testClass.getName(), factoryMethod));
+
+ return factoryMethod;
+ }
+
+ private static boolean looksLikeAFullyQualifiedMethodName(String factoryMethodName) {
+ if (factoryMethodName.contains("#")) {
+ return true;
+ }
+ int indexOfFirstDot = factoryMethodName.indexOf('.');
+ if (indexOfFirstDot == -1) {
+ return false;
+ }
+ int indexOfLastOpeningParenthesis = factoryMethodName.lastIndexOf('(');
+ if (indexOfLastOpeningParenthesis > 0) {
+ // Exclude simple/local method names with parameters
+ return indexOfFirstDot < indexOfLastOpeningParenthesis;
+ }
+ // If we get this far, we conclude the supplied factory method name "looks"
+ // like it was intended to be a fully-qualified method name, even if the
+ // syntax is invalid. We do this in order to provide better diagnostics for
+ // the user when a fully-qualified method name is in fact invalid.
+ return true;
+ }
+
+ // package-private for testing
+ static Method findFactoryMethodByFullyQualifiedName(
+ Class<?> testClass, Method testMethod,
+ String fullyQualifiedMethodName
+ ) {
+ String[] methodParts = ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName);
+ String className = methodParts[0];
+ String methodName = methodParts[1];
+ String methodParameters = methodParts[2];
+ ClassLoader classLoader = ClassLoaderUtils.getClassLoader(testClass);
+ Class<?> clazz = loadRequiredClass(className, classLoader);
+
+ // Attempt to find an exact match first.
+ Method factoryMethod = ReflectionUtils.findMethod(clazz, methodName, methodParameters).orElse(null);
+ if (factoryMethod != null) {
+ return factoryMethod;
+ }
+
+ boolean explicitParameterListSpecified = //
+ StringUtils.isNotBlank(methodParameters) || fullyQualifiedMethodName.endsWith("()");
+
+ // If we didn't find an exact match but an explicit parameter list was specified,
+ // that's a user configuration error.
+ Preconditions.condition(!explicitParameterListSpecified,
+ () -> format("Could not find factory method [%s(%s)] in class [%s]", methodName, methodParameters,
+ className));
+
+ // Otherwise, fall back to the same lenient search semantics that are used
+ // to locate a "default" local factory method.
+ return findFactoryMethodBySimpleName(clazz, testMethod, methodName);
+ }
+
+ /**
+ * Find the factory method by searching for all methods in the given {@code clazz}
+ * with the desired {@code factoryMethodName} which have return types that can be
+ * converted to a {@link Stream}, ignoring the {@code testMethod} itself as well
+ * as any {@code @Test}, {@code @TestTemplate}, or {@code @TestFactory} methods
+ * with the same name.
+ *
+ * @return the single factory method matching the search criteria
+ * @throws PreconditionViolationException if the factory method was not found or
+ * multiple competing factory methods with the same name were found
+ */
+ private static Method findFactoryMethodBySimpleName(Class<?> clazz, Method testMethod, String factoryMethodName) {
+ Predicate<Method> isCandidate = candidate -> factoryMethodName.equals(candidate.getName())
+ && !testMethod.equals(candidate);
+ List<Method> candidates = ReflectionUtils.findMethods(clazz, isCandidate);
+
+ List<Method> factoryMethods = candidates.stream().filter(isFactoryMethod).collect(toList());
+
+ Preconditions.condition(factoryMethods.size() > 0, () -> {
+ // If we didn't find the factory method using the isFactoryMethod Predicate, perhaps
+ // the specified factory method has an invalid return type or is a test method.
+ // In that case, we report the invalid candidates that were found.
+ if (candidates.size() > 0) {
+ return format(
+ "Could not find valid factory method [%s] in class [%s] but found the following invalid candidates: %s",
+ factoryMethodName, clazz.getName(), candidates);
+ }
+ // Otherwise, report that we didn't find anything.
+ return format("Could not find factory method [%s] in class [%s]", factoryMethodName, clazz.getName());
+ });
+ Preconditions.condition(factoryMethods.size() == 1,
+ () -> format("%d factory methods named [%s] were found in class [%s]: %s", factoryMethods.size(),
+ factoryMethodName, clazz.getName(), factoryMethods));
+ return factoryMethods.get(0);
+ }
+
+ private static boolean isTestMethod(Method candidate) {
+ return isAnnotated(candidate, Test.class) || isAnnotated(candidate, TestTemplate.class)
+ || isAnnotated(candidate, TestFactory.class);
+ }
+
+ private static Class<?> loadRequiredClass(String className, ClassLoader classLoader) {
+ return ReflectionUtils.tryToLoadClass(className, classLoader).getOrThrow(
+ cause -> new JUnitException(format("Could not load class [%s]", className), cause));
+ }
+
+ private static Method validateFactoryMethod(Method factoryMethod, Object testInstance) {
+ Preconditions.condition(
+ factoryMethod.getDeclaringClass().isInstance(testInstance) || ReflectionUtils.isStatic(factoryMethod),
+ () -> format("Method '%s' must be static: local factory methods must be static "
+ + "unless the PER_CLASS @TestInstance lifecycle mode is used; "
+ + "external factory methods must always be static.",
+ factoryMethod.toGenericString()));
+ return factoryMethod;
+ }
+}
diff --git a/src/test/java/io/papermc/paper/util/MethodParameterSource.java b/src/test/java/io/papermc/paper/util/MethodParameterSource.java
new file mode 100644
index 0000000000000000000000000000000000000000..6cbf11c898439834cffb99ef84e5df1494356809
--- /dev/null
+++ b/src/test/java/io/papermc/paper/util/MethodParameterSource.java
@@ -0,0 +1,14 @@
+package io.papermc.paper.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junitpioneer.jupiter.cartesian.CartesianArgumentsSource;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
+@CartesianArgumentsSource(MethodParameterProvider.class)
+public @interface MethodParameterSource {
+ String[] value() default {};
+}
diff --git a/src/test/java/org/bukkit/ExplosionResultTest.java b/src/test/java/org/bukkit/ExplosionResultTest.java
index ee5ab15bb0bfeb0ff6fa0d720eeff086d92cb459..7419ea2f68607aad27929a608c402bd4c222f95d 100644
--- a/src/test/java/org/bukkit/ExplosionResultTest.java
+++ b/src/test/java/org/bukkit/ExplosionResultTest.java
@@ -5,6 +5,7 @@ import net.minecraft.world.level.Explosion;
import org.bukkit.craftbukkit.CraftExplosionResult;
import org.junit.jupiter.api.Test;
+@org.bukkit.support.environment.Normal // Paper - test changes - missing test suite annotation
public class ExplosionResultTest {
@Test
diff --git a/src/test/java/org/bukkit/registry/RegistryClassTest.java b/src/test/java/org/bukkit/registry/RegistryClassTest.java
index 740073ddd99b5e6aba056b99d5ed3e2a94a13b6c..575a06125e0b60b5bb8b6f85131f7d6cf86f5083 100644
--- a/src/test/java/org/bukkit/registry/RegistryClassTest.java
+++ b/src/test/java/org/bukkit/registry/RegistryClassTest.java
@@ -57,6 +57,7 @@ import org.objectweb.asm.Type;
* Note: This test class assumes that feature flags only enable more features and do not disable vanilla ones.
*/
@AllFeatures
+@org.junit.jupiter.api.Disabled // Paper - disabled for now as it constructs a second root registry, which is not supported on paper
public class RegistryClassTest {
private static final Map<Class<? extends Keyed>, Data> INIT_DATA = new HashMap<>();
diff --git a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java
index 8fb906c77070eedec8cde263154eaf4610ad3197..8705ee2a28d5dba7bb579eae1436451502e1e3c2 100644
--- a/src/test/java/org/bukkit/registry/RegistryConstantsTest.java
+++ b/src/test/java/org/bukkit/registry/RegistryConstantsTest.java
@@ -26,7 +26,7 @@ public class RegistryConstantsTest {
@Test
public void testDamageType() {
this.testExcessConstants(DamageType.class, Registry.DAMAGE_TYPE);
- // this.testMissingConstants(DamageType.class, Registries.DAMAGE_TYPE); // WIND_CHARGE not registered
+ this.testMissingConstants(DamageType.class, Registries.DAMAGE_TYPE); // Paper - re-enable this one
}
@Test
diff --git a/src/test/java/org/bukkit/support/DummyServerHelper.java b/src/test/java/org/bukkit/support/DummyServerHelper.java
index 6ecafd167d4d8c79f2cbc43ea9da98a5d61d8751..f81f86cf05a8c6816d356185af6d0d84a0474dd2 100644
--- a/src/test/java/org/bukkit/support/DummyServerHelper.java
+++ b/src/test/java/org/bukkit/support/DummyServerHelper.java
@@ -47,7 +47,7 @@ public final class DummyServerHelper {
when(instance.getTag(any(), any(), any())).then(mock -> {
String registry = mock.getArgument(0);
Class<?> clazz = mock.getArgument(2);
- MinecraftKey key = CraftNamespacedKey.toMinecraft(mock.getArgument(1));
+ net.minecraft.resources.ResourceLocation key = CraftNamespacedKey.toMinecraft(mock.getArgument(1)); // Paper - address remapping issues
switch (registry) {
case org.bukkit.Tag.REGISTRY_BLOCKS -> {
@@ -66,24 +66,31 @@ public final class DummyServerHelper {
}
case org.bukkit.Tag.REGISTRY_FLUIDS -> {
Preconditions.checkArgument(clazz == org.bukkit.Fluid.class, "Fluid namespace must have fluid type");
- TagKey<FluidType> fluidTagKey = TagKey.create(Registries.FLUID, key);
+ TagKey<net.minecraft.world.level.material.Fluid> fluidTagKey = TagKey.create(Registries.FLUID, key); // Paper - address remapping issues
if (BuiltInRegistries.FLUID.getTag(fluidTagKey).isPresent()) {
return new CraftFluidTag(BuiltInRegistries.FLUID, fluidTagKey);
}
}
case org.bukkit.Tag.REGISTRY_ENTITY_TYPES -> {
Preconditions.checkArgument(clazz == org.bukkit.entity.EntityType.class, "Entity type namespace must have entity type");
- TagKey<EntityTypes<?>> entityTagKey = TagKey.create(Registries.ENTITY_TYPE, key);
+ TagKey<net.minecraft.world.entity.EntityType<?>> entityTagKey = TagKey.create(Registries.ENTITY_TYPE, key); // Paper - address remapping issues
if (BuiltInRegistries.ENTITY_TYPE.getTag(entityTagKey).isPresent()) {
return new CraftEntityTag(BuiltInRegistries.ENTITY_TYPE, entityTagKey);
}
}
- default -> throw new IllegalArgumentException();
+ default -> new io.papermc.paper.util.EmptyTag(); // Paper - testing additions
}
return null;
});
+ // Paper start - testing additions
+ final Thread currentThread = Thread.currentThread();
+ when(instance.isPrimaryThread()).thenAnswer(ignored -> Thread.currentThread().equals(currentThread));
+ final org.bukkit.plugin.PluginManager pluginManager = new org.bukkit.plugin.SimplePluginManager(instance, new org.bukkit.command.SimpleCommandMap(instance));
+ when(instance.getPluginManager()).thenReturn(pluginManager);
+ // Paper end - testing additions
+
return instance;
}
}
diff --git a/src/test/java/org/bukkit/support/RegistryHelper.java b/src/test/java/org/bukkit/support/RegistryHelper.java
index 73645ea2e5c17e973ed3b888aab56f733f9c87a3..4d169ff3541044d447de519d2b0973da98b0bd53 100644
--- a/src/test/java/org/bukkit/support/RegistryHelper.java
+++ b/src/test/java/org/bukkit/support/RegistryHelper.java
@@ -98,6 +98,11 @@ public final class RegistryHelper {
private static LayeredRegistryAccess<RegistryLayer> createLayers(MultiPackResourceManager resourceManager) {
// add tags and loot tables for unit tests
LayeredRegistryAccess<RegistryLayer> layers = RegistryLayer.createRegistryAccess();
+ // Paper start - load registry here to ensure bukkit object registry are correctly delayed if needed
+ try {
+ Class.forName("org.bukkit.Registry");
+ } catch (final ClassNotFoundException ignored) {}
+ // Paper end - load registry here to ensure bukkit object registry are correctly delayed if needed
layers = WorldLoader.loadAndReplaceLayer(resourceManager, layers, RegistryLayer.WORLDGEN, RegistryDataLoader.WORLDGEN_REGISTRIES);
return layers;

File diff suppressed because it is too large Load diff

View file

@ -1,124 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Wed, 30 Mar 2016 19:36:20 -0400
Subject: [PATCH] MC Dev fixes
diff --git a/src/main/java/net/minecraft/commands/arguments/item/ItemInput.java b/src/main/java/net/minecraft/commands/arguments/item/ItemInput.java
index 643bb8860962ad691b11073f6dbf406bf7ec5fb1..9b8ec1fd158f6e51779be263fd56b9119d0d9bbb 100644
--- a/src/main/java/net/minecraft/commands/arguments/item/ItemInput.java
+++ b/src/main/java/net/minecraft/commands/arguments/item/ItemInput.java
@@ -78,6 +78,6 @@ public class ItemInput {
}
private String getItemName() {
- return this.item.unwrapKey().map(ResourceKey::location).orElseGet(() -> "unknown[" + this.item + "]").toString();
+ return this.item.unwrapKey().<Object>map(ResourceKey::location).orElseGet(() -> "unknown[" + this.item + "]").toString(); // Paper - decompile fix
}
}
diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java
index 0ef1a7b2a17e81144d594f29f7b5e54d5038dcf4..8994a381b05dcdd1163d2e7a0b63a8875b6063ed 100644
--- a/src/main/java/net/minecraft/core/BlockPos.java
+++ b/src/main/java/net/minecraft/core/BlockPos.java
@@ -439,12 +439,12 @@ public class BlockPos extends Vec3i {
if (this.index == l) {
return this.endOfData();
} else {
- int i = this.index % i;
- int j = this.index / i;
- int k = j % j;
- int l = j / j;
+ int offsetX = this.index % i; // Paper - decomp fix
+ int u = this.index / i; // Paper - decomp fix
+ int offsetY = u % j; // Paper - decomp fix
+ int offsetZ = u / j; // Paper - decomp fix
this.index++;
- return this.cursor.set(startX + i, startY + k, startZ + l);
+ return this.cursor.set(startX + offsetX, startY + offsetY, startZ + offsetZ); // Paper - decomp fix
}
}
};
diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
index a467eb6b3b74cdb5378ff5f3043efbe6f4a6f06e..34b3b3251da21bce616870d312fd42fd58ba7881 100644
--- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
+++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
@@ -317,7 +317,7 @@ public class BuiltInRegistries {
Bootstrap.checkBootstrapCalled(() -> "registry " + key);
ResourceLocation resourceLocation = key.location();
LOADERS.put(resourceLocation, () -> initializer.run(registry));
- WRITABLE_REGISTRY.register((ResourceKey<WritableRegistry<?>>)key, registry, RegistrationInfo.BUILT_IN);
+ WRITABLE_REGISTRY.register((ResourceKey)key, registry, RegistrationInfo.BUILT_IN); // Paper - decompile fix
return registry;
}
diff --git a/src/main/java/net/minecraft/nbt/TagParser.java b/src/main/java/net/minecraft/nbt/TagParser.java
index a614e960fcd5958ad17b679eee8a8e6926f58e62..da101bca71f4710812621b98f0a0d8cab180346a 100644
--- a/src/main/java/net/minecraft/nbt/TagParser.java
+++ b/src/main/java/net/minecraft/nbt/TagParser.java
@@ -253,11 +253,11 @@ public class TagParser {
}
if (typeReader == ByteTag.TYPE) {
- list.add((T)((NumericTag)tag).getAsByte());
+ list.add((T)(Byte)((NumericTag)tag).getAsByte()); // Paper - decompile fix
} else if (typeReader == LongTag.TYPE) {
- list.add((T)((NumericTag)tag).getAsLong());
+ list.add((T)(Long)((NumericTag)tag).getAsLong()); // Paper - decompile fix
} else {
- list.add((T)((NumericTag)tag).getAsInt());
+ list.add((T)(Integer)((NumericTag)tag).getAsInt()); // Paper - decompile fix
}
if (!this.hasElementSeparator()) {
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 299afe72a2fc2affca196f2fdfbf27e5533c51a6..40adb6117b9e0d5f70103113202a07715e403e2a 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1952,7 +1952,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
PackRepository resourcepackrepository = this.packRepository;
Objects.requireNonNull(this.packRepository);
- return stream.map(resourcepackrepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()); // CraftBukkit - decompile error
+ return stream.<Pack>map(resourcepackrepository::getPack).filter(Objects::nonNull).map(Pack::open).collect(ImmutableList.toImmutableList()); // CraftBukkit - decompile error // Paper - decompile error // todo: is this needed anymore?
}, this).thenCompose((immutablelist) -> {
MultiPackResourceManager resourcemanager = new MultiPackResourceManager(PackType.SERVER_DATA, immutablelist);
diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java
index 661a6274a800ca9b91bdb809d026972d23c3b263..ea72dcb064a35bc6245bc5c94d592efedd8faf41 100644
--- a/src/main/java/net/minecraft/util/SortedArraySet.java
+++ b/src/main/java/net/minecraft/util/SortedArraySet.java
@@ -28,7 +28,7 @@ public class SortedArraySet<T> extends AbstractSet<T> {
}
public static <T extends Comparable<T>> SortedArraySet<T> create(int initialCapacity) {
- return new SortedArraySet<>(initialCapacity, Comparator.naturalOrder());
+ return new SortedArraySet<>(initialCapacity, Comparator.<T>naturalOrder()); // Paper - decompile fix
}
public static <T> SortedArraySet<T> create(Comparator<T> comparator) {
diff --git a/src/main/java/net/minecraft/world/entity/monster/Pillager.java b/src/main/java/net/minecraft/world/entity/monster/Pillager.java
index 8eb1aca72df0bca292473e90ecb74159db4fe034..4b4dcee6abe7a6db43638d04665125eec560496e 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Pillager.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Pillager.java
@@ -66,7 +66,7 @@ public class Pillager extends AbstractIllager implements CrossbowAttackMob, Inve
protected void registerGoals() {
super.registerGoals();
this.goalSelector.addGoal(0, new FloatGoal(this));
- this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, this, 10.0F));
+ this.goalSelector.addGoal(2, new Raider.HoldGroundAttackGoal(this, 10.0F)); // Paper - decomp fix
this.goalSelector.addGoal(3, new RangedCrossbowAttackGoal<>(this, 1.0D, 8.0F));
this.goalSelector.addGoal(8, new RandomStrollGoal(this, 0.6D));
this.goalSelector.addGoal(9, new LookAtPlayerGoal(this, Player.class, 15.0F, 1.0F));
diff --git a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
index e2b72817857a7a203aae4c9de4e01ba1396dc95b..82fc5133325a127890984d51c1381883759eb444 100644
--- a/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
+++ b/src/main/java/net/minecraft/world/level/chunk/status/ChunkStatusTasks.java
@@ -153,7 +153,7 @@ public class ChunkStatusTasks {
if (protochunk instanceof ImposterProtoChunk) {
chunk1 = ((ImposterProtoChunk) protochunk).getWrapped();
} else {
- chunk1 = new LevelChunk(worldserver, protochunk, (chunk1) -> {
+ chunk1 = new LevelChunk(worldserver, protochunk, ($) -> { // Paper - decompile fix
ChunkStatusTasks.postLoadProtoChunk(worldserver, protochunk.getEntities());
});
generationchunkholder.replaceProtoChunk(new ImposterProtoChunk(chunk1, false));

File diff suppressed because it is too large Load diff

View file

@ -1,142 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Fri, 25 Feb 2022 07:14:48 -0800
Subject: [PATCH] CB fixes
* Missing Level -> LevelStem generic in StructureCheck
Need to use the right for injectDatafixingContext (Spottedleaf)
* Fix summon_entity effect attempting to add incorrect entity (granny)
* Removed incorrect parent perm for `minecraft.debugstick.always` (Machine_Maker)
* Fixed method signature of Marker#addPassenger (Machine_Maker)
* Removed unneeded UOE in CustomWorldChunkManager (extends BiomeSource) (Machine_Maker)
* Honor Server#getLootTable method contract (Machine_Maker)
Co-authored-by: Spottedleaf <Spottedleaf@users.noreply.github.com>
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
index a696a0d168987aaa4e59c471a23eeb48d683c1b2..9d11fcb3df12182ae00ce73f7e30091fd199a341 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -303,7 +303,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
long l = minecraftserver.getWorldData().worldGenOptions().seed();
- this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), minecraftserver.getStructureManager(), resourcekey, chunkgenerator, this.chunkSource.randomState(), this, chunkgenerator.getBiomeSource(), l, datafixer);
+ this.structureCheck = new StructureCheck(this.chunkSource.chunkScanner(), this.registryAccess(), minecraftserver.getStructureManager(), this.getTypeKey(), chunkgenerator, this.chunkSource.randomState(), this, chunkgenerator.getBiomeSource(), l, datafixer); // Paper - Fix missing CB diff
this.structureManager = new StructureManager(this, this.serverLevelData.worldGenOptions(), this.structureCheck); // CraftBukkit
if ((this.dimension() == Level.END && this.dimensionTypeRegistration().is(BuiltinDimensionTypes.END)) || env == org.bukkit.World.Environment.THE_END) { // CraftBukkit - Allow to create EnderDragonBattle in default and custom END
this.dragonFight = new EndDragonFight(this, this.serverLevelData.worldGenOptions().seed(), this.serverLevelData.endDragonFightData()); // CraftBukkit
diff --git a/src/main/java/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java b/src/main/java/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java
index d927357d541cf206bb3019b2fda3473a77b44ec4..4601c7069ba82ccfe87e9dc304b6f3262f7bbfbf 100644
--- a/src/main/java/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java
+++ b/src/main/java/net/minecraft/world/item/enchantment/effects/SummonEntityEffect.java
@@ -54,7 +54,7 @@ public record SummonEntityEffect(HolderSet<EntityType<?>> entityTypes, boolean j
// CraftBukkit start
world.strikeLightning(entity1, (context.itemStack().getItem() == Items.TRIDENT) ? LightningStrikeEvent.Cause.TRIDENT : LightningStrikeEvent.Cause.ENCHANTMENT);
} else {
- world.addFreshEntityWithPassengers(user, CreatureSpawnEvent.SpawnReason.ENCHANTMENT);
+ world.addFreshEntityWithPassengers(entity1, CreatureSpawnEvent.SpawnReason.ENCHANTMENT); // Paper - Fix typo when adding summoned entity
// CraftBukkit end
}
diff --git a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
index 0dc7f88877020bddd5a84db51d349f52b673048e..aae73586265593ee7830fb8dd5c2e3d7560057f0 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/structure/StructureCheck.java
@@ -40,7 +40,7 @@ public class StructureCheck {
private final ChunkScanAccess storageAccess;
private final RegistryAccess registryAccess;
private final StructureTemplateManager structureTemplateManager;
- private final ResourceKey<Level> dimension;
+ private final ResourceKey<net.minecraft.world.level.dimension.LevelStem> dimension; // Paper - fix missing CB diff
private final ChunkGenerator chunkGenerator;
private final RandomState randomState;
private final LevelHeightAccessor heightAccessor;
@@ -54,7 +54,7 @@ public class StructureCheck {
ChunkScanAccess chunkIoWorker,
RegistryAccess registryManager,
StructureTemplateManager structureTemplateManager,
- ResourceKey<Level> worldKey,
+ ResourceKey<net.minecraft.world.level.dimension.LevelStem> worldKey, // Paper - fix missing CB diff
ChunkGenerator chunkGenerator,
RandomState noiseConfig,
LevelHeightAccessor world,
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
index 85c7f3027978b1d7d6c31b7ad21b3377cdda5925..e34deaf398dc6722c3128bdd6b9bc16da2d33bf7 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftLootTable.java
@@ -187,4 +187,11 @@ public class CraftLootTable implements org.bukkit.loot.LootTable {
org.bukkit.loot.LootTable table = (org.bukkit.loot.LootTable) obj;
return table.getKey().equals(this.getKey());
}
+
+ // Paper start - satisfy equals/hashCode contract
+ @Override
+ public int hashCode() {
+ return java.util.Objects.hash(key);
+ }
+ // Paper end
}
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index 0ea2d13ab80559472513c6df362583b8371a9532..13c37384defda4475de584f33d1860a2d53ce05e 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -123,6 +123,7 @@ public class Main {
this.acceptsAll(Main.asList("forceUpgrade"), "Whether to force a world upgrade");
this.acceptsAll(Main.asList("eraseCache"), "Whether to force cache erase during world upgrade");
this.acceptsAll(Main.asList("recreateRegionFiles"), "Whether to recreate region files during world upgrade");
+ this.accepts("safeMode", "Loads level with vanilla datapack only"); // Paper
this.acceptsAll(Main.asList("nogui"), "Disables the graphical console");
this.acceptsAll(Main.asList("nojline"), "Disables jline and emulates the vanilla console");
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
index 905adf97c0d1f0d1c774a6835a5dffcfea884e58..c017ce2ca1bc535795c958a2e509af2adf88efa9 100644
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftScheduler.java
@@ -26,6 +26,7 @@ import org.bukkit.scheduler.BukkitWorker;
/**
* The fundamental concepts for this implementation:
+ * <ul>
* <li>Main thread owns {@link #head} and {@link #currentTick}, but it may be read from any thread</li>
* <li>Main thread exclusively controls {@link #temp} and {@link #pending}.
* They are never to be accessed outside of the main thread; alternatives exist to prevent locking.</li>
@@ -41,6 +42,7 @@ import org.bukkit.scheduler.BukkitWorker;
* <li>Sync tasks are only to be removed from runners on the main thread when coupled with a removal from pending and temp.</li>
* <li>Most of the design in this scheduler relies on queuing special tasks to perform any data changes on the main thread.
* When executed from inside a synchronous method, the scheduler will be updated before next execution by virtue of the frequent {@link #parsePending()} calls.</li>
+ * </ul>
*/
public class CraftScheduler implements BukkitScheduler {
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index a0f228ee83fd3a913666f88381c946f8a6340644..60dc550431fa4641f8c6d964329b0e2698d1fdd6 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -246,7 +246,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
try {
nmsStack.applyComponents(new ItemParser(Commands.createValidationContext(MinecraftServer.getDefaultRegistryAccess())).parse(new StringReader(arguments)).components());
} catch (CommandSyntaxException ex) {
- Logger.getLogger(CraftMagicNumbers.class.getName()).log(Level.SEVERE, null, ex);
+ com.mojang.logging.LogUtils.getClassLogger().error("Exception modifying ItemStack", new Throwable(ex)); // Paper - show stack trace
}
stack.setItemMeta(CraftItemStack.getItemMeta(nmsStack));
diff --git a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java
index 5ac25dab93fd4c9e9533c80d1ca3d93446d7a365..245ad120a36b6defca7e6889faae1ca5fc33d0c7 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/permissions/CraftDefaultPermissions.java
@@ -15,7 +15,7 @@ public final class CraftDefaultPermissions {
DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".nbt.place", "Gives the user the ability to place restricted blocks with NBT in creative", org.bukkit.permissions.PermissionDefault.OP, parent);
DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".nbt.copy", "Gives the user the ability to copy NBT in creative", org.bukkit.permissions.PermissionDefault.TRUE, parent);
DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick", "Gives the user the ability to use the debug stick in creative", org.bukkit.permissions.PermissionDefault.OP, parent);
- DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick.always", "Gives the user the ability to use the debug stick in all game modes", org.bukkit.permissions.PermissionDefault.FALSE, parent);
+ DefaultPermissions.registerPermission(CraftDefaultPermissions.ROOT + ".debugstick.always", "Gives the user the ability to use the debug stick in all game modes", org.bukkit.permissions.PermissionDefault.FALSE/* , parent */); // Paper - should not have this parent, as it's not a "vanilla" utility
// Spigot end
parent.recalculatePermissibles();
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,700 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Minecrell <minecrell@minecrell.net>
Date: Fri, 9 Jun 2017 19:03:43 +0200
Subject: [PATCH] Use TerminalConsoleAppender for console improvements
Rewrite console improvements (console colors, tab completion,
persistent input line, ...) using JLine 3.x and TerminalConsoleAppender.
Also uses the new ANSIComponentSerializer to serialize components when
logging them via the ComponentLogger, or when sending messages to the
console, for hex color support.
New features:
- Support console colors for Vanilla commands
- Add console colors for warnings and errors
- Server can now be turned off safely using CTRL + C. JLine catches
the signal and the implementation shuts down the server cleanly.
- Support console colors and persistent input line when running in
IntelliJ IDEA
Other changes:
- Server starts 1-2 seconds faster thanks to optimizations in Log4j
configuration
Co-Authored-By: Emilia Kond <emilia@rymiel.space>
diff --git a/build.gradle.kts b/build.gradle.kts
index c4572c0a6d9695088613d158838f236fe64efb48..83daf789efc4ce90ff54420e040b783569eb5cab 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -5,9 +5,29 @@ plugins {
`maven-publish`
}
+val log4jPlugins = sourceSets.create("log4jPlugins")
+configurations.named(log4jPlugins.compileClasspathConfigurationName) {
+ extendsFrom(configurations.compileClasspath.get())
+}
+val alsoShade: Configuration by configurations.creating
+
dependencies {
implementation(project(":paper-api"))
- implementation("jline:jline:2.12.1")
+ // Paper start
+ implementation("org.jline:jline-terminal-jansi:3.21.0")
+ implementation("net.minecrell:terminalconsoleappender:1.3.0")
+ implementation("net.kyori:adventure-text-serializer-ansi:4.17.0") // Keep in sync with adventureVersion from Paper-API build file
+ /*
+ Required to add the missing Log4j2Plugins.dat file from log4j-core
+ which has been removed by Mojang. Without it, log4j has to classload
+ all its classes to check if they are plugins.
+ Scanning takes about 1-2 seconds so adding this speeds up the server start.
+ */
+ runtimeOnly("org.apache.logging.log4j:log4j-core:2.19.0")
+ log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - Needed to generate meta for our Log4j plugins
+ runtimeOnly(log4jPlugins.output)
+ alsoShade(log4jPlugins.output)
+ // Paper end
implementation("org.apache.logging.log4j:log4j-iostreams:2.22.1") // Paper - remove exclusion
implementation("org.ow2.asm:asm-commons:9.7.1")
implementation("org.spongepowered:configurate-yaml:4.2.0-SNAPSHOT") // Paper - config files
@@ -79,6 +99,19 @@ tasks.check {
dependsOn(scanJar)
}
// Paper end
+// Paper start - use TCA for console improvements
+tasks.serverJar {
+ from(alsoShade.elements.map {
+ it.map { f ->
+ if (f.asFile.isFile) {
+ zipTree(f.asFile)
+ } else {
+ f.asFile
+ }
+ }
+ })
+}
+// Paper end - use TCA for console improvements
tasks.test {
include("**/**TestSuite.class")
diff --git a/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java b/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java
new file mode 100644
index 0000000000000000000000000000000000000000..91547f6e6fe90006713beb2818da634304bdd236
--- /dev/null
+++ b/src/log4jPlugins/java/io/papermc/paper/console/StripANSIConverter.java
@@ -0,0 +1,51 @@
+package io.papermc.paper.console;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.pattern.ConverterKeys;
+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternFormatter;
+import org.apache.logging.log4j.core.pattern.PatternParser;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+@Plugin(name = "stripAnsi", category = PatternConverter.CATEGORY)
+@ConverterKeys({"stripAnsi"})
+public final class StripANSIConverter extends LogEventPatternConverter {
+ final private Pattern ANSI_PATTERN = Pattern.compile("\\e\\[[\\d;]*[^\\d;]");
+
+ private final List<PatternFormatter> formatters;
+
+ private StripANSIConverter(List<PatternFormatter> formatters) {
+ super("stripAnsi", null);
+ this.formatters = formatters;
+ }
+
+ @Override
+ public void format(LogEvent event, StringBuilder toAppendTo) {
+ int start = toAppendTo.length();
+ for (PatternFormatter formatter : formatters) {
+ formatter.format(event, toAppendTo);
+ }
+ String content = toAppendTo.substring(start);
+ content = ANSI_PATTERN.matcher(content).replaceAll("");
+
+ toAppendTo.setLength(start);
+ toAppendTo.append(content);
+ }
+
+ public static StripANSIConverter newInstance(Configuration config, String[] options) {
+ if (options.length != 1) {
+ LOGGER.error("Incorrect number of options on stripAnsi. Expected exactly 1, received " + options.length);
+ return null;
+ }
+
+ PatternParser parser = PatternLayout.createPatternParser(config);
+ List<PatternFormatter> formatters = parser.parse(options[0]);
+ return new StripANSIConverter(formatters);
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/console/PaperConsole.java b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4070b59e261f0f1ac4beec47b11492f4724bf27
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/console/PaperConsole.java
@@ -0,0 +1,41 @@
+package com.destroystokyo.paper.console;
+
+import net.minecraft.server.dedicated.DedicatedServer;
+import net.minecrell.terminalconsole.SimpleTerminalConsole;
+import org.bukkit.craftbukkit.command.ConsoleCommandCompleter;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+
+public final class PaperConsole extends SimpleTerminalConsole {
+
+ private final DedicatedServer server;
+
+ public PaperConsole(DedicatedServer server) {
+ this.server = server;
+ }
+
+ @Override
+ protected LineReader buildReader(LineReaderBuilder builder) {
+ return super.buildReader(builder
+ .appName("Paper")
+ .variable(LineReader.HISTORY_FILE, java.nio.file.Paths.get(".console_history"))
+ .completer(new ConsoleCommandCompleter(this.server))
+ );
+ }
+
+ @Override
+ protected boolean isRunning() {
+ return !this.server.isStopped() && this.server.isRunning();
+ }
+
+ @Override
+ protected void runCommand(String command) {
+ this.server.handleConsoleInput(command, this.server.createCommandSourceStack());
+ }
+
+ @Override
+ protected void shutdown() {
+ this.server.halt(false);
+ }
+
+}
diff --git a/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f07539a82f449ad217e316a7513a1708781fb63
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/console/TerminalConsoleCommandSender.java
@@ -0,0 +1,26 @@
+package com.destroystokyo.paper.console;
+
+import net.kyori.adventure.audience.MessageType;
+import net.kyori.adventure.identity.Identity;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+import org.apache.logging.log4j.LogManager;
+import org.bukkit.craftbukkit.command.CraftConsoleCommandSender;
+
+public class TerminalConsoleCommandSender extends CraftConsoleCommandSender {
+
+ private static final ComponentLogger LOGGER = ComponentLogger.logger(LogManager.getRootLogger().getName());
+
+ @Override
+ public void sendRawMessage(String message) {
+ final Component msg = LegacyComponentSerializer.legacySection().deserialize(message);
+ this.sendMessage(Identity.nil(), msg, MessageType.SYSTEM);
+ }
+
+ @Override
+ public void sendMessage(Identity identity, Component message, MessageType type) {
+ LOGGER.info(message);
+ }
+
+}
diff --git a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java
index dc4837c577676115f0653acc35f55962a432e425..22fe529890f34f66534c01248f654dc911b44c3b 100644
--- a/src/main/java/io/papermc/paper/adventure/PaperAdventure.java
+++ b/src/main/java/io/papermc/paper/adventure/PaperAdventure.java
@@ -32,6 +32,7 @@ import net.kyori.adventure.text.flattener.ComponentFlattener;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.serializer.ComponentSerializer;
+import net.kyori.adventure.text.serializer.ansi.ANSIComponentSerializer;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
@@ -129,6 +130,7 @@ public final class PaperAdventure {
public static final AttributeKey<Locale> LOCALE_ATTRIBUTE = AttributeKey.valueOf("adventure:locale"); // init after FLATTENER because classloading triggered here might create a logger
@Deprecated
public static final PlainComponentSerializer PLAIN = PlainComponentSerializer.builder().flattener(FLATTENER).build();
+ public static final ANSIComponentSerializer ANSI_SERIALIZER = ANSIComponentSerializer.builder().flattener(FLATTENER).build();
public static final Codec<Tag, String, CommandSyntaxException, RuntimeException> NBT_CODEC = new Codec<>() {
@Override
public @NotNull Tag decode(final @NotNull String encoded) throws CommandSyntaxException {
diff --git a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java
index 8323f135d6bf2e1f12525e05094ffa3f2420e7e1..a143ea1e58464a3122fbd8ccafe417bdb3c31c78 100644
--- a/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java
+++ b/src/main/java/io/papermc/paper/adventure/providers/ComponentLoggerProviderImpl.java
@@ -1,9 +1,11 @@
package io.papermc.paper.adventure.providers;
import io.papermc.paper.adventure.PaperAdventure;
+import java.util.Locale;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.kyori.adventure.text.logger.slf4j.ComponentLoggerProvider;
+import net.kyori.adventure.translation.GlobalTranslator;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
@@ -15,6 +17,6 @@ public class ComponentLoggerProviderImpl implements ComponentLoggerProvider {
}
private String serialize(final Component message) {
- return PaperAdventure.asPlain(message, null);
+ return PaperAdventure.ANSI_SERIALIZER.serialize(GlobalTranslator.render(message, Locale.getDefault()));
}
}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 387552877ac82baa3af0da723dbb7c331fad6cf4..b334cfc9b98d96f9bfba1d5b14c03c336d30ff23 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -154,7 +154,7 @@ import com.mojang.serialization.Dynamic;
import com.mojang.serialization.Lifecycle;
import java.io.File;
import java.util.Random;
-import jline.console.ConsoleReader;
+// import jline.console.ConsoleReader; // Paper
import joptsimple.OptionSet;
import net.minecraft.nbt.NbtException;
import net.minecraft.nbt.ReportedNbtException;
@@ -296,7 +296,6 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public org.bukkit.craftbukkit.CraftServer server;
public OptionSet options;
public org.bukkit.command.ConsoleCommandSender console;
- public ConsoleReader reader;
public static int currentTick = (int) (System.currentTimeMillis() / 50);
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
public int autosavePeriod;
@@ -383,7 +382,9 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.options = options;
this.worldLoader = worldLoader;
this.vanillaCommandDispatcher = worldstem.dataPackResources().commands; // CraftBukkit
+ // Paper start - Handled by TerminalConsoleAppender
// Try to see if we're actually running in a terminal, disable jline if not
+ /*
if (System.console() == null && System.getProperty("jline.terminal") == null) {
System.setProperty("jline.terminal", "jline.UnsupportedTerminal");
Main.useJline = false;
@@ -404,6 +405,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
MinecraftServer.LOGGER.warn((String) null, ex);
}
}
+ */
+ // Paper end
Runtime.getRuntime().addShutdownHook(new org.bukkit.craftbukkit.util.ServerShutdownThread(this));
// CraftBukkit end
this.paperConfigurations = services.paperConfigurations(); // Paper - add paper configuration files
@@ -1118,7 +1121,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
org.spigotmc.WatchdogThread.doStop(); // Spigot
// CraftBukkit start - Restore terminal to original settings
try {
- this.reader.getTerminal().restore();
+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
} catch (Exception ignored) {
}
// CraftBukkit end
@@ -1656,7 +1659,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
@Override
public void sendSystemMessage(Component message) {
- MinecraftServer.LOGGER.info(message.getString());
+ MinecraftServer.LOGGER.info(io.papermc.paper.adventure.PaperAdventure.ANSI_SERIALIZER.serialize(io.papermc.paper.adventure.PaperAdventure.asAdventure(message))); // Paper - Log message with colors
}
public KeyPair getKeyPair() {
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index cd26616aba6abd44abc5eb8b01cc96f29248aecd..b41eb920b5665b7a1b7cd9f38955c31eeb350847 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -112,6 +112,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
if (!org.bukkit.craftbukkit.Main.useConsole) {
return;
}
+ // Paper start - Use TerminalConsoleAppender
+ new com.destroystokyo.paper.console.PaperConsole(DedicatedServer.this).start();
+ /*
jline.console.ConsoleReader bufferedreader = DedicatedServer.this.reader;
// MC-33041, SPIGOT-5538: if System.in is not valid due to javaw, then return
@@ -143,7 +146,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
continue;
}
if (s.trim().length() > 0) { // Trim to filter lines which are just spaces
- DedicatedServer.this.handleConsoleInput(s, DedicatedServer.this.createCommandSourceStack());
+ DedicatedServer.this.issueCommand(s, DedicatedServer.this.getServerCommandListener());
}
// CraftBukkit end
}
@@ -151,6 +154,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
DedicatedServer.LOGGER.error("Exception handling console input", ioexception);
}
+ */
+ // Paper end
}
};
@@ -162,6 +167,9 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
}
global.addHandler(new org.bukkit.craftbukkit.util.ForwardLogHandler());
+ // Paper start - Not needed with TerminalConsoleAppender
+ final org.apache.logging.log4j.Logger logger = LogManager.getRootLogger();
+ /*
final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger());
for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) {
if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) {
@@ -172,6 +180,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
TerminalConsoleWriterThread writerThread = new TerminalConsoleWriterThread(System.out, this.reader);
this.reader.setCompletionHandler(new TerminalCompletionHandler(writerThread, this.reader.getCompletionHandler()));
writerThread.start();
+ */
+ // Paper end - Not needed with TerminalConsoleAppender
System.setOut(IoBuilder.forLogger(logger).setLevel(Level.INFO).buildPrintStream());
System.setErr(IoBuilder.forLogger(logger).setLevel(Level.WARN).buildPrintStream());
diff --git a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
index 3d92c61f7781221cfdc0324d11bd0088954e4a68..84a2c6c397604279ba821286f5c3c855e6041400 100644
--- a/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
+++ b/src/main/java/net/minecraft/server/gui/MinecraftServerGui.java
@@ -166,7 +166,7 @@ public class MinecraftServerGui extends JComponent {
this.finalizers.forEach(Runnable::run);
}
- private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})*)?[m|K]"); // CraftBukkit
+ private static final java.util.regex.Pattern ANSI = java.util.regex.Pattern.compile("\\e\\[[\\d;]*[^\\d;]"); // CraftBukkit // Paper
public void print(JTextArea textArea, JScrollPane scrollPane, String message) {
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater(() -> {
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
index e9109526880159e2341cc97b53939ba2bcfaeaf9..9dcfcea63f57f45a5584bb80c34fe445d65849e8 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
@@ -161,8 +161,7 @@ public abstract class PlayerList {
public PlayerList(MinecraftServer server, LayeredRegistryAccess<RegistryLayer> registryManager, PlayerDataStorage saveHandler, int maxPlayers) {
this.cserver = server.server = new CraftServer((DedicatedServer) server, this);
- server.console = org.bukkit.craftbukkit.command.ColouredConsoleSender.getInstance();
- server.reader.addCompleter(new org.bukkit.craftbukkit.command.ConsoleCommandCompleter(server.server));
+ server.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender(); // Paper
// CraftBukkit end
this.bans = new UserBanList(PlayerList.USERBANLIST_FILE);
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 9f7634053ddfc4fc67f89cc99524e9ebd6096f46..752933ceabd7ebe7b1e9f7d41198128f6f2182a6 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -43,7 +43,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
-import jline.console.ConsoleReader;
+// import jline.console.ConsoleReader;
import net.minecraft.advancements.AdvancementHolder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
@@ -1356,9 +1356,13 @@ public final class CraftServer implements Server {
return this.logger;
}
+ // Paper start - JLine update
+ /*
public ConsoleReader getReader() {
return this.console.reader;
}
+ */
+ // Paper end
@Override
public PluginCommand getPluginCommand(String name) {
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index 3b0370256b4d8c96053b340a657c6bcc6be6c9de..bdb8e882ec1efbe5afec9ec5a5df57bf38d4ba2b 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -13,7 +13,6 @@ import java.util.logging.Logger;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.util.PathConverter;
-import org.fusesource.jansi.AnsiConsole;
public class Main {
public static boolean useJline = true;
@@ -196,6 +195,8 @@ public class Main {
}
try {
+ // Paper start - Handled by TerminalConsoleAppender
+ /*
// This trick bypasses Maven Shade's clever rewriting of our getProperty call when using String literals
String jline_UnsupportedTerminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 'U', 'n', 's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd', 'T', 'e', 'r', 'm', 'i', 'n', 'a', 'l'});
String jline_terminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 't', 'e', 'r', 'm', 'i', 'n', 'a', 'l'});
@@ -213,9 +214,18 @@ public class Main {
// This ensures the terminal literal will always match the jline implementation
System.setProperty(jline.TerminalFactory.JLINE_TERMINAL, jline.UnsupportedTerminal.class.getName());
}
+ */
+
+ if (options.has("nojline")) {
+ System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false");
+ useJline = false;
+ }
+ // Paper end
if (options.has("noconsole")) {
Main.useConsole = false;
+ useJline = false; // Paper
+ System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper
}
if (Main.class.getPackage().getImplementationVendor() != null && System.getProperty("IReallyKnowWhatIAmDoingISwear") == null) {
@@ -231,6 +241,7 @@ public class Main {
}
}
+ System.setProperty("library.jansi.version", "Paper"); // Paper - set meaningless jansi version to prevent git builds from crashing on Windows
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/command/ColouredConsoleSender.java b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java
index bcf1c36d07b79520a39643d3a01020a67b1c9ef2..217e7e3b9db04c7fc5f6518f39cc9d3488f9128d 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/ColouredConsoleSender.java
@@ -5,15 +5,13 @@ import java.util.EnumMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import jline.Terminal;
+//import jline.Terminal;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.craftbukkit.CraftServer;
-import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.Ansi.Attribute;
-public class ColouredConsoleSender extends CraftConsoleCommandSender {
+public class ColouredConsoleSender /*extends CraftConsoleCommandSender */{/* // Paper - disable
private final Terminal terminal;
private final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
private final ChatColor[] colors = ChatColor.values();
@@ -93,5 +91,5 @@ public class ColouredConsoleSender extends CraftConsoleCommandSender {
} else {
return new ColouredConsoleSender();
}
- }
+ }*/ // Paper
}
diff --git a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
index 0b4c62387c1093652ac15b64a8703249de4cf088..0b27172073530617a6e0b2b83fe1e9d231653b8d 100644
--- a/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
+++ b/src/main/java/org/bukkit/craftbukkit/command/ConsoleCommandCompleter.java
@@ -4,50 +4,73 @@ import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
-import jline.console.completer.Completer;
+import net.minecraft.server.dedicated.DedicatedServer;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.craftbukkit.util.Waitable;
+
+// Paper start - JLine update
+import org.jline.reader.Candidate;
+import org.jline.reader.Completer;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+// Paper end
import org.bukkit.event.server.TabCompleteEvent;
public class ConsoleCommandCompleter implements Completer {
- private final CraftServer server;
+ private final DedicatedServer server; // Paper - CraftServer -> DedicatedServer
- public ConsoleCommandCompleter(CraftServer server) {
+ public ConsoleCommandCompleter(DedicatedServer server) { // Paper - CraftServer -> DedicatedServer
this.server = server;
}
+ // Paper start - Change method signature for JLine update
@Override
- public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
+ public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
+ final CraftServer server = this.server.server;
+ final String buffer = "/" + line.line();
+ // Paper end
Waitable<List<String>> waitable = new Waitable<List<String>>() {
@Override
protected List<String> evaluate() {
- List<String> offers = ConsoleCommandCompleter.this.server.getCommandMap().tabComplete(ConsoleCommandCompleter.this.server.getConsoleSender(), buffer);
+ List<String> offers = server.getCommandMap().tabComplete(server.getConsoleSender(), buffer); // Paper - Remove "this."
- TabCompleteEvent tabEvent = new TabCompleteEvent(ConsoleCommandCompleter.this.server.getConsoleSender(), buffer, (offers == null) ? Collections.EMPTY_LIST : offers);
- ConsoleCommandCompleter.this.server.getPluginManager().callEvent(tabEvent);
+ TabCompleteEvent tabEvent = new TabCompleteEvent(server.getConsoleSender(), buffer, (offers == null) ? Collections.EMPTY_LIST : offers); // Paper - Remove "this."
+ server.getPluginManager().callEvent(tabEvent); // Paper - Remove "this."
return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions();
}
};
- this.server.getServer().processQueue.add(waitable);
+ server.getServer().processQueue.add(waitable); // Paper - Remove "this."
try {
List<String> offers = waitable.get();
if (offers == null) {
- return cursor;
+ return; // Paper - Method returns void
+ }
+
+ // Paper start - JLine update
+ for (String completion : offers) {
+ if (completion.isEmpty()) {
+ continue;
+ }
+
+ candidates.add(new Candidate(completion));
}
- candidates.addAll(offers);
+ // Paper end
+ // Paper start - JLine handles cursor now
+ /*
final int lastSpace = buffer.lastIndexOf(' ');
if (lastSpace == -1) {
return cursor - buffer.length();
} else {
return cursor - (buffer.length() - lastSpace - 1);
}
+ */
+ // Paper end
} catch (ExecutionException e) {
- this.server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e);
+ server.getLogger().log(Level.WARNING, "Unhandled exception when tab completing", e); // Paper - Remove "this."
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
- return cursor;
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
index 8390f5b5b957b5435efece26507a89756d0a7b3c..c6e8441e299f477ddb22c1ce2618710763978f1a 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/ServerShutdownThread.java
@@ -16,7 +16,7 @@ public class ServerShutdownThread extends Thread {
this.server.close();
} finally {
try {
- this.server.reader.getTerminal().restore();
+ net.minecrell.terminalconsole.TerminalConsoleAppender.close(); // Paper - Use TerminalConsoleAppender
} catch (Exception e) {
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/TerminalCompletionHandler.java b/src/main/java/org/bukkit/craftbukkit/util/TerminalCompletionHandler.java
index 7d1c0810181c51ffa64f9a23b251e05c789870b0..09804513265e6db0c1f3501355335551100a36a8 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/TerminalCompletionHandler.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/TerminalCompletionHandler.java
@@ -4,14 +4,12 @@ import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import jline.console.ConsoleReader;
-import jline.console.completer.CompletionHandler;
/**
* SPIGOT-6705: Make sure we print the display line again on tab completion, so that the user does not get stuck on it
* e.g. The user needs to press y / n to continue
*/
-public class TerminalCompletionHandler implements CompletionHandler {
+public class TerminalCompletionHandler /* implements CompletionHandler */ { /* Paper - comment out whole class
private final TerminalConsoleWriterThread writerThread;
private final CompletionHandler delegate;
@@ -50,4 +48,5 @@ public class TerminalCompletionHandler implements CompletionHandler {
return result;
}
+*/ // Paper end - comment out whole class
}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java
index 2e5101fca081548bb616bc5eaa0134698719b63a..6e4d50a4863d43a2d6a2d574350c74d0819df402 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java
@@ -7,13 +7,9 @@ import java.util.Locale;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
-import jline.console.ConsoleReader;
-import jline.console.completer.CandidateListCompletionHandler;
import org.bukkit.craftbukkit.Main;
-import org.fusesource.jansi.Ansi;
-import org.fusesource.jansi.Ansi.Erase;
-public class TerminalConsoleWriterThread extends Thread {
+public class TerminalConsoleWriterThread /*extends Thread*/ {/* // Paper - Comment out entire class
private final ResourceBundle bundle = ResourceBundle.getBundle(CandidateListCompletionHandler.class.getName(), Locale.getDefault());
private final ConsoleReader reader;
private final OutputStream output;
@@ -70,4 +66,5 @@ public class TerminalConsoleWriterThread extends Thread {
void setCompletion(int completion) {
this.completion = completion;
}
+*/ // Paper - Comment out entire class
}
diff --git a/src/main/resources/log4j2.component.properties b/src/main/resources/log4j2.component.properties
new file mode 100644
index 0000000000000000000000000000000000000000..0694b21465fb9e4164e71862ff24b62241b191f2
--- /dev/null
+++ b/src/main/resources/log4j2.component.properties
@@ -0,0 +1 @@
+log4j.skipJansi=true
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 0ff3f750fb7f684c12080894cd8660f0455c658b..301874c1fe16c52ffa6228d79e6617d746e9a035 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -1,17 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
- <Console name="SysOut" target="SYSTEM_OUT">
- <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n" />
- </Console>
<Queue name="ServerGuiConsole">
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />
</Queue>
- <Queue name="TerminalConsole">
- <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n" />
- </Queue>
+ <TerminalConsole name="TerminalConsole">
+ <PatternLayout pattern="%highlightError{[%d{HH:mm:ss} %level]: %msg%n%xEx}" />
+ </TerminalConsole>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
- <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg{nolookups}%n" />
+ <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %stripAnsi{%msg}%n" />
<Policies>
<TimeBasedTriggeringPolicy />
<OnStartupTriggeringPolicy />
@@ -24,10 +21,9 @@
<filters>
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL" />
</filters>
- <AppenderRef ref="SysOut" level="info"/>
<AppenderRef ref="File"/>
- <AppenderRef ref="ServerGuiConsole" level="info"/>
<AppenderRef ref="TerminalConsole" level="info"/>
+ <AppenderRef ref="ServerGuiConsole" level="info"/>
</Root>
</Loggers>
</Configuration>

View file

@ -1,71 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Minecrell <minecrell@minecrell.net>
Date: Thu, 21 Sep 2017 16:14:55 +0200
Subject: [PATCH] Handle plugin prefixes using Log4J configuration
Display logger name in the console for all loggers except the
root logger, Bukkit's logger ("Minecraft") and Minecraft loggers.
Since plugins now use the plugin name as logger name this will
restore the plugin prefixes without having to prepend them manually
to the log messages.
Logger prefixes are shown by default for all loggers except for
the root logger, the Minecraft/Mojang loggers and the Bukkit loggers.
This may cause additional prefixes to be disabled for plugins bypassing
the plugin logger.
diff --git a/build.gradle.kts b/build.gradle.kts
index 0a05e753ff5e7b1d741c7719524715d7364cac4f..d82d1e90cbda544b3d20edcc13d1cb955c48f731 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -23,7 +23,7 @@ dependencies {
all its classes to check if they are plugins.
Scanning takes about 1-2 seconds so adding this speeds up the server start.
*/
- runtimeOnly("org.apache.logging.log4j:log4j-core:2.19.0")
+ implementation("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - implementation
log4jPlugins.annotationProcessorConfigurationName("org.apache.logging.log4j:log4j-core:2.19.0") // Paper - Needed to generate meta for our Log4j plugins
runtimeOnly(log4jPlugins.output)
alsoShade(log4jPlugins.output)
diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java
index e42677a14ec8e1a42747603fb4112822e326fb70..744edd40128c910c3ad2f3657bde995612e0a1e4 100644
--- a/src/main/java/org/spigotmc/SpigotConfig.java
+++ b/src/main/java/org/spigotmc/SpigotConfig.java
@@ -284,7 +284,7 @@ public class SpigotConfig
private static void playerSample()
{
SpigotConfig.playerSample = SpigotConfig.getInt( "settings.sample-count", 12 );
- System.out.println( "Server Ping Player Sample Count: " + SpigotConfig.playerSample );
+ Bukkit.getLogger().log( Level.INFO, "Server Ping Player Sample Count: {0}", playerSample ); // Paper - Use logger
}
public static int playerShuffle;
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 301874c1fe16c52ffa6228d79e6617d746e9a035..e073707a46397f62bedf1d413f9e5764e77dda6a 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -5,10 +5,22 @@
<PatternLayout pattern="[%d{HH:mm:ss} %level]: %msg{nolookups}%n" />
</Queue>
<TerminalConsole name="TerminalConsole">
- <PatternLayout pattern="%highlightError{[%d{HH:mm:ss} %level]: %msg%n%xEx}" />
+ <PatternLayout>
+ <LoggerNamePatternSelector defaultPattern="%highlightError{[%d{HH:mm:ss} %level]: [%logger] %msg%n%xEx}">
+ <!-- Log root, Minecraft, Mojang and Bukkit loggers without prefix -->
+ <PatternMatch key=",net.minecraft.,Minecraft,com.mojang."
+ pattern="%highlightError{[%d{HH:mm:ss} %level]: %msg%n%xEx}" />
+ </LoggerNamePatternSelector>
+ </PatternLayout>
</TerminalConsole>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
- <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %stripAnsi{%msg}%n" />
+ <PatternLayout>
+ <LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss}] [%t/%level]: [%logger] %stripAnsi{%msg}%n">
+ <!-- Log root, Minecraft, Mojang and Bukkit loggers without prefix -->
+ <PatternMatch key=",net.minecraft.,Minecraft,com.mojang."
+ pattern="[%d{HH:mm:ss}] [%t/%level]: %stripAnsi{%msg}%n" />
+ </LoggerNamePatternSelector>
+ </PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<OnStartupTriggeringPolicy />

View file

@ -1,47 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Minecrell <minecrell@minecrell.net>
Date: Sat, 23 Sep 2017 21:07:20 +0200
Subject: [PATCH] Improve Log4J Configuration / Plugin Loggers
Add full exceptions to log4j to not truncate stack traces
Disable logger prefix for various plugins bypassing the plugin logger
Some plugins bypass the plugin logger and add the plugin prefix
manually to the log message. Since they use other logger names
(e.g. qualified class names) these would now also appear in the
log. Disable the logger prefix for these plugins so the messages
show up correctly.
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index e073707a46397f62bedf1d413f9e5764e77dda6a..ab1caec640128aa90f246e4bbecf5ca275e7982e 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -6,19 +6,21 @@
</Queue>
<TerminalConsole name="TerminalConsole">
<PatternLayout>
- <LoggerNamePatternSelector defaultPattern="%highlightError{[%d{HH:mm:ss} %level]: [%logger] %msg%n%xEx}">
+ <LoggerNamePatternSelector defaultPattern="%highlightError{[%d{HH:mm:ss} %level]: [%logger] %msg%n%xEx{full}}">
<!-- Log root, Minecraft, Mojang and Bukkit loggers without prefix -->
- <PatternMatch key=",net.minecraft.,Minecraft,com.mojang."
- pattern="%highlightError{[%d{HH:mm:ss} %level]: %msg%n%xEx}" />
+ <!-- Disable prefix for various plugins that bypass the plugin logger -->
+ <PatternMatch key=",net.minecraft.,Minecraft,com.mojang.,com.sk89q.,ru.tehkode.,Minecraft.AWE"
+ pattern="%highlightError{[%d{HH:mm:ss} %level]: %msg%n%xEx{full}}" />
</LoggerNamePatternSelector>
</PatternLayout>
</TerminalConsole>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout>
- <LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss}] [%t/%level]: [%logger] %stripAnsi{%msg}%n">
+ <LoggerNamePatternSelector defaultPattern="[%d{HH:mm:ss}] [%t/%level]: [%logger] %stripAnsi{%msg}%n%xEx{full}">
<!-- Log root, Minecraft, Mojang and Bukkit loggers without prefix -->
- <PatternMatch key=",net.minecraft.,Minecraft,com.mojang."
- pattern="[%d{HH:mm:ss}] [%t/%level]: %stripAnsi{%msg}%n" />
+ <!-- Disable prefix for various plugins that bypass the plugin logger -->
+ <PatternMatch key=",net.minecraft.,Minecraft,com.mojang.,com.sk89q.,ru.tehkode.,Minecraft.AWE"
+ pattern="[%d{HH:mm:ss}] [%t/%level]: %stripAnsi{%msg}%n%xEx{full}" />
</LoggerNamePatternSelector>
</PatternLayout>
<Policies>

View file

@ -1,44 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Thu, 12 Aug 2021 04:46:41 -0700
Subject: [PATCH] Use AsyncAppender to keep logging IO off main thread
diff --git a/build.gradle.kts b/build.gradle.kts
index d82d1e90cbda544b3d20edcc13d1cb955c48f731..3bd5c2a2add9b462523beb9dfaf2eb5a00d470b9 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -34,6 +34,7 @@ dependencies {
implementation("commons-lang:commons-lang:2.6")
runtimeOnly("org.xerial:sqlite-jdbc:3.46.0.0")
runtimeOnly("com.mysql:mysql-connector-j:8.4.0")
+ runtimeOnly("com.lmax:disruptor:3.4.4") // Paper
runtimeOnly("org.apache.maven:maven-resolver-provider:3.9.6")
runtimeOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18")
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index ab1caec640128aa90f246e4bbecf5ca275e7982e..18e961a37b2830da6e5dab7aa35116b2f5215898 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -29,15 +29,18 @@
</Policies>
<DefaultRolloverStrategy max="1000"/>
</RollingRandomAccessFile>
+ <Async name="Async">
+ <AppenderRef ref="File"/>
+ <AppenderRef ref="TerminalConsole" level="info"/>
+ <AppenderRef ref="ServerGuiConsole" level="info"/>
+ </Async>
</Appenders>
<Loggers>
<Root level="info">
<filters>
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL" />
</filters>
- <AppenderRef ref="File"/>
- <AppenderRef ref="TerminalConsole" level="info"/>
- <AppenderRef ref="ServerGuiConsole" level="info"/>
+ <AppenderRef ref="Async"/>
</Root>
</Loggers>
</Configuration>

View file

@ -1,581 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Sun, 20 Jun 2021 18:19:09 -0700
Subject: [PATCH] Deobfuscate stacktraces in log messages, crash reports, and
etc.
diff --git a/build.gradle.kts b/build.gradle.kts
index 7c81605648c6ba1360936faae25ef5fc11f0bcb4..4838ca75b9536ce5fd30906b51bdab86789668ec 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -46,6 +46,7 @@ dependencies {
testImplementation("org.mockito:mockito-core:5.14.1")
testImplementation("org.ow2.asm:asm-tree:9.7.1")
testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest
+ implementation("net.neoforged:srgutils:1.0.9") // Paper - mappings handling
}
paperweight {
diff --git a/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java b/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..66b6011ee3684695b2ab9292961c80bf2a420ee9
--- /dev/null
+++ b/src/log4jPlugins/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java
@@ -0,0 +1,66 @@
+package io.papermc.paper.logging;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.checkerframework.checker.nullness.qual.NonNull;
+
+@Plugin(
+ name = "StacktraceDeobfuscatingRewritePolicy",
+ category = Core.CATEGORY_NAME,
+ elementType = "rewritePolicy",
+ printObject = true
+)
+public final class StacktraceDeobfuscatingRewritePolicy implements RewritePolicy {
+ private static final MethodHandle DEOBFUSCATE_THROWABLE;
+
+ static {
+ try {
+ final Class<?> cls = Class.forName("io.papermc.paper.util.StacktraceDeobfuscator");
+ final MethodHandles.Lookup lookup = MethodHandles.lookup();
+ final VarHandle instanceHandle = lookup.findStaticVarHandle(cls, "INSTANCE", cls);
+ final Object deobfuscator = instanceHandle.get();
+ DEOBFUSCATE_THROWABLE = lookup
+ .unreflect(cls.getDeclaredMethod("deobfuscateThrowable", Throwable.class))
+ .bindTo(deobfuscator);
+ } catch (final ReflectiveOperationException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ private StacktraceDeobfuscatingRewritePolicy() {
+ }
+
+ @Override
+ public @NonNull LogEvent rewrite(final @NonNull LogEvent rewrite) {
+ final Throwable thrown = rewrite.getThrown();
+ if (thrown != null) {
+ deobfuscateThrowable(thrown);
+ return new Log4jLogEvent.Builder(rewrite)
+ .setThrownProxy(null)
+ .build();
+ }
+ return rewrite;
+ }
+
+ private static void deobfuscateThrowable(final Throwable thrown) {
+ try {
+ DEOBFUSCATE_THROWABLE.invoke(thrown);
+ } catch (final Error e) {
+ throw e;
+ } catch (final Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @PluginFactory
+ public static @NonNull StacktraceDeobfuscatingRewritePolicy createPolicy() {
+ return new StacktraceDeobfuscatingRewritePolicy();
+ }
+}
diff --git a/src/main/java/io/papermc/paper/util/ObfHelper.java b/src/main/java/io/papermc/paper/util/ObfHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e6d48335b37fa5204bfebf396d748089884555b
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/ObfHelper.java
@@ -0,0 +1,156 @@
+package io.papermc.paper.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import net.neoforged.srgutils.IMappingFile;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public enum ObfHelper {
+ INSTANCE;
+
+ private final @Nullable Map<String, ClassMapping> mappingsByObfName;
+ private final @Nullable Map<String, ClassMapping> mappingsByMojangName;
+
+ ObfHelper() {
+ final @Nullable Set<ClassMapping> maps = loadMappingsIfPresent();
+ if (maps != null) {
+ this.mappingsByObfName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::obfName, map -> map));
+ this.mappingsByMojangName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::mojangName, map -> map));
+ } else {
+ this.mappingsByObfName = null;
+ this.mappingsByMojangName = null;
+ }
+ }
+
+ public @Nullable Map<String, ClassMapping> mappingsByObfName() {
+ return this.mappingsByObfName;
+ }
+
+ public @Nullable Map<String, ClassMapping> mappingsByMojangName() {
+ return this.mappingsByMojangName;
+ }
+
+ /**
+ * Attempts to get the obf name for a given class by its Mojang name. Will
+ * return the input string if mappings are not present.
+ *
+ * @param fullyQualifiedMojangName fully qualified class name (dotted)
+ * @return mapped or original fully qualified (dotted) class name
+ */
+ public String reobfClassName(final String fullyQualifiedMojangName) {
+ if (this.mappingsByMojangName == null) {
+ return fullyQualifiedMojangName;
+ }
+
+ final ClassMapping map = this.mappingsByMojangName.get(fullyQualifiedMojangName);
+ if (map == null) {
+ return fullyQualifiedMojangName;
+ }
+
+ return map.obfName();
+ }
+
+ /**
+ * Attempts to get the Mojang name for a given class by its obf name. Will
+ * return the input string if mappings are not present.
+ *
+ * @param fullyQualifiedObfName fully qualified class name (dotted)
+ * @return mapped or original fully qualified (dotted) class name
+ */
+ public String deobfClassName(final String fullyQualifiedObfName) {
+ if (this.mappingsByObfName == null) {
+ return fullyQualifiedObfName;
+ }
+
+ final ClassMapping map = this.mappingsByObfName.get(fullyQualifiedObfName);
+ if (map == null) {
+ return fullyQualifiedObfName;
+ }
+
+ return map.mojangName();
+ }
+
+ private static @Nullable Set<ClassMapping> loadMappingsIfPresent() {
+ try (final @Nullable InputStream mappingsInputStream = ObfHelper.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) {
+ if (mappingsInputStream == null) {
+ return null;
+ }
+ final IMappingFile mappings = IMappingFile.load(mappingsInputStream); // Mappings are mojang->spigot
+ final Set<ClassMapping> classes = new HashSet<>();
+
+ final StringPool pool = new StringPool();
+ for (final IMappingFile.IClass cls : mappings.getClasses()) {
+ final Map<String, String> methods = new HashMap<>();
+ final Map<String, String> fields = new HashMap<>();
+ final Map<String, String> strippedMethods = new HashMap<>();
+
+ for (final IMappingFile.IMethod methodMapping : cls.getMethods()) {
+ methods.put(
+ pool.string(methodKey(
+ Objects.requireNonNull(methodMapping.getMapped()),
+ Objects.requireNonNull(methodMapping.getMappedDescriptor())
+ )),
+ pool.string(Objects.requireNonNull(methodMapping.getOriginal()))
+ );
+
+ strippedMethods.put(
+ pool.string(pool.string(strippedMethodKey(
+ methodMapping.getMapped(),
+ methodMapping.getDescriptor()
+ ))),
+ pool.string(methodMapping.getOriginal())
+ );
+ }
+ for (final IMappingFile.IField field : cls.getFields()) {
+ fields.put(
+ pool.string(field.getMapped()),
+ pool.string(field.getOriginal())
+ );
+ }
+
+ final ClassMapping map = new ClassMapping(
+ Objects.requireNonNull(cls.getMapped()).replace('/', '.'),
+ Objects.requireNonNull(cls.getOriginal()).replace('/', '.'),
+ Map.copyOf(methods),
+ Map.copyOf(fields),
+ Map.copyOf(strippedMethods)
+ );
+ classes.add(map);
+ }
+
+ return Set.copyOf(classes);
+ } catch (final IOException ex) {
+ System.err.println("Failed to load mappings.");
+ ex.printStackTrace();
+ return null;
+ }
+ }
+
+ public static String strippedMethodKey(final String methodName, final String methodDescriptor) {
+ final String methodKey = methodKey(methodName, methodDescriptor);
+ final int returnDescriptorEnd = methodKey.indexOf(')');
+ return methodKey.substring(0, returnDescriptorEnd + 1);
+ }
+
+ public static String methodKey(final String methodName, final String methodDescriptor) {
+ return methodName + methodDescriptor;
+ }
+
+ public record ClassMapping(
+ String obfName,
+ String mojangName,
+ Map<String, String> methodsByObf,
+ Map<String, String> fieldsByObf,
+ // obf name with mapped desc to mapped name. return value is excluded from desc as reflection doesn't use it
+ Map<String, String> strippedMethods
+ ) {}
+}
diff --git a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java
new file mode 100644
index 0000000000000000000000000000000000000000..242811578a786e3807a1a7019d472d5a68f87116
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java
@@ -0,0 +1,144 @@
+package io.papermc.paper.util;
+
+import io.papermc.paper.configuration.GlobalConfiguration;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@DefaultQualifier(NonNull.class)
+public enum StacktraceDeobfuscator {
+ INSTANCE;
+
+ private final Map<Class<?>, Int2ObjectMap<String>> lineMapCache = Collections.synchronizedMap(new LinkedHashMap<>(128, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(final Map.Entry<Class<?>, Int2ObjectMap<String>> eldest) {
+ return this.size() > 127;
+ }
+ });
+
+ public void deobfuscateThrowable(final Throwable throwable) {
+ if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true
+ return;
+ }
+
+ throwable.setStackTrace(this.deobfuscateStacktrace(throwable.getStackTrace()));
+ final Throwable cause = throwable.getCause();
+ if (cause != null) {
+ this.deobfuscateThrowable(cause);
+ }
+ for (final Throwable suppressed : throwable.getSuppressed()) {
+ this.deobfuscateThrowable(suppressed);
+ }
+ }
+
+ public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) {
+ if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true
+ return traceElements;
+ }
+
+ final @Nullable Map<String, ObfHelper.ClassMapping> mappings = ObfHelper.INSTANCE.mappingsByObfName();
+ if (mappings == null || traceElements.length == 0) {
+ return traceElements;
+ }
+ final StackTraceElement[] result = new StackTraceElement[traceElements.length];
+ for (int i = 0; i < traceElements.length; i++) {
+ final StackTraceElement element = traceElements[i];
+
+ final String className = element.getClassName();
+ final String methodName = element.getMethodName();
+
+ final ObfHelper.ClassMapping classMapping = mappings.get(className);
+ if (classMapping == null) {
+ result[i] = element;
+ continue;
+ }
+
+ final Class<?> clazz;
+ try {
+ clazz = Class.forName(className);
+ } catch (final ClassNotFoundException ex) {
+ throw new RuntimeException(ex);
+ }
+ final @Nullable String methodKey = this.determineMethodForLine(clazz, element.getLineNumber());
+ final @Nullable String mappedMethodName = methodKey == null ? null : classMapping.methodsByObf().get(methodKey);
+
+ result[i] = new StackTraceElement(
+ element.getClassLoaderName(),
+ element.getModuleName(),
+ element.getModuleVersion(),
+ classMapping.mojangName(),
+ mappedMethodName != null ? mappedMethodName : methodName,
+ sourceFileName(classMapping.mojangName()),
+ element.getLineNumber()
+ );
+ }
+ return result;
+ }
+
+ private @Nullable String determineMethodForLine(final Class<?> clazz, final int lineNumber) {
+ return this.lineMapCache.computeIfAbsent(clazz, StacktraceDeobfuscator::buildLineMap).get(lineNumber);
+ }
+
+ private static String sourceFileName(final String fullClassName) {
+ final int dot = fullClassName.lastIndexOf('.');
+ final String className = dot == -1
+ ? fullClassName
+ : fullClassName.substring(dot + 1);
+ final String rootClassName = className.split("\\$")[0];
+ return rootClassName + ".java";
+ }
+
+ private static Int2ObjectMap<String> buildLineMap(final Class<?> key) {
+ final StringPool pool = new StringPool();
+ final Int2ObjectMap<String> lineMap = new Int2ObjectOpenHashMap<>();
+ final class LineCollectingMethodVisitor extends MethodVisitor {
+ private final String name;
+ private final String descriptor;
+
+ LineCollectingMethodVisitor(final String name, final String descriptor) {
+ super(Opcodes.ASM9);
+ this.name = name;
+ this.descriptor = descriptor;
+ }
+
+ @Override
+ public void visitLineNumber(final int line, final Label start) {
+ lineMap.put(line, pool.string(ObfHelper.methodKey(this.name, this.descriptor)));
+ }
+ }
+ final ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) {
+ @Override
+ public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) {
+ return new LineCollectingMethodVisitor(name, descriptor);
+ }
+ };
+ try {
+ final @Nullable InputStream inputStream = StacktraceDeobfuscator.class.getClassLoader()
+ .getResourceAsStream(key.getName().replace('.', '/') + ".class");
+ if (inputStream == null) {
+ throw new IllegalStateException("Could not find class file: " + key.getName());
+ }
+ final byte[] classData;
+ try (inputStream) {
+ classData = inputStream.readAllBytes();
+ }
+ final ClassReader reader = new ClassReader(classData);
+ reader.accept(classVisitor, 0);
+ } catch (final IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ return lineMap;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/util/StringPool.java b/src/main/java/io/papermc/paper/util/StringPool.java
new file mode 100644
index 0000000000000000000000000000000000000000..c0a486cb46ff30353c3ff09567891cd36238eeb4
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/StringPool.java
@@ -0,0 +1,34 @@
+package io.papermc.paper.util;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+/**
+ * De-duplicates {@link String} instances without using {@link String#intern()}.
+ *
+ * <p>Interning may not be desired as we may want to use the heap for our pool,
+ * so it can be garbage collected as normal, etc.</p>
+ *
+ * <p>Additionally, interning can be slow due to the potentially large size of the
+ * pool (as it is shared for the entire JVM), and because most JVMs implement
+ * it using JNI.</p>
+ */
+@DefaultQualifier(NonNull.class)
+public final class StringPool {
+ private final Map<String, String> pool;
+
+ public StringPool() {
+ this(new HashMap<>());
+ }
+
+ public StringPool(final Map<String, String> map) {
+ this.pool = map;
+ }
+
+ public String string(final String string) {
+ return this.pool.computeIfAbsent(string, Function.identity());
+ }
+}
diff --git a/src/main/java/net/minecraft/CrashReport.java b/src/main/java/net/minecraft/CrashReport.java
index 1938ae691dafec1fc1e5a68792d1191bd52b4e5c..268310642181a715815d3b2d1c0f090e6252971a 100644
--- a/src/main/java/net/minecraft/CrashReport.java
+++ b/src/main/java/net/minecraft/CrashReport.java
@@ -34,6 +34,7 @@ public class CrashReport {
private final SystemReport systemReport = new SystemReport();
public CrashReport(String message, Throwable cause) {
+ io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); // Paper
this.title = message;
this.exception = cause;
this.systemReport.setDetail("CraftBukkit Information", new org.bukkit.craftbukkit.CraftCrashReport()); // CraftBukkit
diff --git a/src/main/java/net/minecraft/CrashReportCategory.java b/src/main/java/net/minecraft/CrashReportCategory.java
index f367ba058018074bfe6e4fe88bcc875ea9794d9e..2176171954609fd88f97f93408e14e018c1d6eaa 100644
--- a/src/main/java/net/minecraft/CrashReportCategory.java
+++ b/src/main/java/net/minecraft/CrashReportCategory.java
@@ -110,6 +110,7 @@ public class CrashReportCategory {
} else {
this.stackTrace = new StackTraceElement[stackTraceElements.length - 3 - ignoredCallCount];
System.arraycopy(stackTraceElements, 3 + ignoredCallCount, this.stackTrace, 0, this.stackTrace.length);
+ this.stackTrace = io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(this.stackTrace); // Paper
return this.stackTrace.length;
}
}
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
index 77985072928a1b892fb4f7dec1d0899324780082..f5e6610d271ef2c997fb3d1a5f65e0bf0740805a 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
@@ -82,13 +82,13 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
marker.add(Connection.PACKET_MARKER);
});
public static final Supplier<NioEventLoopGroup> NETWORK_WORKER_GROUP = Suppliers.memoize(() -> {
- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build());
+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
});
public static final Supplier<EpollEventLoopGroup> NETWORK_EPOLL_WORKER_GROUP = Suppliers.memoize(() -> {
- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build());
+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
});
public static final Supplier<DefaultEventLoopGroup> LOCAL_WORKER_GROUP = Suppliers.memoize(() -> {
- return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build());
+ return new DefaultEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
});
private static final ProtocolInfo<ServerHandshakePacketListener> INITIAL_PROTOCOL = HandshakeProtocols.SERVERBOUND;
private final PacketFlow receiving;
@@ -197,7 +197,7 @@ public class Connection extends SimpleChannelInboundHandler<Packet<?>> {
}
}
- if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) throwable.printStackTrace(); // Spigot
+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) io.papermc.paper.util.TraceUtil.printStackTrace(throwable); // Spigot // Paper
}
protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet<?> packet) {
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index b41eb920b5665b7a1b7cd9f38955c31eeb350847..bb59986c211f7d6ea50b1ad4bd5565227bec8a6c 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -210,6 +210,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
org.spigotmc.SpigotConfig.init((java.io.File) this.options.valueOf("spigot-settings"));
org.spigotmc.SpigotConfig.registerCommands();
// Spigot end
+ io.papermc.paper.util.ObfHelper.INSTANCE.getClass(); // Paper - load mappings for stacktrace deobf and etc.
// Paper start - initialize global and world-defaults configuration
this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
diff --git a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
index 987360a266a5a870f06929b00c9f451901188fd6..2cf3e79ec5e8706b71d27ebad4668773f0b91195 100644
--- a/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
+++ b/src/main/java/net/minecraft/server/network/ServerConnectionListener.java
@@ -52,10 +52,10 @@ public class ServerConnectionListener {
private static final Logger LOGGER = LogUtils.getLogger();
public static final Supplier<NioEventLoopGroup> SERVER_EVENT_GROUP = Suppliers.memoize(() -> {
- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build());
+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
});
public static final Supplier<EpollEventLoopGroup> SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(() -> {
- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build());
+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper
});
final MinecraftServer server;
public volatile boolean running;
diff --git a/src/main/java/net/minecraft/server/players/OldUsersConverter.java b/src/main/java/net/minecraft/server/players/OldUsersConverter.java
index 8d06e8d286da2573e40794adab695ff77e5afd86..68551947f5b7d3471f15bd74ccd86519ab34c1c1 100644
--- a/src/main/java/net/minecraft/server/players/OldUsersConverter.java
+++ b/src/main/java/net/minecraft/server/players/OldUsersConverter.java
@@ -356,7 +356,7 @@ public class OldUsersConverter {
try {
root = NbtIo.readCompressed(new java.io.FileInputStream(file5), NbtAccounter.unlimitedHeap());
} catch (Exception exception) {
- exception.printStackTrace();
+ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper
}
if (root != null) {
@@ -369,7 +369,7 @@ public class OldUsersConverter {
try {
NbtIo.writeCompressed(root, new java.io.FileOutputStream(file2));
} catch (Exception exception) {
- exception.printStackTrace();
+ io.papermc.paper.util.TraceUtil.printStackTrace(exception); // Paper
}
}
// CraftBukkit end
diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java
index c4bf7053d83d207caca0e13e19f5c1afa7062de3..f697d45e0ac4e9cdc8a46121510a04c0f294d91f 100644
--- a/src/main/java/org/spigotmc/WatchdogThread.java
+++ b/src/main/java/org/spigotmc/WatchdogThread.java
@@ -130,7 +130,7 @@ public class WatchdogThread extends Thread
}
log.log( Level.SEVERE, "\tStack:" );
//
- for ( StackTraceElement stack : thread.getStackTrace() )
+ for ( StackTraceElement stack : io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(thread.getStackTrace()) ) // Paper
{
log.log( Level.SEVERE, "\t\t" + stack );
}
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 18e961a37b2830da6e5dab7aa35116b2f5215898..128fa1376f22d3429a23d79a2772abf2e7fec2bc 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -30,10 +30,14 @@
<DefaultRolloverStrategy max="1000"/>
</RollingRandomAccessFile>
<Async name="Async">
+ <AppenderRef ref="rewrite"/>
+ </Async>
+ <Rewrite name="rewrite">
+ <StacktraceDeobfuscatingRewritePolicy />
<AppenderRef ref="File"/>
<AppenderRef ref="TerminalConsole" level="info"/>
<AppenderRef ref="ServerGuiConsole" level="info"/>
- </Async>
+ </Rewrite>
</Appenders>
<Loggers>
<Root level="info">

View file

@ -1,246 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: SirYwell <hannesgreule@outlook.de>
Date: Sat, 10 Jul 2021 11:12:30 +0200
Subject: [PATCH] Rewrite LogEvents to contain the source jars in stack traces
diff --git a/src/log4jPlugins/java/io/papermc/paper/logging/DelegateLogEvent.java b/src/log4jPlugins/java/io/papermc/paper/logging/DelegateLogEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ffd1befe64c6c3036c22e05ed1c44808d64bd28
--- /dev/null
+++ b/src/log4jPlugins/java/io/papermc/paper/logging/DelegateLogEvent.java
@@ -0,0 +1,130 @@
+package io.papermc.paper.logging;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.ThrowableProxy;
+import org.apache.logging.log4j.core.time.Instant;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
+
+import java.util.Map;
+
+public class DelegateLogEvent implements LogEvent {
+ private final LogEvent original;
+
+ protected DelegateLogEvent(LogEvent original) {
+ this.original = original;
+ }
+
+ @Override
+ public LogEvent toImmutable() {
+ return this.original.toImmutable();
+ }
+
+ @Override
+ public Map<String, String> getContextMap() {
+ return this.original.getContextMap();
+ }
+
+ @Override
+ public ReadOnlyStringMap getContextData() {
+ return this.original.getContextData();
+ }
+
+ @Override
+ public ThreadContext.ContextStack getContextStack() {
+ return this.original.getContextStack();
+ }
+
+ @Override
+ public String getLoggerFqcn() {
+ return this.original.getLoggerFqcn();
+ }
+
+ @Override
+ public Level getLevel() {
+ return this.original.getLevel();
+ }
+
+ @Override
+ public String getLoggerName() {
+ return this.original.getLoggerName();
+ }
+
+ @Override
+ public Marker getMarker() {
+ return this.original.getMarker();
+ }
+
+ @Override
+ public Message getMessage() {
+ return this.original.getMessage();
+ }
+
+ @Override
+ public long getTimeMillis() {
+ return this.original.getTimeMillis();
+ }
+
+ @Override
+ public Instant getInstant() {
+ return this.original.getInstant();
+ }
+
+ @Override
+ public StackTraceElement getSource() {
+ return this.original.getSource();
+ }
+
+ @Override
+ public String getThreadName() {
+ return this.original.getThreadName();
+ }
+
+ @Override
+ public long getThreadId() {
+ return this.original.getThreadId();
+ }
+
+ @Override
+ public int getThreadPriority() {
+ return this.original.getThreadPriority();
+ }
+
+ @Override
+ public Throwable getThrown() {
+ return this.original.getThrown();
+ }
+
+ @Override
+ public ThrowableProxy getThrownProxy() {
+ return this.original.getThrownProxy();
+ }
+
+ @Override
+ public boolean isEndOfBatch() {
+ return this.original.isEndOfBatch();
+ }
+
+ @Override
+ public boolean isIncludeLocation() {
+ return this.original.isIncludeLocation();
+ }
+
+ @Override
+ public void setEndOfBatch(boolean endOfBatch) {
+ this.original.setEndOfBatch(endOfBatch);
+ }
+
+ @Override
+ public void setIncludeLocation(boolean locationRequired) {
+ this.original.setIncludeLocation(locationRequired);
+ }
+
+ @Override
+ public long getNanoTime() {
+ return this.original.getNanoTime();
+ }
+}
diff --git a/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java b/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..558427c65b4051923f73d15d85ee519be005060a
--- /dev/null
+++ b/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoLogEvent.java
@@ -0,0 +1,48 @@
+package io.papermc.paper.logging;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.ExtendedClassInfo;
+import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement;
+import org.apache.logging.log4j.core.impl.ThrowableProxy;
+
+public class ExtraClassInfoLogEvent extends DelegateLogEvent {
+
+ private boolean fixed;
+
+ public ExtraClassInfoLogEvent(LogEvent original) {
+ super(original);
+ }
+
+ @Override
+ public ThrowableProxy getThrownProxy() {
+ if (fixed) {
+ return super.getThrownProxy();
+ }
+ rewriteStackTrace(super.getThrownProxy());
+ fixed = true;
+ return super.getThrownProxy();
+ }
+
+ private void rewriteStackTrace(ThrowableProxy throwable) {
+ ExtendedStackTraceElement[] stackTrace = throwable.getExtendedStackTrace();
+ for (int i = 0; i < stackTrace.length; i++) {
+ ExtendedClassInfo classInfo = stackTrace[i].getExtraClassInfo();
+ if (classInfo.getLocation().equals("?")) {
+ StackTraceElement element = stackTrace[i].getStackTraceElement();
+ String classLoaderName = element.getClassLoaderName();
+ if (classLoaderName != null) {
+ stackTrace[i] = new ExtendedStackTraceElement(element,
+ new ExtendedClassInfo(classInfo.getExact(), classLoaderName, "?"));
+ }
+ }
+ }
+ if (throwable.getCauseProxy() != null) {
+ rewriteStackTrace(throwable.getCauseProxy());
+ }
+ if (throwable.getSuppressedProxies() != null) {
+ for (ThrowableProxy proxy : throwable.getSuppressedProxies()) {
+ rewriteStackTrace(proxy);
+ }
+ }
+ }
+}
diff --git a/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java b/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..34734bb969a1a74c7a4f9c17d40ebf007ad5d701
--- /dev/null
+++ b/src/log4jPlugins/java/io/papermc/paper/logging/ExtraClassInfoRewritePolicy.java
@@ -0,0 +1,29 @@
+package io.papermc.paper.logging;
+
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.jetbrains.annotations.NotNull;
+
+@Plugin(
+ name = "ExtraClassInfoRewritePolicy",
+ category = Core.CATEGORY_NAME,
+ elementType = "rewritePolicy",
+ printObject = true
+)
+public final class ExtraClassInfoRewritePolicy implements RewritePolicy {
+ @Override
+ public LogEvent rewrite(LogEvent source) {
+ if (source.getThrown() != null) {
+ return new ExtraClassInfoLogEvent(source);
+ }
+ return source;
+ }
+
+ @PluginFactory
+ public static @NotNull ExtraClassInfoRewritePolicy createPolicy() {
+ return new ExtraClassInfoRewritePolicy();
+ }
+}
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 128fa1376f22d3429a23d79a2772abf2e7fec2bc..637d64da9938e51a97338b9253b43889585c67bb 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -34,6 +34,10 @@
</Async>
<Rewrite name="rewrite">
<StacktraceDeobfuscatingRewritePolicy />
+ <AppenderRef ref="rewrite2"/>
+ </Rewrite>
+ <Rewrite name="rewrite2">
+ <ExtraClassInfoRewritePolicy />
<AppenderRef ref="File"/>
<AppenderRef ref="TerminalConsole" level="info"/>
<AppenderRef ref="ServerGuiConsole" level="info"/>

View file

@ -1,665 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Mon, 29 Feb 2016 21:02:09 -0600
Subject: [PATCH] Paper command
Co-authored-by: Zach Brown <zach.brown@destroystokyo.com>
diff --git a/src/main/java/io/papermc/paper/command/CallbackCommand.java b/src/main/java/io/papermc/paper/command/CallbackCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa4202abd13c1c286bd398938103d1103d5443e7
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/CallbackCommand.java
@@ -0,0 +1,35 @@
+package io.papermc.paper.command;
+
+import io.papermc.paper.adventure.providers.ClickCallbackProviderImpl;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import java.util.UUID;
+
+@DefaultQualifier(NonNull.class)
+public class CallbackCommand extends Command {
+
+ protected CallbackCommand(final String name) {
+ super(name);
+ this.description = "ClickEvent callback";
+ this.usageMessage = "/callback <uuid>";
+ }
+
+ @Override
+ public boolean execute(final CommandSender sender, final String commandLabel, final String[] args) {
+ if (args.length != 1) {
+ return false;
+ }
+
+ final UUID id;
+ try {
+ id = UUID.fromString(args[0]);
+ } catch (final IllegalArgumentException ignored) {
+ return false;
+ }
+
+ ClickCallbackProviderImpl.CALLBACK_MANAGER.runCallback(sender, id);
+ return true;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/CommandUtil.java b/src/main/java/io/papermc/paper/command/CommandUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..953c30500892e5f0c55b8597bc708ea85bf56d6e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/CommandUtil.java
@@ -0,0 +1,69 @@
+package io.papermc.paper.command;
+
+import com.google.common.base.Functions;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import net.minecraft.resources.ResourceLocation;
+import org.bukkit.command.CommandSender;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class CommandUtil {
+ private CommandUtil() {
+ }
+
+ // Code from Mojang - copyright them
+ public static List<String> getListMatchingLast(
+ final CommandSender sender,
+ final String[] args,
+ final String... matches
+ ) {
+ return getListMatchingLast(sender, args, Arrays.asList(matches));
+ }
+
+ public static boolean matches(final String s, final String s1) {
+ return s1.regionMatches(true, 0, s, 0, s.length());
+ }
+
+ public static List<String> getListMatchingLast(
+ final CommandSender sender,
+ final String[] strings,
+ final Collection<?> collection
+ ) {
+ String last = strings[strings.length - 1];
+ ArrayList<String> results = Lists.newArrayList();
+
+ if (!collection.isEmpty()) {
+ Iterator iterator = Iterables.transform(collection, Functions.toStringFunction()).iterator();
+
+ while (iterator.hasNext()) {
+ String s1 = (String) iterator.next();
+
+ if (matches(last, s1) && (sender.hasPermission(PaperCommand.BASE_PERM + s1) || sender.hasPermission("bukkit.command.paper"))) {
+ results.add(s1);
+ }
+ }
+
+ if (results.isEmpty()) {
+ iterator = collection.iterator();
+
+ while (iterator.hasNext()) {
+ Object object = iterator.next();
+
+ if (object instanceof ResourceLocation && matches(last, ((ResourceLocation) object).getPath())) {
+ results.add(String.valueOf(object));
+ }
+ }
+ }
+ }
+
+ return results;
+ }
+ // end copy stuff
+}
diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..95d3320c865d24609252063be07cddfd07d0d6d8
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
@@ -0,0 +1,143 @@
+package io.papermc.paper.command;
+
+import io.papermc.paper.command.subcommands.*;
+import it.unimi.dsi.fastutil.Pair;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import net.minecraft.Util;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.permissions.Permission;
+import org.bukkit.permissions.PermissionDefault;
+import org.bukkit.plugin.PluginManager;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+
+@DefaultQualifier(NonNull.class)
+public final class PaperCommand extends Command {
+ static final String BASE_PERM = "bukkit.command.paper.";
+ // subcommand label -> subcommand
+ private static final Map<String, PaperSubcommand> SUBCOMMANDS = Util.make(() -> {
+ final Map<Set<String>, PaperSubcommand> commands = new HashMap<>();
+
+ commands.put(Set.of("heap"), new HeapDumpCommand());
+ commands.put(Set.of("entity"), new EntityCommand());
+ commands.put(Set.of("reload"), new ReloadCommand());
+ commands.put(Set.of("version"), new VersionCommand());
+
+ return commands.entrySet().stream()
+ .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ });
+ private static final Set<String> COMPLETABLE_SUBCOMMANDS = SUBCOMMANDS.entrySet().stream().filter(entry -> entry.getValue().tabCompletes()).map(Map.Entry::getKey).collect(Collectors.toSet());
+ // alias -> subcommand label
+ private static final Map<String, String> ALIASES = Util.make(() -> {
+ final Map<String, Set<String>> aliases = new HashMap<>();
+
+ aliases.put("version", Set.of("ver"));
+
+ return aliases.entrySet().stream()
+ .flatMap(entry -> entry.getValue().stream().map(s -> Map.entry(s, entry.getKey())))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ });
+
+ public PaperCommand(final String name) {
+ super(name);
+ this.description = "Paper related commands";
+ this.usageMessage = "/paper [" + String.join(" | ", SUBCOMMANDS.keySet()) + "]";
+ final List<String> permissions = new ArrayList<>();
+ permissions.add("bukkit.command.paper");
+ permissions.addAll(SUBCOMMANDS.keySet().stream().map(s -> BASE_PERM + s).toList());
+ this.setPermission(String.join(";", permissions));
+ final PluginManager pluginManager = Bukkit.getServer().getPluginManager();
+ for (final String perm : permissions) {
+ pluginManager.addPermission(new Permission(perm, PermissionDefault.OP));
+ }
+ }
+
+ private static boolean testPermission(final CommandSender sender, final String permission) {
+ if (sender.hasPermission(BASE_PERM + permission) || sender.hasPermission("bukkit.command.paper")) {
+ return true;
+ }
+ sender.sendMessage(text("I'm sorry, but you do not have permission to perform this command. Please contact the server administrators if you believe that this is in error.", RED));
+ return false;
+ }
+
+ @Override
+ public List<String> tabComplete(
+ final CommandSender sender,
+ final String alias,
+ final String[] args,
+ final @Nullable Location location
+ ) throws IllegalArgumentException {
+ if (args.length <= 1) {
+ return CommandUtil.getListMatchingLast(sender, args, COMPLETABLE_SUBCOMMANDS);
+ }
+
+ final @Nullable Pair<String, PaperSubcommand> subCommand = resolveCommand(args[0]);
+ if (subCommand != null) {
+ return subCommand.second().tabComplete(sender, subCommand.first(), Arrays.copyOfRange(args, 1, args.length));
+ }
+
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean execute(
+ final CommandSender sender,
+ final String commandLabel,
+ final String[] args
+ ) {
+ if (!testPermission(sender)) {
+ return true;
+ }
+
+ if (args.length == 0) {
+ sender.sendMessage(text("Usage: " + this.usageMessage, RED));
+ return false;
+ }
+ final @Nullable Pair<String, PaperSubcommand> subCommand = resolveCommand(args[0]);
+
+ if (subCommand == null) {
+ sender.sendMessage(text("Usage: " + this.usageMessage, RED));
+ return false;
+ }
+
+ if (!testPermission(sender, subCommand.first())) {
+ return true;
+ }
+ final String[] choppedArgs = Arrays.copyOfRange(args, 1, args.length);
+ return subCommand.second().execute(sender, subCommand.first(), choppedArgs);
+ }
+
+ private static @Nullable Pair<String, PaperSubcommand> resolveCommand(String label) {
+ label = label.toLowerCase(Locale.ROOT);
+ @Nullable PaperSubcommand subCommand = SUBCOMMANDS.get(label);
+ if (subCommand == null) {
+ final @Nullable String command = ALIASES.get(label);
+ if (command != null) {
+ label = command;
+ subCommand = SUBCOMMANDS.get(command);
+ }
+ }
+
+ if (subCommand != null) {
+ return Pair.of(label, subCommand);
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e5ec700a368cfdaa1ea0b3f0fa82089895d4b92
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/PaperCommands.java
@@ -0,0 +1,28 @@
+package io.papermc.paper.command;
+
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.command.Command;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class PaperCommands {
+
+ private PaperCommands() {
+ }
+
+ private static final Map<String, Command> COMMANDS = new HashMap<>();
+ static {
+ COMMANDS.put("paper", new PaperCommand("paper"));
+ COMMANDS.put("callback", new CallbackCommand("callback"));
+ }
+
+ public static void registerCommands(final MinecraftServer server) {
+ COMMANDS.forEach((s, command) -> {
+ server.server.getCommandMap().register(s, "Paper", command);
+ });
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/PaperSubcommand.java b/src/main/java/io/papermc/paper/command/PaperSubcommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..7e9e0ff8639be135bf8575e375cbada5b57164e1
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/PaperSubcommand.java
@@ -0,0 +1,20 @@
+package io.papermc.paper.command;
+
+import java.util.Collections;
+import java.util.List;
+import org.bukkit.command.CommandSender;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public interface PaperSubcommand {
+ boolean execute(CommandSender sender, String subCommand, String[] args);
+
+ default List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
+ return Collections.emptyList();
+ }
+
+ default boolean tabCompletes() {
+ return true;
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..67fcba634f8183bb33834ac3b2c3dcfb8d87129e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/subcommands/EntityCommand.java
@@ -0,0 +1,158 @@
+package io.papermc.paper.command.subcommands;
+
+import com.google.common.collect.Maps;
+import io.papermc.paper.command.CommandUtil;
+import io.papermc.paper.command.PaperSubcommand;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.event.ClickEvent;
+import net.kyori.adventure.text.event.HoverEvent;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.level.ServerChunkCache;
+import net.minecraft.server.level.ServerLevel;
+import net.minecraft.world.entity.EntityType;
+import net.minecraft.world.level.ChunkPos;
+import org.apache.commons.lang3.tuple.MutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.bukkit.Bukkit;
+import org.bukkit.HeightMap;
+import org.bukkit.World;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.CraftWorld;
+import org.bukkit.entity.Player;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+
+@DefaultQualifier(NonNull.class)
+public final class EntityCommand implements PaperSubcommand {
+ @Override
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
+ this.listEntities(sender, args);
+ return true;
+ }
+
+ @Override
+ public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
+ if (args.length == 1) {
+ return CommandUtil.getListMatchingLast(sender, args, "help", "list");
+ } else if (args.length == 2) {
+ return CommandUtil.getListMatchingLast(sender, args, BuiltInRegistries.ENTITY_TYPE.keySet().stream().map(ResourceLocation::toString).sorted().toArray(String[]::new));
+ }
+ return Collections.emptyList();
+ }
+
+ /*
+ * Ported from MinecraftForge - author: LexManos <LexManos@gmail.com> - License: LGPLv2.1
+ */
+ private void listEntities(final CommandSender sender, final String[] args) {
+ // help
+ if (args.length < 1 || !args[0].toLowerCase(Locale.ROOT).equals("list")) {
+ sender.sendMessage(text("Use /paper entity [list] help for more information on a specific command", RED));
+ return;
+ }
+
+ if ("list".equals(args[0].toLowerCase(Locale.ROOT))) {
+ String filter = "*";
+ if (args.length > 1) {
+ if (args[1].toLowerCase(Locale.ROOT).equals("help")) {
+ sender.sendMessage(text("Use /paper entity list [filter] [worldName] to get entity info that matches the optional filter.", RED));
+ return;
+ }
+ filter = args[1];
+ }
+ final String cleanfilter = filter.replace("?", ".?").replace("*", ".*?");
+ Set<ResourceLocation> names = BuiltInRegistries.ENTITY_TYPE.keySet().stream()
+ .filter(n -> n.toString().matches(cleanfilter))
+ .collect(Collectors.toSet());
+ if (names.isEmpty()) {
+ sender.sendMessage(text("Invalid filter, does not match any entities. Use /paper entity list for a proper list", RED));
+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED));
+ return;
+ }
+ String worldName;
+ if (args.length > 2) {
+ worldName = args[2];
+ } else if (sender instanceof Player) {
+ worldName = ((Player) sender).getWorld().getName();
+ } else {
+ sender.sendMessage(text("Please specify the name of a world", RED));
+ sender.sendMessage(text("To do so without a filter, specify '*' as the filter", RED));
+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED));
+ return;
+ }
+ Map<ResourceLocation, MutablePair<Integer, Map<ChunkPos, Integer>>> list = Maps.newHashMap();
+ @Nullable World bukkitWorld = Bukkit.getWorld(worldName);
+ if (bukkitWorld == null) {
+ sender.sendMessage(text("Could not load world for " + worldName + ". Please select a valid world.", RED));
+ sender.sendMessage(text("Usage: /paper entity list [filter] [worldName]", RED));
+ return;
+ }
+ ServerLevel world = ((CraftWorld) bukkitWorld).getHandle();
+ Map<ResourceLocation, Integer> nonEntityTicking = Maps.newHashMap();
+ ServerChunkCache chunkProviderServer = world.getChunkSource();
+ world.getAllEntities().forEach(e -> {
+ ResourceLocation key = EntityType.getKey(e.getType());
+
+ MutablePair<Integer, Map<ChunkPos, Integer>> info = list.computeIfAbsent(key, k -> MutablePair.of(0, Maps.newHashMap()));
+ ChunkPos chunk = e.chunkPosition();
+ info.left++;
+ info.right.put(chunk, info.right.getOrDefault(chunk, 0) + 1);
+ if (!chunkProviderServer.isPositionTicking(e)) {
+ nonEntityTicking.merge(key, 1, Integer::sum);
+ }
+ });
+ if (names.size() == 1) {
+ ResourceLocation name = names.iterator().next();
+ Pair<Integer, Map<ChunkPos, Integer>> info = list.get(name);
+ int nonTicking = nonEntityTicking.getOrDefault(name, 0);
+ if (info == null) {
+ sender.sendMessage(text("No entities found.", RED));
+ return;
+ }
+ sender.sendMessage("Entity: " + name + " Total Ticking: " + (info.getLeft() - nonTicking) + ", Total Non-Ticking: " + nonTicking);
+ info.getRight().entrySet().stream()
+ .sorted((a, b) -> !a.getValue().equals(b.getValue()) ? b.getValue() - a.getValue() : a.getKey().toString().compareTo(b.getKey().toString()))
+ .limit(10).forEach(e -> {
+ final int x = (e.getKey().x << 4) + 8;
+ final int z = (e.getKey().z << 4) + 8;
+ final Component message = text(" " + e.getValue() + ": " + e.getKey().x + ", " + e.getKey().z + (chunkProviderServer.isPositionTicking(e.getKey().toLong()) ? " (Ticking)" : " (Non-Ticking)"))
+ .hoverEvent(HoverEvent.showText(text("Click to teleport to chunk", GREEN)))
+ .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/minecraft:execute as @s in " + world.getWorld().getKey() + " run tp " + x + " " + (world.getWorld().getHighestBlockYAt(x, z, HeightMap.MOTION_BLOCKING) + 1) + " " + z));
+ sender.sendMessage(message);
+ });
+ } else {
+ List<Pair<ResourceLocation, Integer>> info = list.entrySet().stream()
+ .filter(e -> names.contains(e.getKey()))
+ .map(e -> Pair.of(e.getKey(), e.getValue().left))
+ .sorted((a, b) -> !a.getRight().equals(b.getRight()) ? b.getRight() - a.getRight() : a.getKey().toString().compareTo(b.getKey().toString()))
+ .toList();
+
+ if (info.isEmpty()) {
+ sender.sendMessage(text("No entities found.", RED));
+ return;
+ }
+
+ int count = info.stream().mapToInt(Pair::getRight).sum();
+ int nonTickingCount = nonEntityTicking.values().stream().mapToInt(Integer::intValue).sum();
+ sender.sendMessage("Total Ticking: " + (count - nonTickingCount) + ", Total Non-Ticking: " + nonTickingCount);
+ info.forEach(e -> {
+ int nonTicking = nonEntityTicking.getOrDefault(e.getKey(), 0);
+ sender.sendMessage(" " + (e.getValue() - nonTicking) + " (" + nonTicking + ") " + ": " + e.getKey());
+ });
+ sender.sendMessage("* First number is ticking entities, second number is non-ticking entities");
+ }
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd2e4d792e972b8bf1e07b8961594a670ae949cf
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/subcommands/HeapDumpCommand.java
@@ -0,0 +1,38 @@
+package io.papermc.paper.command.subcommands;
+
+import io.papermc.paper.command.PaperSubcommand;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.CraftServer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+import static net.kyori.adventure.text.format.NamedTextColor.YELLOW;
+
+@DefaultQualifier(NonNull.class)
+public final class HeapDumpCommand implements PaperSubcommand {
+ @Override
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
+ this.dumpHeap(sender);
+ return true;
+ }
+
+ private void dumpHeap(final CommandSender sender) {
+ java.nio.file.Path dir = java.nio.file.Paths.get("./dumps");
+ String name = "heap-dump-" + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now());
+
+ Command.broadcastCommandMessage(sender, text("Writing JVM heap data...", YELLOW));
+
+ java.nio.file.Path file = CraftServer.dumpHeap(dir, name);
+ if (file != null) {
+ Command.broadcastCommandMessage(sender, text("Heap dump saved to " + file, GREEN));
+ } else {
+ Command.broadcastCommandMessage(sender, text("Failed to write heap dump, see server log for details", RED));
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..bd68139ae635f2ad7ec8e7a21e0056a139c4c62e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/subcommands/ReloadCommand.java
@@ -0,0 +1,33 @@
+package io.papermc.paper.command.subcommands;
+
+import io.papermc.paper.command.PaperSubcommand;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.craftbukkit.CraftServer;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import static net.kyori.adventure.text.Component.text;
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
+
+@DefaultQualifier(NonNull.class)
+public final class ReloadCommand implements PaperSubcommand {
+ @Override
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
+ this.doReload(sender);
+ return true;
+ }
+
+ private void doReload(final CommandSender sender) {
+ Command.broadcastCommandMessage(sender, text("Please note that this command is not supported and may cause issues.", RED));
+ Command.broadcastCommandMessage(sender, text("If you encounter any issues please use the /stop command to restart your server.", RED));
+
+ MinecraftServer server = ((CraftServer) sender.getServer()).getServer();
+ server.paperConfigurations.reloadConfigs(server);
+ server.server.reloadCount++;
+
+ Command.broadcastCommandMessage(sender, text("Paper config reload complete.", GREEN));
+ }
+}
diff --git a/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java b/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae60bd96b5284d54676d8e7e4dd5d170b526ec1e
--- /dev/null
+++ b/src/main/java/io/papermc/paper/command/subcommands/VersionCommand.java
@@ -0,0 +1,21 @@
+package io.papermc.paper.command.subcommands;
+
+import io.papermc.paper.command.PaperSubcommand;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+@DefaultQualifier(NonNull.class)
+public final class VersionCommand implements PaperSubcommand {
+ @Override
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
+ final @Nullable Command ver = MinecraftServer.getServer().server.getCommandMap().getCommand("version");
+ if (ver != null) {
+ ver.execute(sender, "paper", new String[0]);
+ }
+ return true;
+ }
+}
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index bb59986c211f7d6ea50b1ad4bd5565227bec8a6c..9c950fc1de15b5039e34a9fdf893e97a8cc13237 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -215,6 +215,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
this.paperConfigurations.initializeGlobalConfiguration(this.registryAccess());
this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
// Paper end - initialize global and world-defaults configuration
+ io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
this.setPvpAllowed(dedicatedserverproperties.pvp);
this.setFlightAllowed(dedicatedserverproperties.allowFlight);
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 752933ceabd7ebe7b1e9f7d41198128f6f2182a6..492af80bc4a252ad3b82aef0c46c3e6625ec9146 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -988,6 +988,7 @@ public final class CraftServer implements Server {
this.commandMap.clearCommands();
this.reloadData();
org.spigotmc.SpigotConfig.registerCommands(); // Spigot
+ io.papermc.paper.command.PaperCommands.registerCommands(this.console); // Paper
this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*");
this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions");
@@ -2710,6 +2711,34 @@ public final class CraftServer implements Server {
// Paper end
// Paper start
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public static java.nio.file.Path dumpHeap(java.nio.file.Path dir, String name) {
+ try {
+ java.nio.file.Files.createDirectories(dir);
+
+ javax.management.MBeanServer server = java.lang.management.ManagementFactory.getPlatformMBeanServer();
+ java.nio.file.Path file;
+
+ try {
+ Class clazz = Class.forName("openj9.lang.management.OpenJ9DiagnosticsMXBean");
+ Object openj9Mbean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "openj9.lang.management:type=OpenJ9Diagnostics", clazz);
+ java.lang.reflect.Method m = clazz.getMethod("triggerDumpToFile", String.class, String.class);
+ file = dir.resolve(name + ".phd");
+ m.invoke(openj9Mbean, "heap", file.toString());
+ } catch (ClassNotFoundException e) {
+ Class clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
+ Object hotspotMBean = java.lang.management.ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", clazz);
+ java.lang.reflect.Method m = clazz.getMethod("dumpHeap", String.class, boolean.class);
+ file = dir.resolve(name + ".hprof");
+ m.invoke(hotspotMBean, file.toString(), true);
+ }
+
+ return file;
+ } catch (Throwable t) {
+ Bukkit.getLogger().log(Level.SEVERE, "Could not write heap", t);
+ return null;
+ }
+ }
private Iterable<? extends net.kyori.adventure.audience.Audience> adventure$audiences;
@Override
public Iterable<? extends net.kyori.adventure.audience.Audience> audiences() {

View file

@ -1,731 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zach Brown <zach.brown@destroystokyo.com>
Date: Fri, 24 Mar 2017 23:56:01 -0500
Subject: [PATCH] Paper Metrics
Removes Spigot's mcstats metrics in favor of a system using bStats
To disable for privacy or other reasons go to the bStats folder in your plugins folder
and edit the config.yml file present there.
Please keep in mind the data collected is anonymous and collection should have no
tangible effect on server performance. The data is used to allow the authors of
PaperMC to track version and platform usage so that we can make better management
decisions on behalf of the project.
diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java
new file mode 100644
index 0000000000000000000000000000000000000000..6aaed8e8bf8c721fc834da5c76ac72a4c3e92458
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/Metrics.java
@@ -0,0 +1,678 @@
+package com.destroystokyo.paper;
+
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.craftbukkit.util.CraftMagicNumbers;
+import org.bukkit.plugin.Plugin;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+
+import javax.net.ssl.HttpsURLConnection;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.*;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * bStats collects some data for plugin authors.
+ *
+ * Check out https://bStats.org/ to learn more about bStats!
+ */
+public class Metrics {
+
+ // Executor service for requests
+ // We use an executor service because the Bukkit scheduler is affected by server lags
+ private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
+
+ // The version of this bStats class
+ public static final int B_STATS_VERSION = 1;
+
+ // The url to which the data is sent
+ private static final String URL = "https://bStats.org/submitData/server-implementation";
+
+ // Should failed requests be logged?
+ private static boolean logFailedRequests = false;
+
+ // The logger for the failed requests
+ private static Logger logger = Logger.getLogger("bStats");
+
+ // The name of the server software
+ private final String name;
+
+ // The uuid of the server
+ private final String serverUUID;
+
+ // A list with all custom charts
+ private final List<CustomChart> charts = new ArrayList<>();
+
+ /**
+ * Class constructor.
+ *
+ * @param name The name of the server software.
+ * @param serverUUID The uuid of the server.
+ * @param logFailedRequests Whether failed requests should be logged or not.
+ * @param logger The logger for the failed requests.
+ */
+ public Metrics(String name, String serverUUID, boolean logFailedRequests, Logger logger) {
+ this.name = name;
+ this.serverUUID = serverUUID;
+ Metrics.logFailedRequests = logFailedRequests;
+ Metrics.logger = logger;
+
+ // Start submitting the data
+ startSubmitting();
+ }
+
+ /**
+ * Adds a custom chart.
+ *
+ * @param chart The chart to add.
+ */
+ public void addCustomChart(CustomChart chart) {
+ if (chart == null) {
+ throw new IllegalArgumentException("Chart cannot be null!");
+ }
+ charts.add(chart);
+ }
+
+ /**
+ * Starts the Scheduler which submits our data every 30 minutes.
+ */
+ private void startSubmitting() {
+ final Runnable submitTask = this::submitData;
+
+ // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution of requests on the
+ // bStats backend. To circumvent this problem, we introduce some randomness into the initial and second delay.
+ // WARNING: You must not modify any part of this Metrics class, including the submit delay or frequency!
+ // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it!
+ long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3));
+ long secondDelay = (long) (1000 * 60 * (Math.random() * 30));
+ scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS);
+ scheduler.scheduleAtFixedRate(submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Gets the plugin specific data.
+ *
+ * @return The plugin specific data.
+ */
+ private JSONObject getPluginData() {
+ JSONObject data = new JSONObject();
+
+ data.put("pluginName", name); // Append the name of the server software
+ JSONArray customCharts = new JSONArray();
+ for (CustomChart customChart : charts) {
+ // Add the data of the custom charts
+ JSONObject chart = customChart.getRequestJsonObject();
+ if (chart == null) { // If the chart is null, we skip it
+ continue;
+ }
+ customCharts.add(chart);
+ }
+ data.put("customCharts", customCharts);
+
+ return data;
+ }
+
+ /**
+ * Gets the server specific data.
+ *
+ * @return The server specific data.
+ */
+ private JSONObject getServerData() {
+ // OS specific data
+ String osName = System.getProperty("os.name");
+ String osArch = System.getProperty("os.arch");
+ String osVersion = System.getProperty("os.version");
+ int coreCount = Runtime.getRuntime().availableProcessors();
+
+ JSONObject data = new JSONObject();
+
+ data.put("serverUUID", serverUUID);
+
+ data.put("osName", osName);
+ data.put("osArch", osArch);
+ data.put("osVersion", osVersion);
+ data.put("coreCount", coreCount);
+
+ return data;
+ }
+
+ /**
+ * Collects the data and sends it afterwards.
+ */
+ private void submitData() {
+ final JSONObject data = getServerData();
+
+ JSONArray pluginData = new JSONArray();
+ pluginData.add(getPluginData());
+ data.put("plugins", pluginData);
+
+ try {
+ // We are still in the Thread of the timer, so nothing get blocked :)
+ sendData(data);
+ } catch (Exception e) {
+ // Something went wrong! :(
+ if (logFailedRequests) {
+ logger.log(Level.WARNING, "Could not submit stats of " + name, e);
+ }
+ }
+ }
+
+ /**
+ * Sends the data to the bStats server.
+ *
+ * @param data The data to send.
+ * @throws Exception If the request failed.
+ */
+ private static void sendData(JSONObject data) throws Exception {
+ if (data == null) {
+ throw new IllegalArgumentException("Data cannot be null!");
+ }
+ HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
+
+ // Compress the data to save bandwidth
+ byte[] compressedData = compress(data.toString());
+
+ // Add headers
+ connection.setRequestMethod("POST");
+ connection.addRequestProperty("Accept", "application/json");
+ connection.addRequestProperty("Connection", "close");
+ connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request
+ connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
+ connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format
+ connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);
+
+ // Send data
+ connection.setDoOutput(true);
+ DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
+ outputStream.write(compressedData);
+ outputStream.flush();
+ outputStream.close();
+
+ connection.getInputStream().close(); // We don't care about the response - Just send our data :)
+ }
+
+ /**
+ * Gzips the given String.
+ *
+ * @param str The string to gzip.
+ * @return The gzipped String.
+ * @throws IOException If the compression failed.
+ */
+ private static byte[] compress(final String str) throws IOException {
+ if (str == null) {
+ return null;
+ }
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
+ gzip.write(str.getBytes("UTF-8"));
+ gzip.close();
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Represents a custom chart.
+ */
+ public static abstract class CustomChart {
+
+ // The id of the chart
+ final String chartId;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ */
+ CustomChart(String chartId) {
+ if (chartId == null || chartId.isEmpty()) {
+ throw new IllegalArgumentException("ChartId cannot be null or empty!");
+ }
+ this.chartId = chartId;
+ }
+
+ private JSONObject getRequestJsonObject() {
+ JSONObject chart = new JSONObject();
+ chart.put("chartId", chartId);
+ try {
+ JSONObject data = getChartData();
+ if (data == null) {
+ // If the data is null we don't send the chart.
+ return null;
+ }
+ chart.put("data", data);
+ } catch (Throwable t) {
+ if (logFailedRequests) {
+ logger.log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t);
+ }
+ return null;
+ }
+ return chart;
+ }
+
+ protected abstract JSONObject getChartData() throws Exception;
+
+ }
+
+ /**
+ * Represents a custom simple pie.
+ */
+ public static class SimplePie extends CustomChart {
+
+ private final Callable<String> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public SimplePie(String chartId, Callable<String> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ String value = callable.call();
+ if (value == null || value.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("value", value);
+ return data;
+ }
+ }
+
+ /**
+ * Represents a custom advanced pie.
+ */
+ public static class AdvancedPie extends CustomChart {
+
+ private final Callable<Map<String, Integer>> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ JSONObject values = new JSONObject();
+ Map<String, Integer> map = callable.call();
+ if (map == null || map.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ boolean allSkipped = true;
+ for (Map.Entry<String, Integer> entry : map.entrySet()) {
+ if (entry.getValue() == 0) {
+ continue; // Skip this invalid
+ }
+ allSkipped = false;
+ values.put(entry.getKey(), entry.getValue());
+ }
+ if (allSkipped) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("values", values);
+ return data;
+ }
+ }
+
+ /**
+ * Represents a custom drilldown pie.
+ */
+ public static class DrilldownPie extends CustomChart {
+
+ private final Callable<Map<String, Map<String, Integer>>> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ public JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ JSONObject values = new JSONObject();
+ Map<String, Map<String, Integer>> map = callable.call();
+ if (map == null || map.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ boolean reallyAllSkipped = true;
+ for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
+ JSONObject value = new JSONObject();
+ boolean allSkipped = true;
+ for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
+ value.put(valueEntry.getKey(), valueEntry.getValue());
+ allSkipped = false;
+ }
+ if (!allSkipped) {
+ reallyAllSkipped = false;
+ values.put(entryValues.getKey(), value);
+ }
+ }
+ if (reallyAllSkipped) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("values", values);
+ return data;
+ }
+ }
+
+ /**
+ * Represents a custom single line chart.
+ */
+ public static class SingleLineChart extends CustomChart {
+
+ private final Callable<Integer> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public SingleLineChart(String chartId, Callable<Integer> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ int value = callable.call();
+ if (value == 0) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("value", value);
+ return data;
+ }
+
+ }
+
+ /**
+ * Represents a custom multi line chart.
+ */
+ public static class MultiLineChart extends CustomChart {
+
+ private final Callable<Map<String, Integer>> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ JSONObject values = new JSONObject();
+ Map<String, Integer> map = callable.call();
+ if (map == null || map.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ boolean allSkipped = true;
+ for (Map.Entry<String, Integer> entry : map.entrySet()) {
+ if (entry.getValue() == 0) {
+ continue; // Skip this invalid
+ }
+ allSkipped = false;
+ values.put(entry.getKey(), entry.getValue());
+ }
+ if (allSkipped) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("values", values);
+ return data;
+ }
+
+ }
+
+ /**
+ * Represents a custom simple bar chart.
+ */
+ public static class SimpleBarChart extends CustomChart {
+
+ private final Callable<Map<String, Integer>> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ JSONObject values = new JSONObject();
+ Map<String, Integer> map = callable.call();
+ if (map == null || map.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ for (Map.Entry<String, Integer> entry : map.entrySet()) {
+ JSONArray categoryValues = new JSONArray();
+ categoryValues.add(entry.getValue());
+ values.put(entry.getKey(), categoryValues);
+ }
+ data.put("values", values);
+ return data;
+ }
+
+ }
+
+ /**
+ * Represents a custom advanced bar chart.
+ */
+ public static class AdvancedBarChart extends CustomChart {
+
+ private final Callable<Map<String, int[]>> callable;
+
+ /**
+ * Class constructor.
+ *
+ * @param chartId The id of the chart.
+ * @param callable The callable which is used to request the chart data.
+ */
+ public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
+ super(chartId);
+ this.callable = callable;
+ }
+
+ @Override
+ protected JSONObject getChartData() throws Exception {
+ JSONObject data = new JSONObject();
+ JSONObject values = new JSONObject();
+ Map<String, int[]> map = callable.call();
+ if (map == null || map.isEmpty()) {
+ // Null = skip the chart
+ return null;
+ }
+ boolean allSkipped = true;
+ for (Map.Entry<String, int[]> entry : map.entrySet()) {
+ if (entry.getValue().length == 0) {
+ continue; // Skip this invalid
+ }
+ allSkipped = false;
+ JSONArray categoryValues = new JSONArray();
+ for (int categoryValue : entry.getValue()) {
+ categoryValues.add(categoryValue);
+ }
+ values.put(entry.getKey(), categoryValues);
+ }
+ if (allSkipped) {
+ // Null = skip the chart
+ return null;
+ }
+ data.put("values", values);
+ return data;
+ }
+
+ }
+
+ public static class PaperMetrics {
+ public static void startMetrics() {
+ // Get the config file
+ File configFile = new File(new File((File) MinecraftServer.getServer().options.valueOf("plugins"), "bStats"), "config.yml");
+ YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
+
+ // Check if the config file exists
+ if (!config.isSet("serverUuid")) {
+
+ // Add default values
+ config.addDefault("enabled", true);
+ // Every server gets it's unique random id.
+ config.addDefault("serverUuid", UUID.randomUUID().toString());
+ // Should failed request be logged?
+ config.addDefault("logFailedRequests", false);
+
+ // Inform the server owners about bStats
+ config.options().header(
+ "bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
+ "To honor their work, you should not disable it.\n" +
+ "This has nearly no effect on the server performance!\n" +
+ "Check out https://bStats.org/ to learn more :)"
+ ).copyDefaults(true);
+ try {
+ config.save(configFile);
+ } catch (IOException ignored) {
+ }
+ }
+ // Load the data
+ String serverUUID = config.getString("serverUuid");
+ boolean logFailedRequests = config.getBoolean("logFailedRequests", false);
+ // Only start Metrics, if it's enabled in the config
+ if (config.getBoolean("enabled", true)) {
+ Metrics metrics = new Metrics("Paper", serverUUID, logFailedRequests, Bukkit.getLogger());
+
+ metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> {
+ String minecraftVersion = Bukkit.getVersion();
+ minecraftVersion = minecraftVersion.substring(minecraftVersion.indexOf("MC: ") + 4, minecraftVersion.length() - 1);
+ return minecraftVersion;
+ }));
+
+ metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size()));
+ metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline"));
+ final String paperVersion;
+ final String implVersion = org.bukkit.craftbukkit.Main.class.getPackage().getImplementationVersion();
+ if (implVersion != null) {
+ final String buildOrHash = implVersion.substring(implVersion.lastIndexOf('-') + 1);
+ paperVersion = "git-Paper-%s-%s".formatted(Bukkit.getServer().getMinecraftVersion(), buildOrHash);
+ } else {
+ paperVersion = "unknown";
+ }
+ metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> paperVersion));
+
+ metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> {
+ Map<String, Map<String, Integer>> map = new HashMap<>();
+ String javaVersion = System.getProperty("java.version");
+ Map<String, Integer> entry = new HashMap<>();
+ entry.put(javaVersion, 1);
+
+ // http://openjdk.java.net/jeps/223
+ // Java decided to change their versioning scheme and in doing so modified the java.version system
+ // property to return $major[.$minor][.$secuity][-ea], as opposed to 1.$major.0_$identifier
+ // we can handle pre-9 by checking if the "major" is equal to "1", otherwise, 9+
+ String majorVersion = javaVersion.split("\\.")[0];
+ String release;
+
+ int indexOf = javaVersion.lastIndexOf('.');
+
+ if (majorVersion.equals("1")) {
+ release = "Java " + javaVersion.substring(0, indexOf);
+ } else {
+ // of course, it really wouldn't be all that simple if they didn't add a quirk, now would it
+ // valid strings for the major may potentially include values such as -ea to deannotate a pre release
+ Matcher versionMatcher = Pattern.compile("\\d+").matcher(majorVersion);
+ if (versionMatcher.find()) {
+ majorVersion = versionMatcher.group(0);
+ }
+ release = "Java " + majorVersion;
+ }
+ map.put(release, entry);
+
+ return map;
+ }));
+
+ metrics.addCustomChart(new Metrics.DrilldownPie("legacy_plugins", () -> {
+ Map<String, Map<String, Integer>> map = new HashMap<>();
+
+ // count legacy plugins
+ int legacy = 0;
+ for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
+ if (CraftMagicNumbers.isLegacy(plugin.getDescription())) {
+ legacy++;
+ }
+ }
+
+ // insert real value as lower dimension
+ Map<String, Integer> entry = new HashMap<>();
+ entry.put(String.valueOf(legacy), 1);
+
+ // create buckets as higher dimension
+ if (legacy == 0) {
+ map.put("0 \uD83D\uDE0E", entry); // :sunglasses:
+ } else if (legacy <= 5) {
+ map.put("1-5", entry);
+ } else if (legacy <= 10) {
+ map.put("6-10", entry);
+ } else if (legacy <= 25) {
+ map.put("11-25", entry);
+ } else if (legacy <= 50) {
+ map.put("26-50", entry);
+ } else {
+ map.put("50+ \uD83D\uDE2D", entry); // :cry:
+ }
+
+ return map;
+ }));
+ }
+
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index 9c950fc1de15b5039e34a9fdf893e97a8cc13237..6ba90739c20995362a5275e2259cac9e17fbcf59 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -216,6 +216,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
this.paperConfigurations.initializeWorldDefaultsConfiguration(this.registryAccess());
// 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
this.setPvpAllowed(dedicatedserverproperties.pvp);
this.setFlightAllowed(dedicatedserverproperties.allowFlight);
diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java
index b717c9d8b6edc2cafc9281140913b7bdb6108cf0..ba621fdc82896245f6ce448e084847edc4d3fe08 100644
--- a/src/main/java/org/spigotmc/SpigotConfig.java
+++ b/src/main/java/org/spigotmc/SpigotConfig.java
@@ -83,6 +83,7 @@ public class SpigotConfig
MinecraftServer.getServer().server.getCommandMap().register( entry.getKey(), "Spigot", entry.getValue() );
}
+ /* // Paper - Replace with our own
if ( SpigotConfig.metrics == null )
{
try
@@ -94,6 +95,7 @@ public class SpigotConfig
Bukkit.getServer().getLogger().log( Level.SEVERE, "Could not start metrics service", ex );
}
}
+ */ // Paper end
}
public static void readConfig(Class<?> clazz, Object instance) // Paper - package-private -> public

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,185 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zach Brown <zach@zachbr.io>
Date: Wed, 3 Oct 2018 20:09:18 -0400
Subject: [PATCH] Hook into CB plugin rewrites
Allows us to do fun stuff like rewrite the OBC util fastutil location to
our own relocation. Also lets us rewrite NMS calls for when we're
debugging in an IDE pre-relocate.
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
index b60b715af8c3259aed8d386a5165653e0b6ed667..2a29f60c3e82239ab7acd85242fc3390cb9129cd 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
@@ -12,6 +12,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -22,6 +23,7 @@ import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
+import javax.annotation.Nonnull;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
@@ -123,6 +125,40 @@ public class Commodore {
return this.reroutes;
}
+ // Paper start - Plugin rewrites
+ private static final Map<String, String> SEARCH_AND_REMOVE = initReplacementsMap();
+ private static Map<String, String> initReplacementsMap() {
+ Map<String, String> getAndRemove = new HashMap<>();
+ // Be wary of maven shade's relocations
+
+ final java.util.jar.Manifest manifest = io.papermc.paper.util.JarManifests.manifest(Commodore.class);
+ if (Boolean.getBoolean( "debug.rewriteForIde") && manifest != null)
+ {
+ // unversion incoming calls for pre-relocate debug work
+ final String NMS_REVISION_PACKAGE = "v" + manifest.getMainAttributes().getValue("CraftBukkit-Package-Version") + "/";
+
+ getAndRemove.put("org/bukkit/".concat("craftbukkit/" + NMS_REVISION_PACKAGE), NMS_REVISION_PACKAGE);
+ }
+
+ return getAndRemove;
+ }
+
+ @Nonnull
+ private static String getOriginalOrRewrite(@Nonnull String original)
+ {
+ String rewrite = null;
+ for ( Map.Entry<String, String> entry : SEARCH_AND_REMOVE.entrySet() )
+ {
+ if ( original.contains( entry.getKey() ) )
+ {
+ rewrite = original.replace( entry.getValue(), "" );
+ }
+ }
+
+ return rewrite != null ? rewrite : original;
+ }
+ // Paper end - Plugin rewrites
+
public static void main(String[] args) {
OptionParser parser = new OptionParser();
OptionSpec<File> inputFlag = parser.acceptsAll(Arrays.asList("i", "input")).withRequiredArg().ofType(File.class).required();
@@ -278,9 +314,49 @@ public class Commodore {
}
return new MethodVisitor(this.api, super.visitMethod(access, name, desc, signature, exceptions)) {
+ // Paper start - Plugin rewrites
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ type = getOriginalOrRewrite(type);
+
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ for (int i = 0; i < local.length; i++)
+ {
+ if (!(local[i] instanceof String)) { continue; }
+
+ local[i] = getOriginalOrRewrite((String) local[i]);
+ }
+
+ for (int i = 0; i < stack.length; i++)
+ {
+ if (!(stack[i] instanceof String)) { continue; }
+
+ stack[i] = getOriginalOrRewrite((String) stack[i]);
+ }
+
+ super.visitFrame(type, nLocal, local, nStack, stack);
+ }
+
+ @Override
+ public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) {
+ descriptor = getOriginalOrRewrite(descriptor);
+
+ super.visitLocalVariable(name, descriptor, signature, start, end, index);
+ }
+ // Paper end - Plugin rewrites
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ // Paper start - Rewrite plugins
+ owner = getOriginalOrRewrite(owner);
+ if (desc != null) {
+ desc = getOriginalOrRewrite(desc);
+ }
+ // Paper end
name = FieldRename.rename(pluginVersion, owner, name);
if (modern) {
@@ -393,6 +469,13 @@ public class Commodore {
return;
}
+ // Paper start - Rewrite plugins
+ owner = getOriginalOrRewrite(owner) ;
+ if (desc != null) {
+ desc = getOriginalOrRewrite(desc);
+ }
+ // Paper end - Rewrite plugins
+
if (modern) {
if (owner.equals("org/bukkit/Material") || (instantiatedMethodType != null && instantiatedMethodType.getDescriptor().startsWith("(Lorg/bukkit/Material;)"))) {
switch (name) {
@@ -489,6 +572,13 @@ public class Commodore {
@Override
public void visitLdcInsn(Object value) {
+ // Paper start
+ if (value instanceof Type type) {
+ if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
+ value = Type.getType(getOriginalOrRewrite(type.getDescriptor()));
+ }
+ }
+ // Paper end
if (value instanceof String && ((String) value).equals("com.mysql.jdbc.Driver")) {
super.visitLdcInsn("com.mysql.cj.jdbc.Driver");
return;
@@ -499,6 +589,14 @@ public class Commodore {
@Override
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
+ // Paper start - Rewrite plugins
+ name = getOriginalOrRewrite(name);
+ if (descriptor != null) {
+ descriptor = getOriginalOrRewrite(descriptor);
+ }
+ final String fName = name;
+ final String fDescriptor = descriptor;
+ // Paper end - Rewrite plugins
if (bootstrapMethodHandle.getOwner().equals("java/lang/invoke/LambdaMetafactory")
&& bootstrapMethodHandle.getName().equals("metafactory") && bootstrapMethodArguments.length == 3) {
Type samMethodType = (Type) bootstrapMethodArguments[0];
@@ -515,7 +613,7 @@ public class Commodore {
methodArgs.add(new Handle(newOpcode, newOwner, newName, newDescription, newItf));
methodArgs.add(newInstantiated);
- super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, methodArgs.toArray(Object[]::new));
+ super.visitInvokeDynamicInsn(fName, fDescriptor, bootstrapMethodHandle, methodArgs.toArray(Object[]::new)); // Paper - use final local vars
}, implMethod.getTag(), implMethod.getOwner(), implMethod.getName(), implMethod.getDesc(), implMethod.isInterface(), samMethodType, instantiatedMethodType);
return;
}
@@ -566,6 +664,12 @@ public class Commodore {
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
+ // Paper start - Rewrite plugins
+ descriptor = getOriginalOrRewrite(descriptor);
+ if ( signature != null ) {
+ signature = getOriginalOrRewrite(signature);
+ }
+ // Paper end
return new FieldVisitor(this.api, super.visitField(access, name, descriptor, signature, value)) {
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {

View file

@ -1,740 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <nassim@njahnke.dev>
Date: Sun, 30 Oct 2022 23:47:26 +0100
Subject: [PATCH] Remap reflection calls in plugins using internals
Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
diff --git a/build.gradle.kts b/build.gradle.kts
index 381a4885c4f52c1e094af350fcb7e04b590f849a..6d8f4c3b290609d60dbcabe3d2c8274b017246c8 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -48,6 +48,12 @@ dependencies {
testImplementation("org.junit-pioneer:junit-pioneer:2.2.0") // Paper - CartesianTest
implementation("net.neoforged:srgutils:1.0.9") // Paper - mappings handling
implementation("net.neoforged:AutoRenamingTool:2.0.3") // Paper - remap plugins
+ // Paper start - Remap reflection
+ val reflectionRewriterVersion = "0.0.3"
+ implementation("io.papermc:reflection-rewriter:$reflectionRewriterVersion")
+ implementation("io.papermc:reflection-rewriter-runtime:$reflectionRewriterVersion")
+ implementation("io.papermc:reflection-rewriter-proxy-generator:$reflectionRewriterVersion")
+ // Paper end - Remap reflection
}
paperweight {
diff --git a/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java b/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java
index 893ad5e7c2d32ccd64962d95d146bbd317c28ab8..3d73ea0e63c97b2b08e719b7be7af3894fb2d4e8 100644
--- a/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java
+++ b/src/main/java/io/papermc/paper/configuration/serializer/PacketClassSerializer.java
@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableBiMap;
import com.mojang.logging.LogUtils;
import io.leangen.geantyref.TypeToken;
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
+import io.papermc.paper.util.MappingEnvironment;
import io.papermc.paper.util.ObfHelper;
import net.minecraft.network.protocol.Packet;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -69,7 +70,7 @@ public final class PacketClassSerializer extends ScalarSerializer<Class<? extend
@Override
protected @Nullable Object serialize(final Class<? extends Packet<?>> packetClass, final Predicate<Class<?>> typeSupported) {
final String name = packetClass.getName();
- @Nullable String mojName = ObfHelper.INSTANCE.mappingsByMojangName() == null ? name : MOJANG_TO_OBF.inverse().get(name); // if the mappings are null, running on moj-mapped server
+ @Nullable String mojName = ObfHelper.INSTANCE.mappingsByMojangName() == null || !MappingEnvironment.reobf() ? name : MOJANG_TO_OBF.inverse().get(name); // if the mappings are null, running on moj-mapped server
if (mojName == null && MOJANG_TO_OBF.containsKey(name)) {
mojName = name;
}
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java
new file mode 100644
index 0000000000000000000000000000000000000000..1240c061c121e8d5eb9add4e5e21955ee6df9368
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/BytecodeModifyingURLClassLoader.java
@@ -0,0 +1,187 @@
+package io.papermc.paper.plugin.entrypoint.classloader;
+
+import io.papermc.paper.pluginremap.reflect.ReflectionRemapper;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.net.JarURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSigner;
+import java.security.CodeSource;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+
+import static java.util.Objects.requireNonNullElse;
+
+public final class BytecodeModifyingURLClassLoader extends URLClassLoader {
+ static {
+ ClassLoader.registerAsParallelCapable();
+ }
+
+ private static final Object MISSING_MANIFEST = new Object();
+
+ private final Function<byte[], byte[]> modifier;
+ private final Map<String, Object> manifests = new ConcurrentHashMap<>();
+
+ public BytecodeModifyingURLClassLoader(
+ final URL[] urls,
+ final ClassLoader parent,
+ final Function<byte[], byte[]> modifier
+ ) {
+ super(urls, parent);
+ this.modifier = modifier;
+ }
+
+ public BytecodeModifyingURLClassLoader(
+ final URL[] urls,
+ final ClassLoader parent
+ ) {
+ this(urls, parent, bytes -> {
+ final ClassReader classReader = new ClassReader(bytes);
+ final ClassWriter classWriter = new ClassWriter(classReader, 0);
+ final ClassVisitor visitor = ReflectionRemapper.visitor(classWriter);
+ if (visitor == classWriter) {
+ return bytes;
+ }
+ classReader.accept(visitor, 0);
+ return classWriter.toByteArray();
+ });
+ }
+
+ @Override
+ protected Class<?> findClass(final String name) throws ClassNotFoundException {
+ final Class<?> result;
+ final String path = name.replace('.', '/').concat(".class");
+ final URL url = this.findResource(path);
+ if (url != null) {
+ try {
+ result = this.defineClass(name, url);
+ } catch (final IOException e) {
+ throw new ClassNotFoundException(name, e);
+ }
+ } else {
+ result = null;
+ }
+ if (result == null) {
+ throw new ClassNotFoundException(name);
+ }
+ return result;
+ }
+
+ private Class<?> defineClass(String name, URL url) throws IOException {
+ int i = name.lastIndexOf('.');
+ if (i != -1) {
+ String pkgname = name.substring(0, i);
+ // Check if package already loaded.
+ final @Nullable Manifest man = this.manifestFor(url);
+ final URL jarUrl = URI.create(jarName(url)).toURL();
+ if (this.getAndVerifyPackage(pkgname, man, jarUrl) == null) {
+ try {
+ if (man != null) {
+ this.definePackage(pkgname, man, jarUrl);
+ } else {
+ this.definePackage(pkgname, null, null, null, null, null, null, null);
+ }
+ } catch (IllegalArgumentException iae) {
+ // parallel-capable class loaders: re-verify in case of a
+ // race condition
+ if (this.getAndVerifyPackage(pkgname, man, jarUrl) == null) {
+ // Should never happen
+ throw new AssertionError("Cannot find package " +
+ pkgname);
+ }
+ }
+ }
+ }
+ final byte[] bytes;
+ try (final InputStream is = url.openStream()) {
+ bytes = is.readAllBytes();
+ }
+
+ final byte[] modified = this.modifier.apply(bytes);
+
+ final CodeSource cs = new CodeSource(url, (CodeSigner[]) null);
+ return this.defineClass(name, modified, 0, modified.length, cs);
+ }
+
+ private Package getAndVerifyPackage(
+ String pkgname,
+ Manifest man, URL url
+ ) {
+ Package pkg = getDefinedPackage(pkgname);
+ if (pkg != null) {
+ // Package found, so check package sealing.
+ if (pkg.isSealed()) {
+ // Verify that code source URL is the same.
+ if (!pkg.isSealed(url)) {
+ throw new SecurityException(
+ "sealing violation: package " + pkgname + " is sealed");
+ }
+ } else {
+ // Make sure we are not attempting to seal the package
+ // at this code source URL.
+ if ((man != null) && this.isSealed(pkgname, man)) {
+ throw new SecurityException(
+ "sealing violation: can't seal package " + pkgname +
+ ": already loaded");
+ }
+ }
+ }
+ return pkg;
+ }
+
+ private boolean isSealed(String name, Manifest man) {
+ Attributes attr = man.getAttributes(name.replace('.', '/').concat("/"));
+ String sealed = null;
+ if (attr != null) {
+ sealed = attr.getValue(Attributes.Name.SEALED);
+ }
+ if (sealed == null) {
+ if ((attr = man.getMainAttributes()) != null) {
+ sealed = attr.getValue(Attributes.Name.SEALED);
+ }
+ }
+ return "true".equalsIgnoreCase(sealed);
+ }
+
+ private @Nullable Manifest manifestFor(final URL url) throws IOException {
+ Manifest man = null;
+ if (url.getProtocol().equals("jar")) {
+ try {
+ final Object computedManifest = this.manifests.computeIfAbsent(jarName(url), $ -> {
+ try {
+ final Manifest m = ((JarURLConnection) url.openConnection()).getManifest();
+ return requireNonNullElse(m, MISSING_MANIFEST);
+ } catch (final IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ });
+ if (computedManifest instanceof Manifest found) {
+ man = found;
+ }
+ } catch (final UncheckedIOException e) {
+ throw e.getCause();
+ } catch (final IllegalArgumentException e) {
+ throw new IOException(e);
+ }
+ }
+ return man;
+ }
+
+ private static String jarName(final URL sourceUrl) {
+ final int exclamationIdx = sourceUrl.getPath().lastIndexOf('!');
+ if (exclamationIdx != -1) {
+ return sourceUrl.getPath().substring(0, exclamationIdx);
+ }
+ throw new IllegalArgumentException("Could not find jar for URL " + sourceUrl);
+ }
+}
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java
index f9a2c55a354c877749db3f92956de802ae575788..0e734c07dbe82ba4c319a237f9e79b08b57b997f 100644
--- a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java
@@ -7,6 +7,6 @@ public class PaperClassloaderBytecodeModifier implements ClassloaderBytecodeModi
@Override
public byte[] modify(PluginMeta configuration, byte[] bytecode) {
- return bytecode;
+ return io.papermc.paper.pluginremap.reflect.ReflectionRemapper.processClass(bytecode);
}
}
diff --git a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java
index f576060c8fe872772bbafe2016fc9b83a3c095f1..f871a329eb52da077f58d0ceaaabd3349f84cad0 100644
--- a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java
+++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java
@@ -2,12 +2,12 @@ package io.papermc.paper.plugin.loader;
import io.papermc.paper.plugin.PluginInitializerManager;
import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
+import io.papermc.paper.plugin.entrypoint.classloader.BytecodeModifyingURLClassLoader;
+import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
import io.papermc.paper.plugin.loader.library.ClassPathLibrary;
import io.papermc.paper.plugin.loader.library.PaperLibraryStore;
-import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
-import org.jetbrains.annotations.NotNull;
-
+import io.papermc.paper.util.MappingEnvironment;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
@@ -17,6 +17,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarFile;
import java.util.logging.Logger;
+import org.jetbrains.annotations.NotNull;
public class PaperClasspathBuilder implements PluginClasspathBuilder {
@@ -60,7 +61,10 @@ public class PaperClasspathBuilder implements PluginClasspathBuilder {
}
try {
- return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), new URLClassLoader(urls, getClass().getClassLoader()));
+ final URLClassLoader libraryLoader = MappingEnvironment.DISABLE_PLUGIN_REMAPPING
+ ? new URLClassLoader(urls, this.getClass().getClassLoader())
+ : new BytecodeModifyingURLClassLoader(urls, this.getClass().getClassLoader());
+ return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), libraryLoader);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java
index bdd9bc8a414719b9f1d6f01f90539ddb8603a878..1bf0fa1530b8e5f94d726d0313b7a00f675b500c 100644
--- a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java
+++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java
@@ -1,9 +1,12 @@
package io.papermc.paper.plugin.provider.type.spigot;
+import io.papermc.paper.plugin.entrypoint.classloader.BytecodeModifyingURLClassLoader;
import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints;
import io.papermc.paper.plugin.provider.type.PluginTypeFactory;
+import io.papermc.paper.util.MappingEnvironment;
import org.bukkit.plugin.InvalidDescriptionException;
import org.bukkit.plugin.PluginDescriptionFile;
+import org.bukkit.plugin.java.LibraryLoader;
import org.yaml.snakeyaml.error.YAMLException;
import java.io.IOException;
@@ -15,6 +18,12 @@ import java.util.jar.JarFile;
class SpigotPluginProviderFactory implements PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> {
+ static {
+ if (!MappingEnvironment.DISABLE_PLUGIN_REMAPPING) {
+ LibraryLoader.LIBRARY_LOADER_FACTORY = BytecodeModifyingURLClassLoader::new;
+ }
+ }
+
@Override
public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws InvalidDescriptionException {
// Copied from SimplePluginManager#loadPlugins
diff --git a/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java b/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java
new file mode 100644
index 0000000000000000000000000000000000000000..92bc8e4933ff13764fa2ac7f3729216332e202c9
--- /dev/null
+++ b/src/main/java/io/papermc/paper/pluginremap/reflect/PaperReflection.java
@@ -0,0 +1,211 @@
+package io.papermc.paper.pluginremap.reflect;
+
+import com.mojang.logging.LogUtils;
+import io.papermc.paper.util.MappingEnvironment;
+import io.papermc.paper.util.ObfHelper;
+import io.papermc.reflectionrewriter.runtime.AbstractDefaultRulesReflectionProxy;
+import io.papermc.reflectionrewriter.runtime.DefineClassReflectionProxy;
+import java.lang.invoke.MethodHandles;
+import java.nio.ByteBuffer;
+import java.security.CodeSource;
+import java.security.ProtectionDomain;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.slf4j.Logger;
+
+// todo proper inheritance handling
+@SuppressWarnings("unused")
+@DefaultQualifier(NonNull.class)
+public final class PaperReflection extends AbstractDefaultRulesReflectionProxy implements DefineClassReflectionProxy {
+ // concat to avoid being rewritten by shadow
+ private static final Logger LOGGER = LogUtils.getLogger();
+ private static final String CB_PACKAGE_PREFIX = "org.bukkit.".concat("craftbukkit.");
+ private static final String LEGACY_CB_PACKAGE_PREFIX = "org.bukkit.".concat("craftbukkit.") + MappingEnvironment.LEGACY_CB_VERSION + ".";
+
+ private final DefineClassReflectionProxy defineClassProxy;
+ private final Map<String, ObfHelper.ClassMapping> mappingsByMojangName;
+ private final Map<String, ObfHelper.ClassMapping> mappingsByObfName;
+ // Reflection does not care about method return values, so this map removes the return value descriptor from the key
+ private final Map<String, Map<String, String>> strippedMethodMappings;
+
+ PaperReflection() {
+ this.defineClassProxy = DefineClassReflectionProxy.create(PaperReflection::processClass);
+ if (!MappingEnvironment.hasMappings()) {
+ this.mappingsByMojangName = Map.of();
+ this.mappingsByObfName = Map.of();
+ this.strippedMethodMappings = Map.of();
+ return;
+ }
+ final ObfHelper obfHelper = ObfHelper.INSTANCE;
+ this.mappingsByMojangName = Objects.requireNonNull(obfHelper.mappingsByMojangName(), "mappingsByMojangName");
+ this.mappingsByObfName = Objects.requireNonNull(obfHelper.mappingsByObfName(), "mappingsByObfName");
+ this.strippedMethodMappings = this.mappingsByMojangName.entrySet().stream().collect(Collectors.toUnmodifiableMap(
+ Map.Entry::getKey,
+ entry -> entry.getValue().strippedMethods()
+ ));
+ }
+
+ @Override
+ protected String mapClassName(final String name) {
+ final ObfHelper.@Nullable ClassMapping mapping = this.mappingsByObfName.get(name);
+ return mapping != null ? mapping.mojangName() : removeCraftBukkitRelocation(name);
+ }
+
+ @Override
+ protected String mapDeclaredMethodName(final Class<?> clazz, final String name, final Class<?> @Nullable ... parameterTypes) {
+ final @Nullable Map<String, String> mapping = this.strippedMethodMappings.get(clazz.getName());
+ if (mapping == null) {
+ return name;
+ }
+ return mapping.getOrDefault(strippedMethodKey(name, parameterTypes), name);
+ }
+
+ @Override
+ protected String mapMethodName(final Class<?> clazz, final String name, final Class<?> @Nullable ... parameterTypes) {
+ final @Nullable String mapped = this.findMappedMethodName(clazz, name, parameterTypes);
+ return mapped != null ? mapped : name;
+ }
+
+ @Override
+ protected String mapDeclaredFieldName(final Class<?> clazz, final String name) {
+ final ObfHelper.@Nullable ClassMapping mapping = this.mappingsByMojangName.get(clazz.getName());
+ if (mapping == null) {
+ return name;
+ }
+ return mapping.fieldsByObf().getOrDefault(name, name);
+ }
+
+ @Override
+ protected String mapFieldName(final Class<?> clazz, final String name) {
+ final @Nullable String mapped = this.findMappedFieldName(clazz, name);
+ return mapped != null ? mapped : name;
+ }
+
+ private @Nullable String findMappedMethodName(final Class<?> clazz, final String name, final Class<?> @Nullable ... parameterTypes) {
+ final Map<String, String> map = this.strippedMethodMappings.get(clazz.getName());
+ @Nullable String mapped = null;
+ if (map != null) {
+ mapped = map.get(strippedMethodKey(name, parameterTypes));
+ if (mapped != null) {
+ return mapped;
+ }
+ }
+ // JVM checks super before interfaces
+ final Class<?> superClass = clazz.getSuperclass();
+ if (superClass != null) {
+ mapped = this.findMappedMethodName(superClass, name, parameterTypes);
+ }
+ if (mapped == null) {
+ for (final Class<?> i : clazz.getInterfaces()) {
+ mapped = this.findMappedMethodName(i, name, parameterTypes);
+ if (mapped != null) {
+ break;
+ }
+ }
+ }
+ return mapped;
+ }
+
+ private @Nullable String findMappedFieldName(final Class<?> clazz, final String name) {
+ final ObfHelper.ClassMapping mapping = this.mappingsByMojangName.get(clazz.getName());
+ @Nullable String mapped = null;
+ if (mapping != null) {
+ mapped = mapping.fieldsByObf().get(name);
+ if (mapped != null) {
+ return mapped;
+ }
+ }
+ // The JVM checks super before interfaces
+ final Class<?> superClass = clazz.getSuperclass();
+ if (superClass != null) {
+ mapped = this.findMappedFieldName(superClass, name);
+ }
+ if (mapped == null) {
+ for (final Class<?> i : clazz.getInterfaces()) {
+ mapped = this.findMappedFieldName(i, name);
+ if (mapped != null) {
+ break;
+ }
+ }
+ }
+ return mapped;
+ }
+
+ private static String strippedMethodKey(final String methodName, final Class<?> @Nullable ... parameterTypes) {
+ return methodName + parameterDescriptor(parameterTypes);
+ }
+
+ private static String parameterDescriptor(final Class<?> @Nullable ... parameterTypes) {
+ if (parameterTypes == null) {
+ // Null parameterTypes is treated as an empty array
+ return "()";
+ }
+ final StringBuilder builder = new StringBuilder();
+ builder.append('(');
+ for (final Class<?> parameterType : parameterTypes) {
+ builder.append(parameterType.descriptorString());
+ }
+ builder.append(')');
+ return builder.toString();
+ }
+
+ private static String removeCraftBukkitRelocation(final String name) {
+ if (MappingEnvironment.hasMappings()) {
+ // Relocation is applied in reobf, and when mappings are present they handle the relocation
+ return name;
+ }
+ if (name.startsWith(LEGACY_CB_PACKAGE_PREFIX)) {
+ return CB_PACKAGE_PREFIX + name.substring(LEGACY_CB_PACKAGE_PREFIX.length());
+ }
+ return name;
+ }
+
+ @Override
+ public Class<?> defineClass(final Object loader, final byte[] b, final int off, final int len) throws ClassFormatError {
+ return this.defineClassProxy.defineClass(loader, b, off, len);
+ }
+
+ @Override
+ public Class<?> defineClass(final Object loader, final String name, final byte[] b, final int off, final int len) throws ClassFormatError {
+ return this.defineClassProxy.defineClass(loader, name, b, off, len);
+ }
+
+ @Override
+ public Class<?> defineClass(final Object loader, final @Nullable String name, final byte[] b, final int off, final int len, final @Nullable ProtectionDomain protectionDomain) throws ClassFormatError {
+ return this.defineClassProxy.defineClass(loader, name, b, off, len, protectionDomain);
+ }
+
+ @Override
+ public Class<?> defineClass(final Object loader, final String name, final ByteBuffer b, final ProtectionDomain protectionDomain) throws ClassFormatError {
+ return this.defineClassProxy.defineClass(loader, name, b, protectionDomain);
+ }
+
+ @Override
+ public Class<?> defineClass(final Object secureLoader, final String name, final byte[] b, final int off, final int len, final CodeSource cs) {
+ return this.defineClassProxy.defineClass(secureLoader, name, b, off, len, cs);
+ }
+
+ @Override
+ public Class<?> defineClass(final Object secureLoader, final String name, final ByteBuffer b, final CodeSource cs) {
+ return this.defineClassProxy.defineClass(secureLoader, name, b, cs);
+ }
+
+ @Override
+ public Class<?> defineClass(final MethodHandles.Lookup lookup, final byte[] bytes) throws IllegalAccessException {
+ return this.defineClassProxy.defineClass(lookup, bytes);
+ }
+
+ // todo apply bytecode remap here as well
+ private static byte[] processClass(final byte[] bytes) {
+ try {
+ return ReflectionRemapper.processClass(bytes);
+ } catch (final Exception ex) {
+ LOGGER.warn("Failed to process class bytes", ex);
+ return bytes;
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java b/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3045afbc0cc057e99189b909367b21cf6a9e03f
--- /dev/null
+++ b/src/main/java/io/papermc/paper/pluginremap/reflect/ReflectionRemapper.java
@@ -0,0 +1,66 @@
+package io.papermc.paper.pluginremap.reflect;
+
+import io.papermc.asm.ClassInfoProvider;
+import io.papermc.asm.RewriteRuleVisitorFactory;
+import io.papermc.paper.util.MappingEnvironment;
+import io.papermc.reflectionrewriter.BaseReflectionRules;
+import io.papermc.reflectionrewriter.DefineClassRule;
+import io.papermc.reflectionrewriter.proxygenerator.ProxyGenerator;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Method;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Opcodes;
+
+@DefaultQualifier(NonNull.class)
+public final class ReflectionRemapper {
+ private static final String PAPER_REFLECTION_HOLDER = "io.papermc.paper.pluginremap.reflect.PaperReflectionHolder";
+ private static final String PAPER_REFLECTION_HOLDER_DESC = PAPER_REFLECTION_HOLDER.replace('.', '/');
+ private static final RewriteRuleVisitorFactory VISITOR_FACTORY = RewriteRuleVisitorFactory.create(
+ Opcodes.ASM9,
+ chain -> chain.then(new BaseReflectionRules(PAPER_REFLECTION_HOLDER).rules())
+ .then(DefineClassRule.create(PAPER_REFLECTION_HOLDER_DESC, true)),
+ ClassInfoProvider.basic()
+ );
+
+ static {
+ if (!MappingEnvironment.reobf()) {
+ setupProxy();
+ }
+ }
+
+ private ReflectionRemapper() {
+ }
+
+ public static ClassVisitor visitor(final ClassVisitor parent) {
+ if (MappingEnvironment.reobf() || MappingEnvironment.DISABLE_PLUGIN_REMAPPING) {
+ return parent;
+ }
+ return VISITOR_FACTORY.createVisitor(parent);
+ }
+
+ public static byte[] processClass(final byte[] bytes) {
+ if (MappingEnvironment.DISABLE_PLUGIN_REMAPPING) {
+ return bytes;
+ }
+ final ClassReader classReader = new ClassReader(bytes);
+ final ClassWriter classWriter = new ClassWriter(classReader, 0);
+ classReader.accept(ReflectionRemapper.visitor(classWriter), 0);
+ return classWriter.toByteArray();
+ }
+
+ private static void setupProxy() {
+ try {
+ final byte[] bytes = ProxyGenerator.generateProxy(PaperReflection.class, PAPER_REFLECTION_HOLDER_DESC);
+ final MethodHandles.Lookup lookup = MethodHandles.lookup();
+ final Class<?> generated = lookup.defineClass(bytes);
+ final Method init = generated.getDeclaredMethod("init", PaperReflection.class);
+ init.invoke(null, new PaperReflection());
+ } catch (final ReflectiveOperationException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+}
diff --git a/src/main/java/io/papermc/paper/util/MappingEnvironment.java b/src/main/java/io/papermc/paper/util/MappingEnvironment.java
index 8e4229634d41a42b3d93948eebb77def7c0c72b1..fb6aa9e2575b22e3089ea7fb32e6abfa6bb07d18 100644
--- a/src/main/java/io/papermc/paper/util/MappingEnvironment.java
+++ b/src/main/java/io/papermc/paper/util/MappingEnvironment.java
@@ -10,6 +10,8 @@ import org.checkerframework.framework.qual.DefaultQualifier;
@DefaultQualifier(NonNull.class)
public final class MappingEnvironment {
+ public static final boolean DISABLE_PLUGIN_REMAPPING = Boolean.getBoolean("paper.disablePluginRemapping");
+ public static final String LEGACY_CB_VERSION = "v1_21_R1";
private static final @Nullable String MAPPINGS_HASH = readMappingsHash();
private static final boolean REOBF = checkReobf();
diff --git a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java
index 242811578a786e3807a1a7019d472d5a68f87116..0b65fdf53124f3dd042b2363b1b8df8e1ca7de00 100644
--- a/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java
+++ b/src/main/java/io/papermc/paper/util/StacktraceDeobfuscator.java
@@ -29,6 +29,9 @@ public enum StacktraceDeobfuscator {
});
public void deobfuscateThrowable(final Throwable throwable) {
+ if (!MappingEnvironment.reobf()) {
+ return;
+ }
if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true
return;
}
@@ -44,6 +47,9 @@ public enum StacktraceDeobfuscator {
}
public StackTraceElement[] deobfuscateStacktrace(final StackTraceElement[] traceElements) {
+ if (!MappingEnvironment.reobf()) {
+ return traceElements;
+ }
if (GlobalConfiguration.get() != null && !GlobalConfiguration.get().logging.deobfuscateStacktraces) { // handle null as true
return traceElements;
}
diff --git a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
index 2a29f60c3e82239ab7acd85242fc3390cb9129cd..91c6721201b095eb32c5fd5a1aaf2cbcf3ee196d 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/Commodore.java
@@ -126,36 +126,26 @@ public class Commodore {
}
// Paper start - Plugin rewrites
- private static final Map<String, String> SEARCH_AND_REMOVE = initReplacementsMap();
- private static Map<String, String> initReplacementsMap() {
- Map<String, String> getAndRemove = new HashMap<>();
- // Be wary of maven shade's relocations
-
- final java.util.jar.Manifest manifest = io.papermc.paper.util.JarManifests.manifest(Commodore.class);
- if (Boolean.getBoolean( "debug.rewriteForIde") && manifest != null)
- {
- // unversion incoming calls for pre-relocate debug work
- final String NMS_REVISION_PACKAGE = "v" + manifest.getMainAttributes().getValue("CraftBukkit-Package-Version") + "/";
-
- getAndRemove.put("org/bukkit/".concat("craftbukkit/" + NMS_REVISION_PACKAGE), NMS_REVISION_PACKAGE);
+ private static final String CB_PACKAGE_PREFIX = "org/bukkit/".concat("craftbukkit/");
+ private static final String LEGACY_CB_PACKAGE_PREFIX = CB_PACKAGE_PREFIX + io.papermc.paper.util.MappingEnvironment.LEGACY_CB_VERSION + "/";
+ private static String runtimeCbPkgPrefix() {
+ if (io.papermc.paper.util.MappingEnvironment.reobf()) {
+ return LEGACY_CB_PACKAGE_PREFIX;
}
-
- return getAndRemove;
+ return CB_PACKAGE_PREFIX;
}
@Nonnull
private static String getOriginalOrRewrite(@Nonnull String original)
{
- String rewrite = null;
- for ( Map.Entry<String, String> entry : SEARCH_AND_REMOVE.entrySet() )
- {
- if ( original.contains( entry.getKey() ) )
- {
- rewrite = original.replace( entry.getValue(), "" );
+ // Relocation is applied in reobf, and when mappings are present they handle the relocation
+ if (!io.papermc.paper.util.MappingEnvironment.reobf() && !io.papermc.paper.util.MappingEnvironment.hasMappings()) {
+ if (original.contains(LEGACY_CB_PACKAGE_PREFIX)) {
+ original = original.replace(LEGACY_CB_PACKAGE_PREFIX, CB_PACKAGE_PREFIX);
}
}
- return rewrite != null ? rewrite : original;
+ return original;
}
// Paper end - Plugin rewrites
@@ -240,6 +230,7 @@ public class Commodore {
visitor = new LimitedClassRemapper(cw, new SimpleRemapper(Commodore.ENUM_RENAMES));
}
+ visitor = io.papermc.paper.pluginremap.reflect.ReflectionRemapper.visitor(visitor); // Paper
cr.accept(new ClassRemapper(new ClassVisitor(Opcodes.ASM9, visitor) {
final Set<RerouteMethodData> rerouteMethodData = new HashSet<>();
String className;
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
index a339a0aa6fd7cc7be810e93bc9eb192437519c75..33d3085d0d44d748fcb1fc203dfd14c9e1b4aff0 100644
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
@@ -74,6 +74,7 @@ import org.bukkit.potion.PotionType;
@SuppressWarnings("deprecation")
public final class CraftMagicNumbers implements UnsafeValues {
public static final CraftMagicNumbers INSTANCE = new CraftMagicNumbers();
+ public static final boolean DISABLE_OLD_API_SUPPORT = Boolean.getBoolean("paper.disableOldApiSupport"); // Paper
private final Commodore commodore = new Commodore();
@@ -346,7 +347,7 @@ public final class CraftMagicNumbers implements UnsafeValues {
throw new InvalidPluginException("Plugin API version " + pdf.getAPIVersion() + " is lower than the minimum allowed version. Please update or replace it.");
}
- if (toCheck.isOlderThan(ApiVersion.FLATTENING)) {
+ if (!DISABLE_OLD_API_SUPPORT && toCheck.isOlderThan(ApiVersion.FLATTENING)) { // Paper
CraftLegacy.init();
}
@@ -361,6 +362,12 @@ public final class CraftMagicNumbers implements UnsafeValues {
@Override
public byte[] processClass(PluginDescriptionFile pdf, String path, byte[] clazz) {
+ // Paper start
+ if (DISABLE_OLD_API_SUPPORT) {
+ // Make sure we still go through our reflection rewriting if needed
+ return io.papermc.paper.pluginremap.reflect.ReflectionRemapper.processClass(clazz);
+ }
+ // Paper end
try {
clazz = this.commodore.convert(clazz, pdf.getName(), ApiVersion.getOrCreateVersion(pdf.getAPIVersion()), ((CraftServer) Bukkit.getServer()).activeCompatibilities);
} catch (Exception ex) {

File diff suppressed because it is too large Load diff

View file

@ -1,233 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Tue, 1 Mar 2016 23:09:29 -0600
Subject: [PATCH] Further improve server tick loop
Improves how the catchup buffer is handled, allowing it to roll both ways
increasing the effeciency of the thread sleep so it only will sleep once.
Also increases the buffer of the catchup to ensure server stays at 20 TPS unless extreme conditions
Previous implementation did not calculate TPS correctly.
Switch to a realistic rolling average and factor in std deviation as an extra reporting variable
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 49de4625c57689a3624ed421c0b03512507c97c3..46e03617bb32e4037d700c1b3698d397bd75de5c 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -296,7 +296,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
public org.bukkit.craftbukkit.CraftServer server;
public OptionSet options;
public org.bukkit.command.ConsoleCommandSender console;
- public static int currentTick = (int) (System.currentTimeMillis() / 50);
+ public static int currentTick; // Paper - improve tick loop
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<Runnable>();
public int autosavePeriod;
public Commands vanillaCommandDispatcher;
@@ -305,7 +305,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Spigot start
public static final int TPS = 20;
public static final int TICK_TIME = 1000000000 / MinecraftServer.TPS;
- private static final int SAMPLE_INTERVAL = 100;
+ private static final int SAMPLE_INTERVAL = 20; // Paper - improve server tick loop
+ @Deprecated(forRemoval = true) // Paper
public final double[] recentTps = new double[ 3 ];
// Spigot end
public final io.papermc.paper.configuration.PaperConfigurations paperConfigurations; // Paper - add paper configuration files
@@ -1019,6 +1020,57 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
{
return ( avg * exp ) + ( tps * ( 1 - exp ) );
}
+
+ // Paper start - Further improve server tick loop
+ private static final long SEC_IN_NANO = 1000000000;
+ private static final long MAX_CATCHUP_BUFFER = TICK_TIME * TPS * 60L;
+ private long lastTick = 0;
+ private long catchupTime = 0;
+ public final RollingAverage tps1 = new RollingAverage(60);
+ public final RollingAverage tps5 = new RollingAverage(60 * 5);
+ public final RollingAverage tps15 = new RollingAverage(60 * 15);
+
+ public static class RollingAverage {
+ private final int size;
+ private long time;
+ private java.math.BigDecimal total;
+ private int index = 0;
+ private final java.math.BigDecimal[] samples;
+ private final long[] times;
+
+ RollingAverage(int size) {
+ this.size = size;
+ this.time = size * SEC_IN_NANO;
+ this.total = dec(TPS).multiply(dec(SEC_IN_NANO)).multiply(dec(size));
+ this.samples = new java.math.BigDecimal[size];
+ this.times = new long[size];
+ 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);
+ }
+ public void add(java.math.BigDecimal x, long t) {
+ time -= times[index];
+ total = total.subtract(samples[index].multiply(dec(times[index])));
+ samples[index] = x;
+ times[index] = t;
+ time += t;
+ total = total.add(x.multiply(dec(t)));
+ if (++index == size) {
+ index = 0;
+ }
+ }
+
+ 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() {
@@ -1033,7 +1085,10 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
// Spigot start
Arrays.fill( this.recentTps, 20 );
- long tickSection = Util.getMillis(), tickCount = 1;
+ // Paper start - further improve server tick loop
+ long tickSection = Util.getNanos();
+ long currentTime;
+ // Paper end - further improve server tick loop
while (this.running) {
long i;
@@ -1055,15 +1110,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
}
}
// Spigot start
- if ( tickCount++ % MinecraftServer.SAMPLE_INTERVAL == 0 )
- {
- long curTime = Util.getMillis();
- double currentTps = 1E3 / ( curTime - tickSection ) * MinecraftServer.SAMPLE_INTERVAL;
- this.recentTps[0] = MinecraftServer.calcTps( this.recentTps[0], 0.92, currentTps ); // 1/exp(5sec/1min)
- this.recentTps[1] = MinecraftServer.calcTps( this.recentTps[1], 0.9835, currentTps ); // 1/exp(5sec/5min)
- this.recentTps[2] = MinecraftServer.calcTps( this.recentTps[2], 0.9945, currentTps ); // 1/exp(5sec/15min)
- tickSection = curTime;
+ // Paper start - further improve server tick loop
+ currentTime = Util.getNanos();
+ if (++MinecraftServer.currentTick % MinecraftServer.SAMPLE_INTERVAL == 0) {
+ final long diff = currentTime - tickSection;
+ final java.math.BigDecimal currentTps = TPS_BASE.divide(new java.math.BigDecimal(diff), 30, java.math.RoundingMode.HALF_UP);
+ tps1.add(currentTps, diff);
+ tps5.add(currentTps, diff);
+ tps15.add(currentTps, diff);
+
+ // Backwards compat with bad plugins
+ this.recentTps[0] = tps1.getAverage();
+ this.recentTps[1] = tps5.getAverage();
+ this.recentTps[2] = tps15.getAverage();
+ tickSection = currentTime;
}
+ // Paper end - further improve server tick loop
// Spigot end
boolean flag = i == 0L;
@@ -1073,7 +1135,8 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
this.debugCommandProfiler = new MinecraftServer.TimeProfiler(Util.getNanos(), this.tickCount);
}
- MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit
+ //MinecraftServer.currentTick = (int) (System.currentTimeMillis() / 50); // CraftBukkit // Paper - don't overwrite current tick time
+ lastTick = currentTime;
this.nextTickTimeNanos += i;
this.startMetricsRecordingTick();
this.profiler.push("tick");
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 0fda501dedcc1f3f7d35e3b66a4b40394c4b3cb5..2c3efcf07f933278a8b897cf7b1f6f24b452a7c5 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -2672,7 +2672,11 @@ public final class CraftServer implements Server {
@Override
public double[] getTPS() {
- return new double[]{0, 0, 0}; // TODO
+ return new double[] {
+ net.minecraft.server.MinecraftServer.getServer().tps1.getAverage(),
+ net.minecraft.server.MinecraftServer.getServer().tps5.getAverage(),
+ net.minecraft.server.MinecraftServer.getServer().tps15.getAverage()
+ };
}
// Paper start - adventure sounds
diff --git a/src/main/java/org/spigotmc/TicksPerSecondCommand.java b/src/main/java/org/spigotmc/TicksPerSecondCommand.java
index d9ec48be0fdd2bfea938aa29e36b0f6ffa839ab2..9eb2823cc8f83bad2626fc77578b0162d9ed5782 100644
--- a/src/main/java/org/spigotmc/TicksPerSecondCommand.java
+++ b/src/main/java/org/spigotmc/TicksPerSecondCommand.java
@@ -15,6 +15,12 @@ public class TicksPerSecondCommand extends Command
this.usageMessage = "/tps";
this.setPermission( "bukkit.command.tps" );
}
+ // Paper start
+ private static final net.kyori.adventure.text.Component WARN_MSG = net.kyori.adventure.text.Component.text()
+ .append(net.kyori.adventure.text.Component.text("Warning: ", net.kyori.adventure.text.format.NamedTextColor.RED))
+ .append(net.kyori.adventure.text.Component.text("Memory usage on modern garbage collectors is not a stable value and it is perfectly normal to see it reach max. Please do not pay it much attention.", net.kyori.adventure.text.format.NamedTextColor.GOLD))
+ .build();
+ // Paper end
@Override
public boolean execute(CommandSender sender, String currentAlias, String[] args)
@@ -24,22 +30,40 @@ public class TicksPerSecondCommand extends Command
return true;
}
- StringBuilder sb = new StringBuilder( ChatColor.GOLD + "TPS from last 1m, 5m, 15m: " );
- for ( double tps : MinecraftServer.getServer().recentTps )
- {
- sb.append( this.format( tps ) );
- sb.append( ", " );
+ // Paper start - Further improve tick handling
+ double[] tps = org.bukkit.Bukkit.getTPS();
+ net.kyori.adventure.text.Component[] tpsAvg = new net.kyori.adventure.text.Component[tps.length];
+
+ for ( int i = 0; i < tps.length; i++) {
+ tpsAvg[i] = TicksPerSecondCommand.format( tps[i] );
+ }
+
+ net.kyori.adventure.text.TextComponent.Builder builder = net.kyori.adventure.text.Component.text();
+ builder.append(net.kyori.adventure.text.Component.text("TPS from last 1m, 5m, 15m: ", net.kyori.adventure.text.format.NamedTextColor.GOLD));
+ builder.append(net.kyori.adventure.text.Component.join(net.kyori.adventure.text.JoinConfiguration.commas(true), tpsAvg));
+ sender.sendMessage(builder.asComponent());
+ if (args.length > 0 && args[0].equals("mem") && sender.hasPermission("bukkit.command.tpsmemory")) {
+ sender.sendMessage(net.kyori.adventure.text.Component.text()
+ .append(net.kyori.adventure.text.Component.text("Current Memory Usage: ", net.kyori.adventure.text.format.NamedTextColor.GOLD))
+ .append(net.kyori.adventure.text.Component.text(((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: " + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)", net.kyori.adventure.text.format.NamedTextColor.GREEN))
+ );
+ if (!this.hasShownMemoryWarning) {
+ sender.sendMessage(WARN_MSG);
+ this.hasShownMemoryWarning = true;
+ }
}
- sender.sendMessage( sb.substring( 0, sb.length() - 2 ) );
- sender.sendMessage(ChatColor.GOLD + "Current Memory Usage: " + ChatColor.GREEN + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / (1024 * 1024)) + "/" + (Runtime.getRuntime().totalMemory() / (1024 * 1024)) + " mb (Max: "
- + (Runtime.getRuntime().maxMemory() / (1024 * 1024)) + " mb)");
+ // Paper end
return true;
}
- private String format(double tps)
+ private boolean hasShownMemoryWarning; // Paper
+ private static net.kyori.adventure.text.Component format(double tps) // Paper - Made static
{
- return ( ( tps > 18.0 ) ? ChatColor.GREEN : ( tps > 16.0 ) ? ChatColor.YELLOW : ChatColor.RED ).toString()
- + ( ( tps > 20.0 ) ? "*" : "" ) + Math.min( Math.round( tps * 100.0 ) / 100.0, 20.0 );
+ // Paper
+ net.kyori.adventure.text.format.TextColor color = ( ( tps > 18.0 ) ? net.kyori.adventure.text.format.NamedTextColor.GREEN : ( tps > 16.0 ) ? net.kyori.adventure.text.format.NamedTextColor.YELLOW : net.kyori.adventure.text.format.NamedTextColor.RED );
+ String amount = Math.min(Math.round(tps * 100.0) / 100.0, 20.0) + (tps > 21.0 ? "*" : ""); // Paper - only print * at 21, we commonly peak to 20.02 as the tick sleep is not accurate enough, stop the noise
+ return net.kyori.adventure.text.Component.text(amount, color);
+ // Paper end
}
}

View file

@ -1,65 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Tue, 18 May 2021 14:39:44 -0700
Subject: [PATCH] Add command line option to load extra plugin jars not in the
plugins folder
ex: java -jar paperclip.jar nogui -add-plugin=/path/to/plugin.jar -add-plugin=/path/to/another/plugin_jar.jar
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index 2c3efcf07f933278a8b897cf7b1f6f24b452a7c5..b8b2e582115cc42a913b03a35861fa810d774cbb 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -459,6 +459,35 @@ public final class CraftServer implements Server {
io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.INSTANCE.enter(io.papermc.paper.plugin.entrypoint.Entrypoint.PLUGIN); // Paper - replace implementation
}
+ // Paper start
+ @Override
+ public File getPluginsFolder() {
+ return this.console.getPluginsFolder();
+ }
+
+ private List<File> extraPluginJars() {
+ @SuppressWarnings("unchecked")
+ final List<File> jars = (List<File>) this.console.options.valuesOf("add-plugin");
+ final List<File> list = new ArrayList<>();
+ for (final File file : jars) {
+ if (!file.exists()) {
+ net.minecraft.server.MinecraftServer.LOGGER.warn("File '{}' specified through 'add-plugin' argument does not exist, cannot load a plugin from it!", file.getAbsolutePath());
+ continue;
+ }
+ if (!file.isFile()) {
+ net.minecraft.server.MinecraftServer.LOGGER.warn("File '{}' specified through 'add-plugin' argument is not a file, cannot load a plugin from it!", file.getAbsolutePath());
+ continue;
+ }
+ if (!file.getName().endsWith(".jar")) {
+ net.minecraft.server.MinecraftServer.LOGGER.warn("File '{}' specified through 'add-plugin' argument is not a jar file, cannot load a plugin from it!", file.getAbsolutePath());
+ continue;
+ }
+ list.add(file);
+ }
+ return list;
+ }
+ // Paper end
+
public void enablePlugins(PluginLoadOrder type) {
if (type == PluginLoadOrder.STARTUP) {
this.helpMap.clear();
diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java
index bdb8e882ec1efbe5afec9ec5a5df57bf38d4ba2b..034d68c2715b6a90f31e56f949ff3d27235a26eb 100644
--- a/src/main/java/org/bukkit/craftbukkit/Main.java
+++ b/src/main/java/org/bukkit/craftbukkit/Main.java
@@ -160,6 +160,12 @@ public class Main {
.ofType(File.class)
.defaultsTo(new File("paper.yml"))
.describedAs("Yml file");
+
+ acceptsAll(asList("add-plugin", "add-extra-plugin-jar"), "Specify paths to extra plugin jars to be loaded in addition to those in the plugins folder. This argument can be specified multiple times, once for each extra plugin jar path.")
+ .withRequiredArg()
+ .ofType(File.class)
+ .defaultsTo(new File[] {})
+ .describedAs("Jar file");
// Paper end
}
};

View file

@ -1,83 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: MiniDigger <admin@benndorf.dev>
Date: Sat, 6 Jun 2020 18:13:42 +0200
Subject: [PATCH] Support components in ItemMeta
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
index 0c73854243f7fa21d1ffdb3b4c85ee0a69c9c5e4..f6ac13f91f08498a8adda7d34518a5cfe34c15b2 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
@@ -978,11 +978,23 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
return CraftChatMessage.fromComponent(this.displayName);
}
+ // Paper start
+ @Override
+ public net.md_5.bungee.api.chat.BaseComponent[] getDisplayNameComponent() {
+ return displayName == null ? new net.md_5.bungee.api.chat.BaseComponent[0] : net.md_5.bungee.chat.ComponentSerializer.parse(CraftChatMessage.toJSON(displayName));
+ }
+ // Paper end
@Override
public final void setDisplayName(String name) {
this.displayName = CraftChatMessage.fromStringOrNull(name);
}
+ // Paper start
+ @Override
+ public void setDisplayNameComponent(net.md_5.bungee.api.chat.BaseComponent[] component) {
+ this.displayName = CraftChatMessage.fromJSON(net.md_5.bungee.chat.ComponentSerializer.toString(component));
+ }
+ // Paper end
@Override
public boolean hasDisplayName() {
return this.displayName != null;
@@ -1156,6 +1168,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
return this.lore == null ? null : new ArrayList<String>(Lists.transform(this.lore, CraftChatMessage::fromComponent));
}
+ // Paper start
+ @Override
+ public List<net.md_5.bungee.api.chat.BaseComponent[]> getLoreComponents() {
+ return this.lore == null ? null : new ArrayList<>(this.lore.stream().map(entry ->
+ net.md_5.bungee.chat.ComponentSerializer.parse(CraftChatMessage.toJSON(entry))
+ ).collect(java.util.stream.Collectors.toList()));
+ }
+ // Paper end
@Override
public void setLore(List<String> lore) {
if (lore == null || lore.isEmpty()) {
@@ -1170,6 +1190,21 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
}
}
+ // Paper start
+ @Override
+ public void setLoreComponents(List<net.md_5.bungee.api.chat.BaseComponent[]> lore) {
+ if (lore == null) {
+ this.lore = null;
+ } else {
+ if (this.lore == null) {
+ safelyAdd(lore, this.lore = new ArrayList<>(lore.size()), false);
+ } else {
+ this.lore.clear();
+ safelyAdd(lore, this.lore, false);
+ }
+ }
+ }
+ // Paper end
@Override
public boolean hasCustomModelData() {
return this.customModelData != null;
@@ -1882,6 +1917,11 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
}
for (Object object : addFrom) {
+ // Paper start - support components
+ if(object instanceof net.md_5.bungee.api.chat.BaseComponent[] baseComponentArr) {
+ addTo.add(CraftChatMessage.fromJSON(net.md_5.bungee.chat.ComponentSerializer.toString(baseComponentArr)));
+ } else
+ // Paper end
if (!(object instanceof String)) {
if (object != null) {
// SPIGOT-7399: Null check via if is important,

View file

@ -1,92 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zach Brown <zach.brown@destroystokyo.com>
Date: Tue, 1 Mar 2016 13:02:51 -0600
Subject: [PATCH] Configurable cactus bamboo and reed growth height
Bamboo - Both the minimum fully-grown height and the maximum are configurable
- Machine_Maker
diff --git a/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java b/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java
index eda75b316acd09120539c92ff8adb97d92e9523f..e2951dd077441fe9cda461a2d3ef0c0671308316 100644
--- a/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/BambooStalkBlock.java
@@ -137,7 +137,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock {
if (random.nextFloat() < (world.spigotConfig.bambooModifier / (100.0f * 3)) && world.isEmptyBlock(pos.above()) && world.getRawBrightness(pos.above(), 0) >= 9) { // Spigot - SPIGOT-7159: Better modifier resolution
int i = this.getHeightBelowUpToMax(world, pos) + 1;
- if (i < 16) {
+ if (i < world.paperConfig().maxGrowthHeight.bamboo.max) { // Paper - Configurable cactus/bamboo/reed growth height
this.growBamboo(state, world, pos, random, i);
}
}
@@ -168,7 +168,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock {
int i = this.getHeightAboveUpToMax(world, pos);
int j = this.getHeightBelowUpToMax(world, pos);
- return i + j + 1 < 16 && (Integer) world.getBlockState(pos.above(i)).getValue(BambooStalkBlock.STAGE) != 1;
+ return i + j + 1 < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && (Integer) world.getBlockState(pos.above(i)).getValue(BambooStalkBlock.STAGE) != 1; // Paper - Configurable cactus/bamboo/reed growth height
}
@Override
@@ -187,7 +187,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock {
BlockPos blockposition1 = pos.above(i);
BlockState iblockdata1 = world.getBlockState(blockposition1);
- if (k >= 16 || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here
+ if (k >= world.paperConfig().maxGrowthHeight.bamboo.max || !iblockdata1.is(Blocks.BAMBOO) || (Integer) iblockdata1.getValue(BambooStalkBlock.STAGE) == 1 || !world.isEmptyBlock(blockposition1.above())) { // CraftBukkit - If the BlockSpreadEvent was cancelled, we have no bamboo here // Paper - Configurable cactus/bamboo/reed growth height
return;
}
@@ -228,7 +228,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock {
}
int j = (Integer) state.getValue(BambooStalkBlock.AGE) != 1 && !iblockdata2.is(Blocks.BAMBOO) ? 0 : 1;
- int k = (height < 11 || random.nextFloat() >= 0.25F) && height != 15 ? 0 : 1;
+ int k = (height < world.paperConfig().maxGrowthHeight.bamboo.min || random.nextFloat() >= 0.25F) && height != (world.paperConfig().maxGrowthHeight.bamboo.max - 1) ? 0 : 1; // Paper - Configurable cactus/bamboo/reed growth height
// CraftBukkit start
if (org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockSpreadEvent(world, pos, pos.above(), (BlockState) ((BlockState) ((BlockState) this.defaultBlockState().setValue(BambooStalkBlock.AGE, j)).setValue(BambooStalkBlock.LEAVES, blockpropertybamboosize)).setValue(BambooStalkBlock.STAGE, k), 3)) {
@@ -243,7 +243,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock {
protected int getHeightAboveUpToMax(BlockGetter world, BlockPos pos) {
int i;
- for (i = 0; i < 16 && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) {
+ for (i = 0; i < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && world.getBlockState(pos.above(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper - Configurable cactus/bamboo/reed growth height
;
}
@@ -253,7 +253,7 @@ public class BambooStalkBlock extends Block implements BonemealableBlock {
protected int getHeightBelowUpToMax(BlockGetter world, BlockPos pos) {
int i;
- for (i = 0; i < 16 && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) {
+ for (i = 0; i < ((Level) world).paperConfig().maxGrowthHeight.bamboo.max && world.getBlockState(pos.below(i + 1)).is(Blocks.BAMBOO); ++i) { // Paper - Configurable cactus/bamboo/reed growth height
;
}
diff --git a/src/main/java/net/minecraft/world/level/block/CactusBlock.java b/src/main/java/net/minecraft/world/level/block/CactusBlock.java
index c7e462a187196da906aec3b528f7945afec9f6b0..fd344c5cf0d6d523abe34d5e3f8d939106942cbb 100644
--- a/src/main/java/net/minecraft/world/level/block/CactusBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/CactusBlock.java
@@ -61,7 +61,7 @@ public class CactusBlock extends Block {
;
}
- if (i < 3) {
+ if (i < world.paperConfig().maxGrowthHeight.cactus) { // Paper - Configurable cactus/bamboo/reed growth height
int j = (Integer) state.getValue(CactusBlock.AGE);
int modifier = world.spigotConfig.cactusModifier; // Spigot - SPIGOT-7159: Better modifier resolution
diff --git a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java
index f034a7dbc0124353f8cb9b2c841226e73d83423a..c48c622e92cedeaa46b929c7adfedec98dd5a3fb 100644
--- a/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java
+++ b/src/main/java/net/minecraft/world/level/block/SugarCaneBlock.java
@@ -59,7 +59,7 @@ public class SugarCaneBlock extends Block {
;
}
- if (i < 3) {
+ if (i < world.paperConfig().maxGrowthHeight.reeds) { // Paper - Configurable cactus/bamboo/reed growth heigh
int j = (Integer) state.getValue(SugarCaneBlock.AGE);
int modifier = world.spigotConfig.caneModifier; // Spigot - SPIGOT-7159: Better modifier resolution

View file

@ -1,31 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zach Brown <zach.brown@destroystokyo.com>
Date: Tue, 1 Mar 2016 13:09:16 -0600
Subject: [PATCH] Configurable baby zombie movement speed
diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
index 60a9db4131bcf69a33003b83db6117c9a7a83276..393a9c704f4637a0e8031328d2a0facef4723dd8 100644
--- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java
+++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java
@@ -75,7 +75,7 @@ import org.bukkit.event.entity.EntityTransformEvent;
public class Zombie extends Monster {
private static final ResourceLocation SPEED_MODIFIER_BABY_ID = ResourceLocation.withDefaultNamespace("baby");
- private static final AttributeModifier SPEED_MODIFIER_BABY = new AttributeModifier(Zombie.SPEED_MODIFIER_BABY_ID, 0.5D, AttributeModifier.Operation.ADD_MULTIPLIED_BASE);
+ private final AttributeModifier babyModifier = new AttributeModifier(Zombie.SPEED_MODIFIER_BABY_ID, this.level().paperConfig().entities.behavior.babyZombieMovementModifier, AttributeModifier.Operation.ADD_MULTIPLIED_BASE); // Paper - Make baby speed configurable
private static final ResourceLocation REINFORCEMENT_CALLER_CHARGE_ID = ResourceLocation.withDefaultNamespace("reinforcement_caller_charge");
private static final AttributeModifier ZOMBIE_REINFORCEMENT_CALLEE_CHARGE = new AttributeModifier(ResourceLocation.withDefaultNamespace("reinforcement_callee_charge"), -0.05000000074505806D, AttributeModifier.Operation.ADD_VALUE);
private static final ResourceLocation LEADER_ZOMBIE_BONUS_ID = ResourceLocation.withDefaultNamespace("leader_zombie_bonus");
@@ -188,9 +188,9 @@ public class Zombie extends Monster {
if (this.level() != null && !this.level().isClientSide) {
AttributeInstance attributemodifiable = this.getAttribute(Attributes.MOVEMENT_SPEED);
- attributemodifiable.removeModifier(Zombie.SPEED_MODIFIER_BABY_ID);
+ attributemodifiable.removeModifier(this.babyModifier.id()); // Paper - Make baby speed configurable
if (baby) {
- attributemodifiable.addTransientModifier(Zombie.SPEED_MODIFIER_BABY);
+ attributemodifiable.addTransientModifier(this.babyModifier); // Paper - Make baby speed configurable
}
}

View file

@ -1,30 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zach Brown <zach.brown@destroystokyo.com>
Date: Tue, 1 Mar 2016 13:14:11 -0600
Subject: [PATCH] Configurable fishing time ranges
diff --git a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
index 0acb45014039d4392988c7d853595f96e856af4a..ed43ad94ca007a54e3c32d5e17c141048eeb5835 100644
--- a/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
+++ b/src/main/java/net/minecraft/world/entity/projectile/FishingHook.java
@@ -94,6 +94,10 @@ public class FishingHook extends Projectile {
this.noCulling = true;
this.luck = Math.max(0, luckBonus);
this.lureSpeed = Math.max(0, waitTimeReductionTicks);
+ // Paper start - Configurable fishing time ranges
+ minWaitTime = world.paperConfig().fishingTimeRange.minimum;
+ maxWaitTime = world.paperConfig().fishingTimeRange.maximum;
+ // Paper end - Configurable fishing time ranges
}
public FishingHook(EntityType<? extends FishingHook> type, Level world) {
@@ -411,7 +415,7 @@ public class FishingHook extends Projectile {
} else {
// CraftBukkit start - logic to modify fishing wait time
this.timeUntilLured = Mth.nextInt(this.random, this.minWaitTime, this.maxWaitTime);
- this.timeUntilLured -= (this.applyLure) ? this.lureSpeed : 0;
+ this.timeUntilLured -= (this.applyLure) ? (this.lureSpeed >= this.maxWaitTime ? this.timeUntilLured - 1 : this.lureSpeed ) : 0; // Paper - Fix Lure infinite loop
// CraftBukkit end
}
}

View file

@ -1,47 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Zach Brown <zach.brown@destroystokyo.com>
Date: Tue, 1 Mar 2016 13:24:16 -0600
Subject: [PATCH] Allow nerfed mobs to jump
diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java
index 930b5002aa6eaa1137314f7b38fad99778b6edaa..0593d828c911c94c9833bf12b9c294e5dac1f4e8 100644
--- a/src/main/java/net/minecraft/world/entity/Mob.java
+++ b/src/main/java/net/minecraft/world/entity/Mob.java
@@ -124,6 +124,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
private final BodyRotationControl bodyRotationControl;
protected PathNavigation navigation;
public GoalSelector goalSelector;
+ @Nullable public net.minecraft.world.entity.ai.goal.FloatGoal goalFloat; // Paper - Allow nerfed mobs to jump and float
public GoalSelector targetSelector;
@Nullable
private LivingEntity target;
@@ -888,7 +889,15 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Leashab
@Override
protected final void serverAiStep() {
++this.noActionTime;
- if (!this.aware) return; // CraftBukkit
+ // Paper start - Allow nerfed mobs to jump and float
+ if (!this.aware) {
+ if (goalFloat != null) {
+ if (goalFloat.canUse()) goalFloat.tick();
+ this.getJumpControl().tick();
+ }
+ return;
+ }
+ // Paper end - Allow nerfed mobs to jump and float
ProfilerFiller gameprofilerfiller = this.level().getProfiler();
gameprofilerfiller.push("sensing");
diff --git a/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java b/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java
index 5fbb5d2bf8945a361babfe50f5f92fa46b9e8b5f..7eb0e0486203d9ad6ce89d17a4da96a7563088a4 100644
--- a/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java
+++ b/src/main/java/net/minecraft/world/entity/ai/goal/FloatGoal.java
@@ -9,6 +9,7 @@ public class FloatGoal extends Goal {
public FloatGoal(Mob mob) {
this.mob = mob;
+ if (mob.getCommandSenderWorld().paperConfig().entities.behavior.spawnerNerfedMobsShouldJump) mob.goalFloat = this; // Paper - Allow nerfed mobs to jump and float
this.setFlags(EnumSet.of(Goal.Flag.JUMP));
mob.getNavigation().setCanFloat(true);
}

Some files were not shown because too many files have changed in this diff Show more