diff --git a/src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java b/src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java
index a1fb61d..a9c951e 100644
--- a/src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java
+++ b/src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java
@@ -25,6 +25,8 @@ package com.falsepattern.lib.internal.asm;
import com.falsepattern.lib.internal.FPLog;
import com.falsepattern.lib.internal.Tags;
import com.falsepattern.lib.internal.impl.dependencies.DependencyLoaderImpl;
+import com.falsepattern.lib.internal.logging.CrashImprover;
+import com.falsepattern.lib.internal.logging.NotEnoughVerbosity;
import com.falsepattern.lib.mapping.MappingManager;
import com.falsepattern.lib.turboasm.MergeableTurboTransformer;
import lombok.Getter;
@@ -75,6 +77,8 @@ public class CoreLoadingPlugin implements IFMLLoadingPlugin {
FPLog.LOG.info("Scanned in " + (end - start) / 1000000 + "ms");
//Initializing the rest
MappingManager.initialize();
+ NotEnoughVerbosity.apply();
+ CrashImprover.probe();
}
@SuppressWarnings("SameParameterValue")
diff --git a/src/main/java/com/falsepattern/lib/internal/asm/FPTransformer.java b/src/main/java/com/falsepattern/lib/internal/asm/FPTransformer.java
index 5083918..32b948a 100644
--- a/src/main/java/com/falsepattern/lib/internal/asm/FPTransformer.java
+++ b/src/main/java/com/falsepattern/lib/internal/asm/FPTransformer.java
@@ -25,6 +25,7 @@ package com.falsepattern.lib.internal.asm;
import com.falsepattern.lib.StableAPI;
import com.falsepattern.lib.internal.asm.transformers.ConfigOrderTransformer;
+import com.falsepattern.lib.internal.asm.transformers.CrashReportImprover;
import com.falsepattern.lib.internal.asm.transformers.MixinPluginTransformer;
import com.falsepattern.lib.internal.asm.transformers.TypeDiscovererModuleInfoSilencer;
import com.falsepattern.lib.internal.impl.optifine.OptiFineTransformerHooksImpl;
@@ -42,6 +43,7 @@ public class FPTransformer extends MergeableTurboTransformer {
public FPTransformer() {
super(Arrays.asList(new MixinPluginTransformer(),
+ new CrashReportImprover(),
new TypeDiscovererModuleInfoSilencer(),
new ConfigOrderTransformer()));
}
diff --git a/src/main/java/com/falsepattern/lib/internal/asm/transformers/CrashReportImprover.java b/src/main/java/com/falsepattern/lib/internal/asm/transformers/CrashReportImprover.java
new file mode 100644
index 0000000..a0ea91f
--- /dev/null
+++ b/src/main/java/com/falsepattern/lib/internal/asm/transformers/CrashReportImprover.java
@@ -0,0 +1,85 @@
+/*
+ * This file is part of FalsePatternLib.
+ *
+ * Copyright (C) 2022-2024 FalsePattern
+ * All Rights Reserved
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * FalsePatternLib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FalsePatternLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FalsePatternLib. If not, see .
+ */
+
+package com.falsepattern.lib.internal.asm.transformers;
+
+import com.falsepattern.lib.internal.Tags;
+import com.falsepattern.lib.mapping.MappingManager;
+import com.falsepattern.lib.mapping.types.MappingType;
+import com.falsepattern.lib.mapping.types.NameType;
+import com.falsepattern.lib.mapping.types.UniversalClass;
+import com.falsepattern.lib.turboasm.ClassNodeHandle;
+import com.falsepattern.lib.turboasm.TurboClassTransformer;
+import lombok.val;
+import org.jetbrains.annotations.NotNull;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.InsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+
+public class CrashReportImprover implements TurboClassTransformer {
+
+ @Override
+ public String owner() {
+ return Tags.MODNAME;
+ }
+
+ @Override
+ public String name() {
+ return "CrashReportImprover";
+ }
+
+ @Override
+ public boolean shouldTransformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
+ return "net.minecraft.crash.CrashReport".equals(className);
+ }
+
+ @Override
+ public boolean transformClass(@NotNull String className, @NotNull ClassNodeHandle classNode) {
+ val cn = classNode.getNode();
+ if (cn == null)
+ return false;
+ for (val method: cn.methods) {
+ switch (method.name) {
+ case "saveToFile", "func_147149_a" -> {}
+ default -> {
+ continue;
+ }
+ }
+ val insnList = method.instructions.iterator();
+ while (insnList.hasNext()) {
+ val insn = insnList.next();
+ if ( insn.getOpcode() == Opcodes.INVOKEVIRTUAL &&
+ insn instanceof MethodInsnNode call &&
+ "java/io/FileWriter".equals(call.owner) &&
+ "close".equals(call.name) &&
+ "()V".equals(call.desc)) {
+ insnList.previous();
+ insnList.add(new InsnNode(Opcodes.DUP));
+ insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "com/falsepattern/lib/internal/logging/CrashImprover", "injectLatest", "(Ljava/io/FileWriter;)V", false));
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/falsepattern/lib/internal/logging/CrashImprover.java b/src/main/java/com/falsepattern/lib/internal/logging/CrashImprover.java
new file mode 100644
index 0000000..3938ac5
--- /dev/null
+++ b/src/main/java/com/falsepattern/lib/internal/logging/CrashImprover.java
@@ -0,0 +1,71 @@
+/*
+ * This file is part of FalsePatternLib.
+ *
+ * Copyright (C) 2022-2024 FalsePattern
+ * All Rights Reserved
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * FalsePatternLib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FalsePatternLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FalsePatternLib. If not, see .
+ */
+
+package com.falsepattern.lib.internal.logging;
+
+import com.falsepattern.lib.internal.FPLog;
+import lombok.val;
+
+import net.minecraft.launchwrapper.Launch;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+public class CrashImprover {
+ private static final String cookie = UUID.randomUUID().toString();
+ static {
+ FPLog.LOG.info("Magic cookie: {}", cookie);
+ }
+ public static void probe() {
+
+ }
+
+ public static void injectLatest(FileWriter writer) {
+ val potentialLogs = Arrays.asList(new File(Launch.minecraftHome, "logs/fml-client-latest.log"),
+ new File(Launch.minecraftHome, "logs/fml-server-latest.log"));
+ for (val file: potentialLogs) {
+ try(InputStream is = new FileInputStream(file)) {
+ List lines = readLines(is);
+ if(lines.stream().anyMatch(l -> l.contains(cookie))) {
+ writer.append("\n\n").append("Complete log:").append('\n');
+ for (String line : lines) {
+ writer.append(line).append('\n');
+ }
+ }
+ } catch (IOException e) {
+ }
+ }
+ }
+ private static List readLines(InputStream stream) {
+ return new BufferedReader(new InputStreamReader(stream)).lines().collect(Collectors.toList());
+ }
+}
diff --git a/src/main/java/com/falsepattern/lib/internal/logging/NotEnoughVerbosity.java b/src/main/java/com/falsepattern/lib/internal/logging/NotEnoughVerbosity.java
new file mode 100644
index 0000000..deb164b
--- /dev/null
+++ b/src/main/java/com/falsepattern/lib/internal/logging/NotEnoughVerbosity.java
@@ -0,0 +1,193 @@
+/*
+ * This file is part of FalsePatternLib.
+ *
+ * Copyright (C) 2022-2024 FalsePattern
+ * All Rights Reserved
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * FalsePatternLib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FalsePatternLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FalsePatternLib. If not, see .
+ */
+
+package com.falsepattern.lib.internal.logging;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import com.falsepattern.lib.internal.FPLog;
+import com.falsepattern.lib.internal.Tags;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.impl.Log4jContextFactory;
+import org.apache.logging.log4j.spi.LoggerContextFactory;
+
+import net.minecraft.launchwrapper.Launch;
+
+// This class is a port of NotEnoughVerbosity, licensed under the Unlicense
+// It has been merged into FalsePatternLib so that it's available inside more modpacks
+// CHANGES:
+// This variant will inspect the blackboard for an entry called "notenoughverbosity". If it's present, it will no-op.
+// This will allow other people's lib mods to pull in this class without each one of them trying to reapply.
+public class NotEnoughVerbosity {
+
+ public static final Logger LOGGER = FPLog.LOG;
+
+ public static void apply() {
+ try {
+ if (needsToRun()) {
+ reconfigureLog4j();
+ }
+ } catch (Throwable t) {
+ LOGGER.error("NotEnoughVerbosity failed to apply!", t);
+ }
+ }
+
+ private static boolean needsToRun() {
+ try {
+ Class.forName("io.github.legacymoddingmc.notenoughverbosity.NotEnoughVerbosity");
+ // OG mod already present, do nothing
+ return false;
+ } catch (Throwable ignored) {}
+
+ if (Launch.blackboard.containsKey("notenoughverbosity")) {
+ // Other mod already present
+ return false;
+ }
+
+ boolean foundBadRoot = false;
+ for (LoggerContext context : getLoggerContexts()) {
+ LoggerConfig rootConfig = context.getConfiguration().getLoggers().get("");
+ if(rootConfig != null) {
+ if(rootConfig.getLevel().intLevel() < Level.ALL.intLevel()) {
+ LOGGER.warn("Found root logger with verbosity " + rootConfig.getLevel() + ", will try to switch to Forge config.");
+ foundBadRoot = true;
+ break;
+ } else {
+ LOGGER.debug("Found root logger with verbosity " + rootConfig.getLevel() + ", this is fine.");
+ }
+ } else {
+ throw new RuntimeException("Couldn't find root logger.");
+ }
+ }
+ if(!foundBadRoot) {
+ LOGGER.debug("Root config seems fine, log4j will not be reconfigured.");
+ }
+ return foundBadRoot;
+ }
+
+ public static void reconfigureLog4j() {
+ URI log4jUri = findForgeLog4j();
+ if (log4jUri != null) {
+ LOGGER.info("Reconfiguring logger to use config at " + log4jUri);
+ LOGGER.info("New messages will go to fml-x-latest.log");
+ String cookie = UUID.randomUUID().toString();
+ LOGGER.info("Magic cookie: " + cookie);
+ setLog4jConfig(log4jUri);
+ int count = getLoggerContexts().size();
+ LOGGER.info("Reconfigured logger (" + count + " context" + (count != 1 ? "s" : "") + ") to use config at " + log4jUri);
+ LOGGER.info("Earlier messages may be located in latest.log");
+
+ List latestLogLines = readLatestLog(cookie);
+ if(latestLogLines != null) {
+ LOGGER.info("Found earlier messages:\n-----Begin latest.log-----\n" + String.join("\n", latestLogLines) + "\n-----End latest.log-----");
+ }
+ Launch.blackboard.put("notenoughverbosity", Tags.MODID);
+ } else {
+ LOGGER.warn("Could not find Forge's log4j2.xml on classpath, doing nothing");
+ }
+ }
+
+ private static List readLatestLog(String cookie) {
+ File latestLog = new File(Launch.minecraftHome, "logs/latest.log");
+ if(!latestLog.exists()) {
+ LOGGER.debug("Couldn't find latest.log at " + latestLog.getAbsolutePath());
+ return null;
+ }
+
+ long lastModified = latestLog.lastModified();
+ if(lastModified < System.currentTimeMillis() - 1000 * 60) {
+ LOGGER.debug("latest.log at " + latestLog.getAbsolutePath() + " is too old (timestamp: " + lastModified + ")");
+ return null;
+ }
+
+ try(InputStream is = new FileInputStream(latestLog)) {
+ List lines = readLines(is);
+ if(lines.stream().anyMatch(l -> l.contains(cookie))) {
+ return lines;
+ } else {
+ LOGGER.debug("Failed to find magic cookie in latest.log at " + latestLog.getAbsolutePath());
+ }
+ } catch (IOException e) {
+ LOGGER.error("Failed to read latest.log at " + latestLog.getAbsolutePath(), e);
+ }
+
+ return null;
+ }
+
+ private static void setLog4jConfig(URI uri) {
+ for (LoggerContext context : getLoggerContexts()) {
+ context.setConfigLocation(uri);
+ context.reconfigure();
+ }
+ }
+
+ private static Collection getLoggerContexts() {
+ LoggerContextFactory loggerContextFactory = LogManager.getFactory();
+ if (loggerContextFactory instanceof Log4jContextFactory) {
+ return ((Log4jContextFactory) loggerContextFactory).getSelector().getLoggerContexts();
+ } else {
+ throw new IllegalStateException("Logger context factory is not a Log4jContextFactory");
+ }
+ }
+
+ private static URI findForgeLog4j() {
+ try {
+ List candidates = Collections.list(NotEnoughVerbosity.class.getClassLoader().getResources("log4j2.xml"));
+
+ LOGGER.info("Logger configs on classpath: " + candidates);
+
+ for(URL url : candidates) {
+ try {
+ if(readLines(url.openStream()).stream().anyMatch(l -> l.contains("Root level=\"all\""))) {
+ return url.toURI();
+ }
+ } catch(Exception e) {
+ LOGGER.error("Failed to read inspect logger config at URL " + url, e);
+ }
+ }
+ } catch(Exception e) {
+ LOGGER.error("Exception enumerating logger configs", e);
+ }
+ return null;
+ }
+
+ private static List readLines(InputStream stream) {
+ return new BufferedReader(new InputStreamReader(stream)).lines().collect(Collectors.toList());
+ }
+}