diff --git a/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java b/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java index 403af232676..482df626bce 100644 --- a/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java +++ b/src/main/java/de/hysky/skyblocker/SkyblockerScreen.java @@ -4,6 +4,7 @@ import de.hysky.skyblocker.config.SkyblockerConfigManager; import de.hysky.skyblocker.skyblock.Tips; import de.hysky.skyblocker.utils.FunUtils; +import de.hysky.skyblocker.utils.LogsFolderFinder; import de.hysky.skyblocker.utils.scheduler.Scheduler; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.ClientCommands; @@ -40,6 +41,7 @@ public class SkyblockerScreen extends Screen { private static final Component DISCORD_TEXT = Component.translatable("text.skyblocker.discord"); private static final Component SUPPORT_US_TEXT = Component.translatable("text.skyblocker.supportUs"); private static final Component CREDITS_TEXT = Component.translatable("credits_and_attribution.button.credits"); + private static final Component LOGS_FOLDER_TEXT = Component.translatable("text.skyblocker.logsFolder"); private HeaderAndFooterLayout layout; private MultiLineTextWidget tip; @@ -78,7 +80,8 @@ protected void init() { GridLayout.RowHelper adder = gridWidget.createRowHelper(2); adder.addChild(Button.builder(CONFIGURATION_TEXT, _ -> this.openConfig()).width(BUTTON_WIDTH).build(), 2); - adder.addChild(Button.builder(SOURCE_TEXT, ConfirmLinkScreen.confirmLink(this, "https://github.com/SkyblockerMod/Skyblocker")).width(HALF_BUTTON_WIDTH).build()); + adder.addChild(Button.builder(SOURCE_TEXT, ConfirmLinkScreen.confirmLink(this, "https://github.com/SkyblockerMod/Skyblocker")).width(BUTTON_WIDTH).build(), 2); + adder.addChild(Button.builder(LOGS_FOLDER_TEXT, _ -> LogsFolderFinder.openLogsFolder()).width(HALF_BUTTON_WIDTH).build()); adder.addChild(Button.builder(REPORT_BUGS_TEXT, ConfirmLinkScreen.confirmLink(this, "https://github.com/SkyblockerMod/Skyblocker/issues")).width(HALF_BUTTON_WIDTH).build()); adder.addChild(Button.builder(WEBSITE_TEXT, ConfirmLinkScreen.confirmLink(this, "https://hysky.de/")).width(HALF_BUTTON_WIDTH).build()); adder.addChild(Button.builder(TRANSLATE_TEXT, ConfirmLinkScreen.confirmLink(this, "https://translate.hysky.de/")).width(HALF_BUTTON_WIDTH).build()); diff --git a/src/main/java/de/hysky/skyblocker/utils/LogsFolderFinder.java b/src/main/java/de/hysky/skyblocker/utils/LogsFolderFinder.java new file mode 100644 index 00000000000..1ddbb493527 --- /dev/null +++ b/src/main/java/de/hysky/skyblocker/utils/LogsFolderFinder.java @@ -0,0 +1,86 @@ +package de.hysky.skyblocker.utils; + +import com.mojang.brigadier.Command; +import com.mojang.logging.LogUtils; +import de.hysky.skyblocker.SkyblockerMod; +import de.hysky.skyblocker.annotations.Init; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.command.v2.ClientCommands; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Util; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender; +import org.apache.logging.log4j.core.appender.FileManager; +import org.apache.logging.log4j.spi.LoggerContext; +import org.jspecify.annotations.Nullable; +import org.slf4j.Logger; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public final class LogsFolderFinder { + private static final Logger LOGGER = LogUtils.getLogger(); + private static @Nullable Path folder; + + public static Path getLogsFolder() { + if (folder == null) { + folder = computeFolder(); + } + return folder; + } + + public static void openLogsFolder() { + Util.getPlatform().openPath(getLogsFolder()); + } + + @Init + public static void init() { + ClientCommandRegistrationCallback.EVENT.register((dispatcher, _) -> + dispatcher.register(ClientCommands.literal(SkyblockerMod.NAMESPACE).then( + ClientCommands.literal("logsFolder") + .executes(_ -> { + openLogsFolder(); + return Command.SINGLE_SUCCESS; + }) + ))); + } + + private static Path computeFolder() { + LoggerContext context; + if (LogManager.getFactory().isClassLoaderDependent()) { + context = LogManager.getContext(Minecraft.class.getClassLoader(), false); + } else { + context = LogManager.getContext(false); + } + if (!(context instanceof org.apache.logging.log4j.core.LoggerContext instance)) { + LOGGER.warn("Could not compute logs folder, expected instance of LoggerContext got {}", context.getClass()); + return getFallbackFolder(); + } + Map appenders = instance.getConfiguration().getAppenders(); + List fileNames = new ArrayList<>(appenders.size()); + for (Appender appender : appenders.values()) { + if (appender instanceof AbstractOutputStreamAppender streamAppender && streamAppender.getManager() instanceof FileManager manager) { + fileNames.add(manager.getFileName()); + } + } + if (fileNames.isEmpty()) { + LOGGER.warn("Could not compute logs folder: didn't find any StreamAppender with a FileManager"); + return getFallbackFolder(); + } + Optional latestLogOpt = fileNames.stream().filter(s -> s.contains("latest.log")).findFirst(); + if (latestLogOpt.isEmpty()) { + LOGGER.warn("Could not compute logs folder: didn't find a path that contains 'latest.log'. Only got: {}", fileNames); + return getFallbackFolder(); + } + return Path.of(latestLogOpt.get()).toAbsolutePath().getParent(); + } + + private static Path getFallbackFolder() { + return FabricLoader.getInstance().getGameDir().resolve("logs").toAbsolutePath(); + } +} diff --git a/src/main/resources/assets/skyblocker/lang/en_us.json b/src/main/resources/assets/skyblocker/lang/en_us.json index f676c2b91a2..ac7b5f2e1c7 100644 --- a/src/main/resources/assets/skyblocker/lang/en_us.json +++ b/src/main/resources/assets/skyblocker/lang/en_us.json @@ -1987,6 +1987,7 @@ "text.skyblocker.confirm": "Confirm", "text.skyblocker.dead": "Dead", "text.skyblocker.discord": "Discord", + "text.skyblocker.logsFolder": "Open Logs Folder", "text.skyblocker.modrinth": "Modrinth", "text.skyblocker.offline": "Offline", "text.skyblocker.open": "Open",