diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ea48b1..3406ee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ ## v1.3.2 - Fix exceptions when remapping lambda methods +- Added optional remapping of every log message +- Rewrote internal log injection handling + - Now uses Log4j's exception rendering + - Removed "MC//" prefix in stacktrace, minecraft classes are now suffixed with "`~[client-intermediary.jar:?]`" ## v1.3.1 - Added support for quilt mappings below 1.19.2 - - Now goes down to 1.18.2 + - Now goes down to 1.18.2 ## v1.3.0 diff --git a/README.md b/README.md index 93d7e23..0df12f0 100644 --- a/README.md +++ b/README.md @@ -21,22 +21,22 @@ human-readable. Before > ``` -> [18:04:12] [Render thread/ERROR]: Reported exception thrown! +> [23:13:08] [Render thread/ERROR]: Reported exception thrown! > net.minecraft.class_148: Manually triggered debug crash -> at net.minecraft.class_309.method_1474(class_309.java:509) ~[client-intermediary.jar:?] -> at net.minecraft.class_310.method_1574(class_310.java:1955) ~[client-intermediary.jar:?] -> at net.minecraft.class_310.method_1523(class_310.java:1180) ~[client-intermediary.jar:?] -> at net.minecraft.class_310.method_1514(class_310.java:801) ~[client-intermediary.jar:?] -> at net.minecraft.client.main.Main.main(Main.java:237) ~[minecraft-1.19.4-client.jar:?] -> at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:462) ~[fabric-loader-0.14.18.jar:?] -> at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) ~[fabric-loader-0.14.18.jar:?] -> at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) ~[fabric-loader-0.14.18.jar:?] -> at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:88) ~[NewLaunch.jar:?] -> at org.prismlauncher.EntryPoint.listen(EntryPoint.java:126) ~[NewLaunch.jar:?] -> at org.prismlauncher.EntryPoint.main(EntryPoint.java:71) ~[NewLaunch.jar:?] +> at net.minecraft.class_309.method_1474(class_309.java:509) ~[client-intermediary.jar:?] +> at net.minecraft.class_310.method_1574(class_310.java:1955) ~[client-intermediary.jar:?] +> at net.minecraft.class_310.method_1523(class_310.java:1180) ~[client-intermediary.jar:?] +> at net.minecraft.class_310.method_1514(class_310.java:801) ~[client-intermediary.jar:?] +> at net.minecraft.client.main.Main.main(Main.java:237) ~[minecraft-1.19.4-client.jar:?] +> at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:462) ~[fabric-loader-0.14.18.jar:?] +> at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) ~[fabric-loader-0.14.18.jar:?] +> at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) ~[fabric-loader-0.14.18.jar:?] +> at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:88) ~[NewLaunch.jar:?] +> at org.prismlauncher.EntryPoint.listen(EntryPoint.java:126) ~[NewLaunch.jar:?] +> at org.prismlauncher.EntryPoint.main(EntryPoint.java:71) ~[NewLaunch.jar:?] > Caused by: java.lang.Throwable: Manually triggered debug crash -> at net.minecraft.class_309.method_1474(class_309.java:506) ~[client-intermediary.jar:?] -> ... 10 more +> at net.minecraft.class_309.method_1474(class_309.java:506) ~[client-intermediary.jar:?] +> ... 10 more > ``` @@ -44,22 +44,22 @@ human-readable. After (yarn/quilt mappings) > ``` -> [18:02:00] [Render thread/ERROR]: Reported exception thrown! -> [18:02:00] [Render thread/ERROR]: net.minecraft.util.crash.CrashException: Manually triggered debug crash -> at MC//net.minecraft.client.Keyboard.pollDebugCrash(Keyboard.java:509) -> at MC//net.minecraft.client.MinecraftClient.tick(MinecraftClient.java:1955) -> at MC//net.minecraft.client.MinecraftClient.render(MinecraftClient.java:1180) -> at MC//net.minecraft.client.MinecraftClient.run(MinecraftClient.java:801) -> at net.minecraft.client.main.Main.main(Main.java:237) -> at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:462) -> at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) -> at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) -> at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:88) -> at org.prismlauncher.EntryPoint.listen(EntryPoint.java:126) -> at org.prismlauncher.EntryPoint.main(EntryPoint.java:71) +> [23:11:25] [Render thread/ERROR]: Reported exception thrown! +> net.minecraft.util.crash.CrashException: Manually triggered debug crash +> at net.minecraft.client.Keyboard.pollDebugCrash(Keyboard.java:509) ~[client-intermediary.jar:?] +> at net.minecraft.client.MinecraftClient.tick(MinecraftClient.java:1955) ~[client-intermediary.jar:?] +> at net.minecraft.client.MinecraftClient.render(MinecraftClient.java:1180) ~[client-intermediary.jar:?] +> at net.minecraft.client.MinecraftClient.run(MinecraftClient.java:801) ~[client-intermediary.jar:?] +> at net.minecraft.client.main.Main.main(Main.java:237) ~[minecraft-1.19.4-client.jar:?] +> at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:462) ~[fabric-loader-0.14.18.jar:?] +> at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) ~[fabric-loader-0.14.18.jar:?] +> at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) ~[fabric-loader-0.14.18.jar:?] +> at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:88) ~[NewLaunch.jar:?] +> at org.prismlauncher.EntryPoint.listen(EntryPoint.java:126) ~[NewLaunch.jar:?] +> at org.prismlauncher.EntryPoint.main(EntryPoint.java:71) ~[NewLaunch.jar:?] > Caused by: java.lang.Throwable: Manually triggered debug crash -> at MC//net.minecraft.client.Keyboard.pollDebugCrash(Keyboard.java:506) -> ... 10 more +> at net.minecraft.client.Keyboard.pollDebugCrash(Keyboard.java:506) ~[client-intermediary.jar:?] +> ... 10 more > ``` @@ -67,22 +67,22 @@ human-readable. After (mojang mappings) > ``` -> [17:52:01] [Render thread/ERROR]: Reported exception thrown! -> [17:52:01] [Render thread/ERROR]: net.minecraft.ReportedException: Manually triggered debug crash -> at MC//net.minecraft.client.KeyboardHandler.tick(KeyboardHandler.java:509) -> at MC//net.minecraft.client.Minecraft.tick(Minecraft.java:1955) -> at MC//net.minecraft.client.Minecraft.runTick(Minecraft.java:1180) -> at MC//net.minecraft.client.Minecraft.run(Minecraft.java:801) -> at net.minecraft.client.main.Main.main(Main.java:237) -> at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:462) -> at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) -> at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) -> at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:88) -> at org.prismlauncher.EntryPoint.listen(EntryPoint.java:126) -> at org.prismlauncher.EntryPoint.main(EntryPoint.java:71) +> [23:04:12] [Render thread/ERROR]: Reported exception thrown! +> net.minecraft.ReportedException: Manually triggered debug crash +> at net.minecraft.client.KeyboardHandler.tick(KeyboardHandler.java:509) ~[client-intermediary.jar:?] +> at net.minecraft.client.Minecraft.tick(Minecraft.java:1955) ~[client-intermediary.jar:?] +> at net.minecraft.client.Minecraft.runTick(Minecraft.java:1180) ~[client-intermediary.jar:?] +> at net.minecraft.client.Minecraft.run(Minecraft.java:801) ~[client-intermediary.jar:?] +> at net.minecraft.client.main.Main.main(Main.java:237) ~[minecraft-1.19.4-client.jar:?] +> at net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider.launch(MinecraftGameProvider.java:462) ~[fabric-loader-0.14.18.jar:?] +> at net.fabricmc.loader.impl.launch.knot.Knot.launch(Knot.java:74) ~[fabric-loader-0.14.18.jar:?] +> at net.fabricmc.loader.impl.launch.knot.KnotClient.main(KnotClient.java:23) ~[fabric-loader-0.14.18.jar:?] +> at org.prismlauncher.launcher.impl.StandardLauncher.launch(StandardLauncher.java:88) ~[NewLaunch.jar:?] +> at org.prismlauncher.EntryPoint.listen(EntryPoint.java:126) ~[NewLaunch.jar:?] +> at org.prismlauncher.EntryPoint.main(EntryPoint.java:71) ~[NewLaunch.jar:?] > Caused by: java.lang.Throwable: Manually triggered debug crash -> at MC//net.minecraft.client.KeyboardHandler.tick(KeyboardHandler.java:506) -> ... 10 more +> at net.minecraft.client.KeyboardHandler.tick(KeyboardHandler.java:506) ~[client-intermediary.jar:?] +> ... 10 more > ``` diff --git a/src/main/java/dev/booky/stackdeobf/StackDeobfMod.java b/src/main/java/dev/booky/stackdeobf/StackDeobfMod.java index 4bfcd18..8ca2fd6 100644 --- a/src/main/java/dev/booky/stackdeobf/StackDeobfMod.java +++ b/src/main/java/dev/booky/stackdeobf/StackDeobfMod.java @@ -3,7 +3,8 @@ import dev.booky.stackdeobf.config.StackDeobfConfig; import dev.booky.stackdeobf.mappings.CachedMappings; -import dev.booky.stackdeobf.mappings.RemappingUtil; +import dev.booky.stackdeobf.util.CompatUtil; +import dev.booky.stackdeobf.util.RemappingRewritePolicy; import net.fabricmc.api.ModInitializer; import net.fabricmc.loader.api.FabricLoader; import org.apache.logging.log4j.LogManager; @@ -18,7 +19,9 @@ public void onInitialize() { StackDeobfConfig config = this.loadConfig(); if (config.hasLogInjectEnabled()) { - RemappingUtil.injectLogFilter((Logger) LogManager.getRootLogger(), config.shouldRewriteEveryLogMessage()); + CompatUtil.LOGGER.info("Injecting into root logger..."); + RemappingRewritePolicy policy = new RemappingRewritePolicy(config); + policy.inject((Logger) LogManager.getRootLogger()); } CachedMappings.init(config.getMappingProvider()); diff --git a/src/main/java/dev/booky/stackdeobf/mappings/RemappedThrowable.java b/src/main/java/dev/booky/stackdeobf/mappings/RemappedThrowable.java index 1553860..8ffcc5e 100644 --- a/src/main/java/dev/booky/stackdeobf/mappings/RemappedThrowable.java +++ b/src/main/java/dev/booky/stackdeobf/mappings/RemappedThrowable.java @@ -1,15 +1,25 @@ package dev.booky.stackdeobf.mappings; // Created by booky10 in StackDeobfuscator (18:03 20.03.23) -class RemappedThrowable extends Throwable { +import org.jetbrains.annotations.ApiStatus; +@ApiStatus.Internal +public class RemappedThrowable extends Throwable { + + private final Throwable original; private final String className; - public RemappedThrowable(String message, Throwable cause, String className) { + public RemappedThrowable(String message, Throwable cause, + Throwable original, String className) { super(message, cause); + this.original = original; this.className = className; } + public Throwable getOriginal() { + return this.original; + } + public String getClassName() { return this.className; } diff --git a/src/main/java/dev/booky/stackdeobf/mappings/RemappingUtil.java b/src/main/java/dev/booky/stackdeobf/mappings/RemappingUtil.java index b4a6a10..8ee8be9 100644 --- a/src/main/java/dev/booky/stackdeobf/mappings/RemappingUtil.java +++ b/src/main/java/dev/booky/stackdeobf/mappings/RemappingUtil.java @@ -1,13 +1,6 @@ package dev.booky.stackdeobf.mappings; // Created by booky10 in StackDeobfuscator (17:43 17.12.22) -import dev.booky.stackdeobf.util.CompatUtil; -import org.apache.logging.log4j.core.LogEvent; -import org.apache.logging.log4j.core.filter.AbstractFilter; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -20,57 +13,7 @@ public final class RemappingUtil { private RemappingUtil() { } - public static void injectLogFilter(org.apache.logging.log4j.core.Logger logger, boolean rewriteMessages) { - CompatUtil.LOGGER.info("Injecting into root logger..."); - logger.addFilter(new AbstractFilter() { - @Override - public Result filter(LogEvent event) { - // TODO: this is a horribly hacky way to rewrite messages, find something better - - if (event.getThrown() == null) { - if (!rewriteMessages) { - return Result.NEUTRAL; - } - - String message = event.getMessage().getFormattedMessage(); - String remappedMessage = RemappingUtil.remapString(message); - - if (message.equals(remappedMessage)) { - // didn't change anything, continue - return Result.NEUTRAL; - } - - logger.logIfEnabled(event.getLoggerFqcn(), event.getLevel(), event.getMarker(), remappedMessage); - - // cancel the underlying event, this needs - // to be rewritten - return Result.DENY; - } - - // we need to manually print out the stacktrace, because - // log4j also builds it manually, resulting - // in every logged exception being a "RemappedThrowable" - try (StringWriter strWriter = new StringWriter()) { - try (PrintWriter writer = new PrintWriter(strWriter)) { - RemappingUtil.remapThrowable(event.getThrown()).printStackTrace(writer); - } - - // this message doesn't need to be remapped if every message should be remapped, - // as this is handled by the above logic already - logger.logIfEnabled(event.getLoggerFqcn(), event.getLevel(), event.getMarker(), event.getMessage(), null); - - logger.logIfEnabled(event.getLoggerFqcn(), event.getLevel(), event.getMarker(), strWriter.toString()); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - - // cancel the underlying log event - return Result.DENY; - } - }); - } - - private static String remapClasses(String string) { + public static String remapClasses(String string) { return CLASS_PATTERN.matcher(string).replaceAll(result -> { int classId = Integer.parseInt(result.group(2)); String className = CachedMappings.remapClass(classId); @@ -92,7 +35,7 @@ private static String remapClasses(String string) { }); } - private static String remapMethods(String string) { + public static String remapMethods(String string) { return METHOD_PATTERN.matcher(string).replaceAll(result -> { int methodId = Integer.parseInt(result.group(1)); String methodName = CachedMappings.remapMethod(methodId); @@ -100,7 +43,7 @@ private static String remapMethods(String string) { }); } - private static String remapFields(String string) { + public static String remapFields(String string) { return FIELD_PATTERN.matcher(string).replaceAll(result -> { int fieldId = Integer.parseInt(result.group(1)); String fieldName = CachedMappings.remapField(fieldId); @@ -147,7 +90,7 @@ public static Throwable remapThrowable(Throwable throwable) { throwableName = remapClasses(throwableName); } - Throwable remapped = new RemappedThrowable(message, cause, throwableName); + Throwable remapped = new RemappedThrowable(message, cause, throwable, throwableName); remapped.setStackTrace(stackTrace); for (Throwable suppressed : throwable.getSuppressed()) { remapped.addSuppressed(remapThrowable(suppressed)); @@ -179,7 +122,16 @@ public static StackTraceElement remapStackTraceElement(StackTraceElement element methodName = remapMethods(methodName); } - return new StackTraceElement(remappedClass ? "MC" : null, element.getModuleName(), element.getModuleVersion(), + String classLoaderName = element.getClassLoaderName(); + if (remappedClass) { + if (classLoaderName == null) { + classLoaderName = "MC"; + } else { + classLoaderName += "//MC"; + } + } + + return new StackTraceElement(classLoaderName, element.getModuleName(), element.getModuleVersion(), className, methodName, fileName, element.getLineNumber()); } } diff --git a/src/main/java/dev/booky/stackdeobf/mixin/CrashReportMixin.java b/src/main/java/dev/booky/stackdeobf/mixin/CrashReportMixin.java index 10bb068..e27a98b 100644 --- a/src/main/java/dev/booky/stackdeobf/mixin/CrashReportMixin.java +++ b/src/main/java/dev/booky/stackdeobf/mixin/CrashReportMixin.java @@ -1,6 +1,7 @@ package dev.booky.stackdeobf.mixin; // Created by booky10 in StackDeobfuscator (18:35 20.03.23) +import dev.booky.stackdeobf.mappings.RemappedThrowable; import dev.booky.stackdeobf.mappings.RemappingUtil; import net.minecraft.CrashReport; import org.spongepowered.asm.mixin.Final; @@ -10,6 +11,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; @Mixin(CrashReport.class) public class CrashReportMixin { @@ -23,4 +25,20 @@ public class CrashReportMixin { public void postInit(String title, Throwable throwable, CallbackInfo ci) { this.exception = RemappingUtil.remapThrowable(throwable); } + + @Inject( + method = "getException", + at = @At("HEAD"), + cancellable = true + ) + public void preExceptionGet(CallbackInfoReturnable cir) { + // redirect calls to getException to the original, unmapped Throwable + // + // this method is called in the ReportedException, which + // caused the "RemappedThrowable" name to show up in the logger + + if (this.exception instanceof RemappedThrowable remapped) { + cir.setReturnValue(remapped.getOriginal()); + } + } } diff --git a/src/main/java/dev/booky/stackdeobf/util/Log4jRemapUtil.java b/src/main/java/dev/booky/stackdeobf/util/Log4jRemapUtil.java new file mode 100644 index 0000000..e68cd4d --- /dev/null +++ b/src/main/java/dev/booky/stackdeobf/util/Log4jRemapUtil.java @@ -0,0 +1,56 @@ +package dev.booky.stackdeobf.util; +// Created by booky10 in StackDeobfuscator (22:33 14.04.23) + +import dev.booky.stackdeobf.mappings.RemappingUtil; +import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement; +import org.apache.logging.log4j.core.impl.ThrowableProxy; + +import java.lang.reflect.Field; + +final class Log4jRemapUtil { + + private static final Field PROXY_NAME = getField(ThrowableProxy.class, "name"); + private static final Field PROXY_MESSAGE = getField(ThrowableProxy.class, "message"); + private static final Field PROXY_LOCALIZED_MESSAGE = getField(ThrowableProxy.class, "localizedMessage"); + private static final Field EXT_STACK_ELEMENT = getField(ExtendedStackTraceElement.class, "stackTraceElement"); + + private static Field getField(Class clazz, String name) { + try { + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + return field; + } catch (ReflectiveOperationException exception) { + throw new RuntimeException(exception); + } + } + + static void remapThrowableProxy(ThrowableProxy proxy) throws IllegalAccessException { + // remap throwable classname + if (proxy.getName() != null && proxy.getName().startsWith("net.minecraft.class_")) { + PROXY_NAME.set(proxy, RemappingUtil.remapClasses(proxy.getName())); + } + + // remap throwable message + if (proxy.getMessage() != null) { + PROXY_MESSAGE.set(proxy, RemappingUtil.remapString(proxy.getMessage())); + } + if (proxy.getLocalizedMessage() != null) { + PROXY_LOCALIZED_MESSAGE.set(proxy, RemappingUtil.remapString(proxy.getLocalizedMessage())); + } + + // remap throwable stack trace + for (ExtendedStackTraceElement extElement : proxy.getExtendedStackTrace()) { + StackTraceElement element = extElement.getStackTraceElement(); + element = RemappingUtil.remapStackTraceElement(element); + EXT_STACK_ELEMENT.set(extElement, element); + } + + // remap cause + suppressed throwables + if (proxy.getCauseProxy() != null) { + remapThrowableProxy(proxy.getCauseProxy()); + } + for (ThrowableProxy suppressed : proxy.getSuppressedProxies()) { + remapThrowableProxy(suppressed); + } + } +} diff --git a/src/main/java/dev/booky/stackdeobf/util/RemappingRewritePolicy.java b/src/main/java/dev/booky/stackdeobf/util/RemappingRewritePolicy.java new file mode 100644 index 0000000..31e2635 --- /dev/null +++ b/src/main/java/dev/booky/stackdeobf/util/RemappingRewritePolicy.java @@ -0,0 +1,72 @@ +package dev.booky.stackdeobf.util; +// Created by booky10 in StackDeobfuscator (15:17 14.04.23) + +import dev.booky.stackdeobf.config.StackDeobfConfig; +import dev.booky.stackdeobf.mappings.RemappingUtil; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender; +import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy; +import org.apache.logging.log4j.core.config.AppenderRef; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.LoggerConfig; +import org.apache.logging.log4j.core.impl.Log4jLogEvent; +import org.apache.logging.log4j.core.impl.ThrowableProxy; +import org.apache.logging.log4j.message.SimpleMessage; + +import java.util.Set; + +public final class RemappingRewritePolicy implements RewritePolicy { + + private final boolean rewriteMessages; + + public RemappingRewritePolicy(StackDeobfConfig config) { + this.rewriteMessages = config.shouldRewriteEveryLogMessage(); + } + + public void inject(Logger logger) { + // code mostly based on ChatGPT + + Set appenders = Set.copyOf(logger.getAppenders().values()); + for (Appender appender : appenders) { + logger.removeAppender(appender); + } + + Configuration config = logger.getContext().getConfiguration(); + LoggerConfig logCfg = config.getLoggerConfig(logger.getName()); + AppenderRef[] refs = logCfg.getAppenderRefs().toArray(AppenderRef[]::new); + + RewriteAppender appender = RewriteAppender.createAppender("StackDeobfAppender", + null, refs, config, this, null); + appender.start(); + logger.addAppender(appender); + } + + @Override + public LogEvent rewrite(LogEvent source) { + if (!this.rewriteMessages && source.getThrown() == null) { + return source; + } + + Log4jLogEvent.Builder builder = new Log4jLogEvent.Builder(source); + if (this.rewriteMessages) { + String message = source.getMessage().getFormattedMessage(); + message = RemappingUtil.remapString(message); + builder.setMessage(new SimpleMessage(message)); + } + if (source.getThrown() != null) { + // the remapping part of the logger needs to be done in the ThrowableProxy, + // otherwise the ExtendedClassInfo would be lost + + try { + ThrowableProxy proxy = new ThrowableProxy(source.getThrown()); + Log4jRemapUtil.remapThrowableProxy(proxy); + builder.setThrownProxy(proxy); + } catch (IllegalAccessException exception) { + throw new RuntimeException(exception); + } + } + return builder.build(); + } +}