From 10e9c5a01c0fe5a6df08c5e7fe59d9ec8500a76e Mon Sep 17 00:00:00 2001 From: Jason <11360596+jpenilla@users.noreply.github.com> Date: Mon, 21 Jun 2021 01:09:18 -0700 Subject: [PATCH] Deobfuscate stacktraces in log messages using a RewriteAppender and a custom RewritePolicy (#5926) Also replace a couple calls to `System.err` with logger usages, as traces printed with the former do not get deobfuscated. --- ...event-tile-entity-and-entity-crashes.patch | 12 +- .../0053-Add-exception-reporting-event.patch | 24 +- ...t-armor-stands-from-doing-entity-loo.patch | 4 +- ...-Optimize-Captured-TileEntity-Lookup.patch | 4 +- patches/server/0367-Anti-Xray.patch | 10 +- ...opper-searches-if-there-are-no-items.patch | 4 +- .../0404-Improved-Watchdog-Support.patch | 12 +- ...ktraces-in-log-messages-using-a-Rewr.patch | 384 ++++++++++++++++++ 8 files changed, 415 insertions(+), 39 deletions(-) create mode 100644 patches/server/0717-Deobfuscate-stacktraces-in-log-messages-using-a-Rewr.patch diff --git a/patches/server/0026-Prevent-tile-entity-and-entity-crashes.patch b/patches/server/0026-Prevent-tile-entity-and-entity-crashes.patch index 03ccde0b5..d86c11667 100644 --- a/patches/server/0026-Prevent-tile-entity-and-entity-crashes.patch +++ b/patches/server/0026-Prevent-tile-entity-and-entity-crashes.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Prevent tile entity and entity crashes diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index d29334b5dee6fe3f292f774442304b7dc6b0e2f0..d6ed061e36668ea1cbaee44c06575534336008fc 100644 +index d29334b5dee6fe3f292f774442304b7dc6b0e2f0..939f88ab37eca30e8ad08cbe2045b66ee64aa952 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -729,11 +729,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @@ -18,8 +18,8 @@ index d29334b5dee6fe3f292f774442304b7dc6b0e2f0..d6ed061e36668ea1cbaee44c06575534 - entity.fillCrashReportCategory(crashreportsystemdetails); - throw new ReportedException(crashreport); + // Paper start - Prevent tile entity and entity crashes -+ System.err.println("Entity threw exception at " + entity.level.getWorld().getName() + ":" + entity.getX() + "," + entity.getY() + "," + entity.getZ()); -+ throwable.printStackTrace(); ++ final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level.getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); ++ MinecraftServer.LOGGER.error(msg, throwable); + entity.discard(); + // Paper end } @@ -44,7 +44,7 @@ index 7b333e2d6884b272abefbc820bcce8026a4cdf7e..66ab4deedd177f507d170a61ceca4c3e } } diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 743aafec1f1c819846813688355dd90534dddb3e..21f613b8d7785b996866b5059f3cb1bc87533d2e 100644 +index 743aafec1f1c819846813688355dd90534dddb3e..5bd2172a88c95722b86959e42442e8a3ab76879c 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -1241,11 +1241,11 @@ public class LevelChunk implements ChunkAccess { @@ -57,8 +57,8 @@ index 743aafec1f1c819846813688355dd90534dddb3e..21f613b8d7785b996866b5059f3cb1bc - this.blockEntity.fillCrashReportCategory(crashreportsystemdetails); - throw new ReportedException(crashreport); + // Paper start - Prevent tile entity and entity crashes -+ System.err.println("TileEntity threw exception at " + LevelChunk.this.getLevel().getWorld().getName() + ":" + this.getPos().getX() + "," + this.getPos().getY() + "," + this.getPos().getZ()); -+ throwable.printStackTrace(); ++ final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); ++ net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); + LevelChunk.this.removeBlockEntity(this.getPos()); + // Paper end // Spigot start diff --git a/patches/server/0053-Add-exception-reporting-event.patch b/patches/server/0053-Add-exception-reporting-event.patch index 76cc7b585..f9ece81a0 100644 --- a/patches/server/0053-Add-exception-reporting-event.patch +++ b/patches/server/0053-Add-exception-reporting-event.patch @@ -108,7 +108,7 @@ index 08c5f6fd1a307c89cf8365f56314a0c6d3e89e4b..26e0f03f2e736ed6ba86e2510a7962de } diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index da8e6f5b48a83ca4178c0de439200b7952f409d9..969a5ed2484036103834dc23b57b33c11896d72f 100644 +index 718f31b74207c7ef06c1861fbc58de01cf67be3b..dda9ac55bd7b8cce055eac1f067d7ab70ed4dca7 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -1,5 +1,10 @@ @@ -122,14 +122,10 @@ index da8e6f5b48a83ca4178c0de439200b7952f409d9..969a5ed2484036103834dc23b57b33c1 import com.google.common.collect.Lists; import com.mojang.serialization.Codec; import java.io.IOException; -@@ -729,8 +734,10 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - tickConsumer.accept(entity); - } catch (Throwable throwable) { +@@ -731,6 +736,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // Paper start - Prevent tile entity and entity crashes -- System.err.println("Entity threw exception at " + entity.level.getWorld().getName() + ":" + entity.getX() + "," + entity.getY() + "," + entity.getZ()); -+ String msg = "Entity threw exception at " + entity.level.getWorld().getName() + ":" + entity.getX() + "," + entity.getY() + "," + entity.getZ(); -+ System.err.println(msg); - throwable.printStackTrace(); + final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level.getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); + MinecraftServer.LOGGER.error(msg, throwable); + getCraftServer().getPluginManager().callEvent(new ServerExceptionEvent(new ServerInternalException(msg, throwable))); entity.discard(); // Paper end @@ -155,7 +151,7 @@ index b4a7776ba9486bbca42ffb596c8a8bcdf6471ce3..59fae60116167baf989e85596334824e } diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 21f613b8d7785b996866b5059f3cb1bc87533d2e..249f39bc0c5820e313cad7813bd1087cb2e09ed6 100644 +index 5bd2172a88c95722b86959e42442e8a3ab76879c..bf5a039553a31ed6e9d9bfa64cbd435b3e551a08 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -1,6 +1,7 @@ @@ -186,14 +182,10 @@ index 21f613b8d7785b996866b5059f3cb1bc87533d2e..249f39bc0c5820e313cad7813bd1087c // CraftBukkit end } } -@@ -1242,8 +1248,10 @@ public class LevelChunk implements ChunkAccess { - gameprofilerfiller.pop(); - } catch (Throwable throwable) { +@@ -1244,6 +1250,7 @@ public class LevelChunk implements ChunkAccess { // Paper start - Prevent tile entity and entity crashes -- System.err.println("TileEntity threw exception at " + LevelChunk.this.getLevel().getWorld().getName() + ":" + this.getPos().getX() + "," + this.getPos().getY() + "," + this.getPos().getZ()); -+ String msg = "TileEntity threw exception at " + LevelChunk.this.getLevel().getWorld().getName() + ":" + this.getPos().getX() + "," + this.getPos().getY() + "," + this.getPos().getZ(); -+ System.err.println(msg); - throwable.printStackTrace(); + final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); + net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); + net.minecraft.world.level.chunk.LevelChunk.this.level.getCraftServer().getPluginManager().callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new ServerInternalException(msg, throwable))); LevelChunk.this.removeBlockEntity(this.getPos()); // Paper end diff --git a/patches/server/0232-Option-to-prevent-armor-stands-from-doing-entity-loo.patch b/patches/server/0232-Option-to-prevent-armor-stands-from-doing-entity-loo.patch index c6ce092ea..ecfe3af82 100644 --- a/patches/server/0232-Option-to-prevent-armor-stands-from-doing-entity-loo.patch +++ b/patches/server/0232-Option-to-prevent-armor-stands-from-doing-entity-loo.patch @@ -31,10 +31,10 @@ index 6d717d3852afb3a3a4bef30c68980c402bdfefff..b47b1215e685c453c3496439bb350a91 for (int i = 0; i < list.size(); ++i) { diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 08f612b7d2e861ef7e8b91e622c7a278542809f0..00e5672f4d302d5ccfe3942e61fe0c0beb782b82 100644 +index 45a9c7d5b8999d1abd7f5ef805f068f5b532dcfa..00e6ad6ce3bc8e4542284f426fd601abb669b57d 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -768,6 +768,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -767,6 +767,13 @@ public abstract class Level implements LevelAccessor, AutoCloseable { // Paper end } } diff --git a/patches/server/0323-Optimize-Captured-TileEntity-Lookup.patch b/patches/server/0323-Optimize-Captured-TileEntity-Lookup.patch index 06592ce2f..0102d7e41 100644 --- a/patches/server/0323-Optimize-Captured-TileEntity-Lookup.patch +++ b/patches/server/0323-Optimize-Captured-TileEntity-Lookup.patch @@ -10,10 +10,10 @@ Optimize to check if the captured list even has values in it, and also to just do a get call since the value can never be null. diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index c85f6fedfaf756f8b17b554bcb2d94d0f00f01f1..5d021fbaeeb2655a775053225a9614d7881331c4 100644 +index 8076ad15b67d86b38ba61d701df921b0f615fd80..5b06aa23191d5e78cee7ac167ea39446c6c25b95 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -871,9 +871,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -870,9 +870,12 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @Nullable public BlockEntity getTileEntity(BlockPos blockposition, boolean validate) { diff --git a/patches/server/0367-Anti-Xray.patch b/patches/server/0367-Anti-Xray.patch index b2296b3fa..e05eb362a 100644 --- a/patches/server/0367-Anti-Xray.patch +++ b/patches/server/0367-Anti-Xray.patch @@ -1118,7 +1118,7 @@ index badac53382c776eba3efccfda42283912d07b99a..06e581bc0c0ea4e8141f7b9610cc5a7e } diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 22db34272c6635438d11f525fb4f08e2aa631f55..fcf5c7b28e927db977ad5b851edaf68e59de8d5b 100644 +index 39997c5028fe51e2fefbd2d50353575080d92f3c..950182275f2e95c5d2ac5e715b968e86c3bdeb93 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -292,7 +292,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl @@ -1153,7 +1153,7 @@ index f4a056185990181e486f452960159a5287947382..a695e5a0c2e8846333ccb9aea499b565 public void destroyAndAck(BlockPos pos, ServerboundPlayerActionPacket.Action action, String reason) { diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 580b3d7d993751e7c04e6b7d5d4d8fa9d150d383..83673e66aab4988e912d78fb8d1a45f807e38bd9 100644 +index 6d9c74595cd8883167554e8b52008a99d6e113c3..afdbec2365db55ab968b15368ae9ce239b63f373 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -164,6 +164,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @@ -1229,7 +1229,7 @@ index 69c2454533e6f21c70792b555ec02c6bc6d169b3..2607c7ba5cf1aca5f3e5c22be2e4e8b3 @Override diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 8cc0f2a74191357ab52f30439a515709f8ab80d1..fbb82d344f654b90809582916442c520f344aa39 100644 +index 0530de6e9167419b180a3fd6e890d27d9d86c04c..2accbda808b485fa75011b7493567138b0945096 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -456,7 +456,7 @@ public class LevelChunk implements ChunkAccess { @@ -1241,7 +1241,7 @@ index 8cc0f2a74191357ab52f30439a515709f8ab80d1..fbb82d344f654b90809582916442c520 this.sections[j] = chunksection; } -@@ -1307,4 +1307,11 @@ public class LevelChunk implements ChunkAccess { +@@ -1306,4 +1306,11 @@ public class LevelChunk implements ChunkAccess { return "Level ticker for " + s + "@" + this.getPos(); } } @@ -1448,7 +1448,7 @@ index 64cb0658021866c3875d145cc4266896e57c081e..f0c537e1d6b32ecde52b3d456f0f3889 @Override public FluidState getFluidIfLoaded(BlockPos blockposition) { diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index c131f6093c395b4d9e401d3c447e7fb13c631ecf..c09a1c640075bde9f656b11258f5adbd2daa4b0b 100644 +index abd02d88911cbbbe6f770039aa0649cff85d85ee..9db83a8f2eceac41f6b86430e719bad1a12471b5 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java @@ -136,7 +136,7 @@ public class ChunkSerializer { diff --git a/patches/server/0373-Avoid-hopper-searches-if-there-are-no-items.patch b/patches/server/0373-Avoid-hopper-searches-if-there-are-no-items.patch index 43367800c..ecc0cb593 100644 --- a/patches/server/0373-Avoid-hopper-searches-if-there-are-no-items.patch +++ b/patches/server/0373-Avoid-hopper-searches-if-there-are-no-items.patch @@ -14,10 +14,10 @@ And since minecart hoppers are used _very_ rarely near we can avoid alot of sear Combined, this adds up a lot. diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index d58a7adb58abc91f66fc893ee7a7a7bded3619a8..af1cee79e27667b78413760286f517c91d1517cb 100644 +index 864b3efc014bf1c499257d01a7e8bce910ae85d0..7ffe9718188ba57cb2a7b934a40d5f893543a63a 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -990,7 +990,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -989,7 +989,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } } diff --git a/patches/server/0404-Improved-Watchdog-Support.patch b/patches/server/0404-Improved-Watchdog-Support.patch index 8aa30675c..c9b6cf333 100644 --- a/patches/server/0404-Improved-Watchdog-Support.patch +++ b/patches/server/0404-Improved-Watchdog-Support.patch @@ -299,7 +299,7 @@ index 7bf4bf5cb2c1b54a7e2733091f48f3a824336d36..dcce05d2f4ab16424db4ab103a12188e } diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index af1cee79e27667b78413760286f517c91d1517cb..c84070d05d325a8f49c3918cc112087b9c1074e9 100644 +index 7ffe9718188ba57cb2a7b934a40d5f893543a63a..c407e85bd16b8f91b262bd6528253f902cbf9af6 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java @@ -843,6 +843,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { @@ -308,10 +308,10 @@ index af1cee79e27667b78413760286f517c91d1517cb..c84070d05d325a8f49c3918cc112087b } catch (Throwable throwable) { + if (throwable instanceof ThreadDeath) throw throwable; // Paper // Paper start - Prevent tile entity and entity crashes - String msg = "Entity threw exception at " + entity.level.getWorld().getName() + ":" + entity.getX() + "," + entity.getY() + "," + entity.getZ(); - System.err.println(msg); + final String msg = String.format("Entity threw exception at %s:%s,%s,%s", entity.level.getWorld().getName(), entity.getX(), entity.getY(), entity.getZ()); + MinecraftServer.LOGGER.error(msg, throwable); diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index e19f5d5d58a8b2e4f35907e1c88224cac7f20e57..61c70e17401ec85c0a7e6e1793f6689506b8059a 100644 +index a1f763536d77065e1ce779733a505df43be225ae..5c83bc6e5fbf10989f72c7210d3528daa9966f04 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -1319,6 +1319,7 @@ public class LevelChunk implements ChunkAccess { @@ -320,8 +320,8 @@ index e19f5d5d58a8b2e4f35907e1c88224cac7f20e57..61c70e17401ec85c0a7e6e1793f66895 } catch (Throwable throwable) { + if (throwable instanceof ThreadDeath) throw throwable; // Paper // Paper start - Prevent tile entity and entity crashes - String msg = "TileEntity threw exception at " + LevelChunk.this.getLevel().getWorld().getName() + ":" + this.getPos().getX() + "," + this.getPos().getY() + "," + this.getPos().getZ(); - System.err.println(msg); + final String msg = String.format("BlockEntity threw exception at %s:%s,%s,%s", LevelChunk.this.getLevel().getWorld().getName(), this.getPos().getX(), this.getPos().getY(), this.getPos().getZ()); + net.minecraft.server.MinecraftServer.LOGGER.error(msg, throwable); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 2f7ab3142f44c657f3585c8f8a6872fbfac81768..a7d087875ec2d493ef6ed0fcdc03d89445d9008b 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java diff --git a/patches/server/0717-Deobfuscate-stacktraces-in-log-messages-using-a-Rewr.patch b/patches/server/0717-Deobfuscate-stacktraces-in-log-messages-using-a-Rewr.patch new file mode 100644 index 000000000..e21b54a39 --- /dev/null +++ b/patches/server/0717-Deobfuscate-stacktraces-in-log-messages-using-a-Rewr.patch @@ -0,0 +1,384 @@ +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 using a + RewriteAppender and a custom RewritePolicy + + +diff --git a/build.gradle.kts b/build.gradle.kts +index 04938bb10c35d2e424043adb7ed8fec2e42bb816..f427c6f5adf20f9e1b1fa4ab56041506d8240c92 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -1,8 +1,12 @@ + import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer + import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer ++import io.papermc.paperweight.tasks.BaseTask + import io.papermc.paperweight.util.Git ++import io.papermc.paperweight.util.defaultOutput ++import io.papermc.paperweight.util.openZip + import io.papermc.paperweight.util.path + import shadow.org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor.PLUGIN_CACHE_FILE ++import java.nio.file.Files + import java.text.SimpleDateFormat + import java.util.Date + import java.util.Locale +@@ -15,6 +19,14 @@ plugins { + + repositories { + maven("https://libraries.minecraft.net/") ++ // Paper start ++ maven("https://maven.quiltmc.org/repository/release/") { ++ mavenContent { ++ releasesOnly() ++ includeModule("org.quiltmc", "tiny-mappings-parser") ++ } ++ } ++ // Paper end + } + + dependencies { +@@ -52,6 +64,8 @@ dependencies { + + implementation("com.github.oshi:oshi-core:5.7.5") // Paper - fix startup delay and warning + ++ implementation("org.quiltmc:tiny-mappings-parser:0.3.0") // Paper - needed to read mappings for stacktrace deobfuscation ++ + testImplementation("io.github.classgraph:classgraph:4.8.47") // Paper - mob goal test + testImplementation("junit:junit:4.13.1") + testImplementation("org.hamcrest:hamcrest-library:1.3") +@@ -115,6 +129,44 @@ tasks.shadowJar { + transform(ModifiedLog4j2PluginsCacheFileTransformer::class.java) + } + ++// Paper start - include reobf mappings in jar for stacktrace deobfuscation ++abstract class IncludeMappings : BaseTask() { ++ @get:InputFile ++ abstract val inputJar: RegularFileProperty ++ ++ @get:InputFile ++ abstract val mappings: RegularFileProperty ++ ++ @get:OutputFile ++ abstract val outputJar: RegularFileProperty ++ ++ override fun init() { ++ outputJar.convention(defaultOutput()) ++ } ++ ++ @TaskAction ++ private fun addMappings() { ++ outputJar.get().asFile.parentFile.mkdirs() ++ inputJar.get().asFile.copyTo(outputJar.get().asFile, overwrite = true) ++ outputJar.get().path.openZip().use { fs -> ++ val dir = fs.getPath("META-INF/mappings/") ++ Files.createDirectories(dir) ++ val target = dir.resolve("reobf.tiny") ++ Files.copy(mappings.path, target) ++ } ++ } ++} ++ ++val includeMappings = tasks.register("includeMappings") { ++ inputJar.set(tasks.shadowJar.flatMap { it.archiveFile }) ++ mappings.set(tasks.reobfJar.flatMap { it.mappingsFile }) ++} ++ ++tasks.reobfJar { ++ inputJar.set(includeMappings.flatMap { it.outputJar }) ++} ++// Paper end - include reobf mappings in jar for stacktrace deobfuscation ++ + tasks.test { + exclude("org/bukkit/craftbukkit/inventory/ItemStack*Test.class") + } +diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java +index 7acf077bc131af718c7548cc29deef558c04e463..73652e403eaa419fb5b4b54bd506f246c0aa4e99 100644 +--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java ++++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java +@@ -487,6 +487,11 @@ public class PaperConfig { + enableBrigadierConsoleCompletions = getBoolean("settings.console.enable-brigadier-completions", enableBrigadierConsoleCompletions); + } + ++ public static boolean deobfuscateStacktraces = true; ++ private static void loggerSettings() { ++ deobfuscateStacktraces = getBoolean("settings.loggers.deobfuscate-stacktraces", deobfuscateStacktraces); ++ } ++ + public static int itemValidationDisplayNameLength = 8192; + public static int itemValidationLocNameLength = 8192; + public static int itemValidationLoreLineLength = 8192; +diff --git a/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java b/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..5293a59d17eeb711694965816857e66481ff0277 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/logging/StacktraceDeobfuscatingRewritePolicy.java +@@ -0,0 +1,242 @@ ++package io.papermc.paper.logging; ++ ++import com.destroystokyo.paper.PaperConfig; ++import com.google.common.base.Charsets; ++import com.google.common.collect.ImmutableMap; ++import com.mojang.datafixers.util.Pair; ++import it.unimi.dsi.fastutil.ints.IntArrayList; ++import it.unimi.dsi.fastutil.ints.IntList; ++import java.io.BufferedReader; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.InputStreamReader; ++import java.util.Collections; ++import java.util.HashMap; ++import java.util.LinkedHashMap; ++import java.util.Map; ++import net.fabricmc.mapping.tree.ClassDef; ++import net.fabricmc.mapping.tree.MethodDef; ++import net.fabricmc.mapping.tree.TinyMappingFactory; ++import net.fabricmc.mapping.tree.TinyTree; ++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; ++import org.jetbrains.annotations.Nullable; ++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; ++ ++@Plugin( ++ name = "StacktraceDeobfuscatingRewritePolicy", ++ category = Core.CATEGORY_NAME, ++ elementType = "rewritePolicy", ++ printObject = true ++) ++public final class StacktraceDeobfuscatingRewritePolicy implements RewritePolicy { ++ private static final String MOJANG_PLUS_YARN_NAMESPACE = "mojang+yarn"; ++ private static final String SPIGOT_NAMESPACE = "spigot"; ++ ++ private final @Nullable Map mappings; ++ private final Map, Map, IntList>> lineMapCache = Collections.synchronizedMap(new LinkedHashMap<>(64, 0.75f, true) { ++ @Override ++ protected boolean removeEldestEntry(final Map.Entry, Map, IntList>> eldest) { ++ return this.size() > 63; ++ } ++ }); ++ ++ StacktraceDeobfuscatingRewritePolicy() { ++ this.mappings = loadMappingsIfPresent(); ++ } ++ ++ private static @Nullable Map loadMappingsIfPresent() { ++ try (final InputStream mappingsInputStream = StacktraceDeobfuscatingRewritePolicy.class.getClassLoader().getResourceAsStream("mappings/reobf.tiny")) { ++ if (mappingsInputStream == null) { ++ return null; ++ } ++ final TinyTree tree = TinyMappingFactory.loadWithDetection(new BufferedReader(new InputStreamReader(mappingsInputStream, Charsets.UTF_8))); ++ final var builder = ImmutableMap.builder(); ++ ++ for (final ClassDef classDef : tree.getClasses()) { ++ final String obfClassName = classDef.getName(SPIGOT_NAMESPACE).replace('/', '.'); ++ final var methodMappings = ImmutableMap., MethodMapping>builder(); ++ ++ for (final MethodDef methodDef : classDef.getMethods()) { ++ final MethodMapping method = new MethodMapping( ++ methodDef.getName(SPIGOT_NAMESPACE), ++ methodDef.getName(MOJANG_PLUS_YARN_NAMESPACE), ++ methodDef.getDescriptor(SPIGOT_NAMESPACE) ++ ); ++ methodMappings.put( ++ new Pair<>(method.obfName(), method.descriptor()), ++ method ++ ); ++ } ++ ++ final ClassMapping map = new ClassMapping( ++ obfClassName, ++ classDef.getName(MOJANG_PLUS_YARN_NAMESPACE).replace('/', '.'), ++ methodMappings.build() ++ ); ++ builder.put(map.obfName(), map); ++ } ++ ++ return builder.build(); ++ } catch (final IOException ex) { ++ System.err.println("Failed to load mappings for stacktrace deobfuscation."); ++ ex.printStackTrace(); ++ return null; ++ } ++ } ++ ++ @Override ++ public @NotNull LogEvent rewrite(final @NotNull LogEvent rewrite) { ++ if (!PaperConfig.deobfuscateStacktraces) { ++ return rewrite; ++ } ++ ++ final Throwable thrown = rewrite.getThrown(); ++ if (thrown != null) { ++ this.deobfuscateThrowable(thrown); ++ } ++ return rewrite; ++ } ++ ++ public void deobfuscateThrowable(final Throwable throwable) { ++ 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 (this.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 ClassMapping classMapping = this.mappings.get(className); ++ if (classMapping == null) { ++ result[i] = element; ++ continue; ++ } ++ ++ final Pair nameDescriptorPair; ++ try { ++ final Class clazz = Class.forName(className); ++ nameDescriptorPair = this.determineMethodForLine(clazz, element.getLineNumber()); ++ } catch (final ReflectiveOperationException ex) { ++ throw new RuntimeException(ex); ++ } ++ ++ final MethodMapping methodMapping = classMapping.methodMappings().get(nameDescriptorPair); ++ ++ result[i] = new StackTraceElement( ++ element.getClassLoaderName(), ++ element.getModuleName(), ++ element.getModuleVersion(), ++ classMapping.mojangName(), ++ methodMapping != null ? methodMapping.mojangName() : methodName, ++ this.mappedFileName(classMapping.mojangName()), ++ element.getLineNumber() ++ ); ++ } ++ return result; ++ } ++ ++ private static @NotNull Map, IntList> buildLineMap(final @NotNull Class key) { ++ final Map, IntList> lineMap = new HashMap<>(); ++ final class LineCollectingMethodVisitor extends MethodVisitor { ++ private final IntList lines = new IntArrayList(); ++ private final String name; ++ private final String descriptor; ++ ++ LineCollectingMethodVisitor(String name, String descriptor) { ++ super(Opcodes.ASM9); ++ this.name = name; ++ this.descriptor = descriptor; ++ } ++ ++ @Override ++ public void visitLineNumber(int line, Label start) { ++ super.visitLineNumber(line, start); ++ this.lines.add(line); ++ } ++ ++ @Override ++ public void visitEnd() { ++ super.visitEnd(); ++ lineMap.put(new Pair<>(this.name, this.descriptor), this.lines); ++ } ++ } ++ final ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) { ++ @Override ++ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { ++ return new LineCollectingMethodVisitor(name, descriptor); ++ } ++ }; ++ try { ++ final ClassReader reader = new ClassReader(key.getName()); ++ reader.accept(classVisitor, 0); ++ } catch (final IOException ex) { ++ throw new RuntimeException(ex); ++ } ++ return lineMap; ++ } ++ ++ private @Nullable Pair determineMethodForLine(final @NotNull Class clazz, final int lineNumber) { ++ final Map, IntList> lineMap = this.lineMapCache.computeIfAbsent(clazz, StacktraceDeobfuscatingRewritePolicy::buildLineMap); ++ for (final var entry : lineMap.entrySet()) { ++ final Pair pair = entry.getKey(); ++ final IntList lines = entry.getValue(); ++ for (int i = 0, linesSize = lines.size(); i < linesSize; i++) { ++ final int num = lines.getInt(i); ++ if (num == lineNumber) { ++ return pair; ++ } ++ } ++ } ++ return null; ++ } ++ ++ private @NotNull String mappedFileName(final @NotNull 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"; ++ } ++ ++ @PluginFactory ++ public static @NotNull StacktraceDeobfuscatingRewritePolicy createPolicy() { ++ return new StacktraceDeobfuscatingRewritePolicy(); ++ } ++ ++ private record ClassMapping( ++ String obfName, ++ String mojangName, ++ Map, MethodMapping> methodMappings ++ ) { ++ } ++ ++ private record MethodMapping( ++ String obfName, ++ String mojangName, ++ String descriptor ++ ) { ++ } ++} +diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml +index 67da1aa7a21622fb231d19dede3775a282a4a12e..f91d0df569b2f1d430ea5eee5f53779902a4f32c 100644 +--- a/src/main/resources/log4j2.xml ++++ b/src/main/resources/log4j2.xml +@@ -29,15 +29,19 @@ + + + ++ ++ ++ ++ ++ ++ + + + + + + +- +- +- ++ + + +