From 0b2f13ec32cd14ff07e7d029989d410fe0842e02 Mon Sep 17 00:00:00 2001 From: ev chang Date: Sat, 27 Jul 2024 04:07:02 +0900 Subject: [PATCH 01/37] update to twoconfig, crash gui slightly ported --- build.gradle.kts | 12 +- gradle/wrapper/gradle-wrapper.properties | 2 +- main.py | 93 ++ settings.gradle.kts | 2 +- .../crashpatch/hooks/ModsCheckerPlugin.java | 106 ++- .../crashpatch/mixin/MixinGuiConnecting.java | 4 +- .../crashpatch/mixin/MixinGuiDupesFound.java | 2 +- .../crashpatch/mixin/MixinMinecraft.java | 52 +- .../org/polyfrost/crashpatch/CrashPatch.kt | 46 +- .../crashpatch/config/CrashPatchConfig.kt | 54 +- .../crashpatch/crashes/CrashHelper.kt | 6 +- .../org/polyfrost/crashpatch/gui/CrashGui.kt | 870 +++++++++--------- .../crashpatch/gui/CrashGuiRewrite.kt | 100 ++ .../org/polyfrost/crashpatch/gui/constants.kt | 25 +- .../crashpatch/utils/GuiDisconnectedHook.kt | 6 +- .../crashpatch/utils/InternetUtils.kt | 2 +- 16 files changed, 771 insertions(+), 611 deletions(-) create mode 100644 main.py create mode 100644 src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt diff --git a/build.gradle.kts b/build.gradle.kts index a3b3415..f88d63b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,6 +53,8 @@ loom { runConfigs { "client" { programArgs("--tweakClass", "org.polyfrost.crashpatch.hooks.ModsCheckerPlugin") + programArgs("--tweakClass", "org.polyfrost.oneconfig.internal.legacy.OneConfigTweaker") + programArgs("--tweakClass", "org.spongepowered.asm.launch.MixinTweaker") //property("fml.coreMods.load", "") property("mixin.debug.export", "true") } @@ -83,19 +85,25 @@ sourceSets { // Adds the Polyfrost maven repository so that we can get the libraries necessary to develop the mod. repositories { maven("https://repo.polyfrost.org/releases") + maven("https://repo.polyfrost.org/snapshots") } // Configures the libraries/dependencies for your mod. dependencies { // Adds the OneConfig library, so we can develop with it. - modCompileOnly("cc.polyfrost:oneconfig-$platform:0.2.2-alpha+") + val oneconfig = "1.0.0-alpha.19" + implementation("org.polyfrost.oneconfig:config-impl:$oneconfig") + implementation("org.polyfrost.oneconfig:commands:$oneconfig") + implementation("org.polyfrost.oneconfig:events:$oneconfig") + implementation("org.polyfrost.oneconfig:ui:$oneconfig") + implementation("org.polyfrost.oneconfig:internal:$oneconfig") + modImplementation("org.polyfrost.oneconfig:$platform:$oneconfig") modRuntimeOnly("me.djtheredstoner:DevAuth-${if (platform.isFabric) "fabric" else if (platform.isLegacyForge) "forge-legacy" else "forge-latest"}:1.2.0") shade("gs.mclo:api:3.0.1") // If we are building for legacy forge, includes the launch wrapper with `shade` as we configured earlier. if (platform.isLegacyForge) { compileOnly("org.spongepowered:mixin:0.7.11-SNAPSHOT") - shade("cc.polyfrost:oneconfig-wrapper-launchwrapper:1.0.0-beta17") } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7431fb5..f136d42 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip diff --git a/main.py b/main.py new file mode 100644 index 0000000..b851061 --- /dev/null +++ b/main.py @@ -0,0 +1,93 @@ +import os +import pathlib + +def add_import(imports): + with open(dir, "w") as new_file: + if original_text != content: + package_index = original_text.find('package') + line_end_index = original_text.find('\n', package_index) + + modified_text = content[:line_end_index] + '\nimport ' + imports + + if name.endswith(".java"): + modified_text += ";" + modified_text += "\n" + content[line_end_index:] + new_file.write(modified_text) + else: + new_file.write(content) + + +input("MAKE SURE YOU HAVE MADE A BACKUP OF YOUR PROJECT BEFORE RUNNING THIS SCRIPT (press enter to continue)\n") + +try: + os.chdir('src') +except FileNotFoundError: + input('Please put this script next to the src/ directory! (press enter to exit)') + exit() + +for path, _, files in os.walk("."): + for name in files: + dir = str(pathlib.PurePath(path, name)) + if not (name.endswith(".java") or name.endswith(".kt")): + continue + + with open(dir,"r+") as f: + new_f = f.readlines() + f.seek(0) + for line in new_f: + if "import cc.polyfrost.oneconfig.config.core.OneColor" in line: + f.write(line.replace("cc.polyfrost.oneconfig.config.core.OneColor", "org.polyfrost.polyui.color.PolyColor")) + elif "import cc.polyfrost.oneconfig.config.core.OneKeyBind" in line: + f.write(line.replace("cc.polyfrost.oneconfig.config.core.OneKeyBind", "org.polyfrost.polyui.input.Keybinder.Bind")) + if "import cc.polyfrost.oneconfig.config" in line: + f.write(line.replace("cc.polyfrost.oneconfig.config", "org.polyfrost.oneconfig.api.config.v1")) + elif "import cc.polyfrost.oneconfig.utils.commands.annotations" in line: + f.write(line.replace("cc.polyfrost.oneconfig.utils.commands.annotations", "org.polyfrost.oneconfig.api.commands.v1.factories.annotated")) + elif "import cc.polyfrost.oneconfig.utils.commands" in line: + f.write(line.replace("cc.polyfrost.oneconfig.utils.commands", "org.polyfrost.oneconfig.api.commands.v1")) + elif "import cc.polyfrost.oneconfig.utils.hypixel" in line: + f.write(line.replace("cc.polyfrost.oneconfig.utils.hypixel", "org.polyfrost.oneconfig.api.hypixel.v1")) + elif "import cc.polyfrost.oneconfig.utils.Notifications" in line: + f.write(line.replace("cc.polyfrost.oneconfig.utils.Notifications", "org.polyfrost.oneconfig.api.ui.v1.notifications.Notifications")) + elif "import cc.polyfrost.oneconfig.utils" in line: + f.write(line.replace("cc.polyfrost.oneconfig.utils", "org.polyfrost.oneconfig.utils.v1")) + elif "import cc.polyfrost.oneconfig.platform" in line: + f.write(line.replace("cc.polyfrost.oneconfig.platform", "org.polyfrost.oneconfig.api.platform.v1")) + elif "import cc.polyfrost.oneconfig.events.event" in line: + f.write(line.replace("cc.polyfrost.oneconfig.events", "org.polyfrost.oneconfig.api.event.v1.events")) + elif "import cc.polyfrost.oneconfig.events" in line: + f.write(line.replace("cc.polyfrost.oneconfig.events", "org.polyfrost.oneconfig.api.event.v1")) + elif "import cc.polyfrost.oneconfig.libs.universal" in line: + f.write(line.replace("cc.polyfrost.oneconfig.libs.universal", "org.polyfrost.universal")) + elif "import cc.polyfrost" in line: + f.write(line.replace("cc.polyfrost", "org.polyfrost")) + else: + f.write(line) + + f.truncate() + +input("Finished removing OneConfig V0 imports.\n\nPress enter to start replacing V0 methods.\n") + +for path, _, files in os.walk("."): + for name in files: + dir = str(pathlib.PurePath(path, name)) + if not (name.endswith(".java") or name.endswith(".kt")): + continue + + with open(dir, "r") as file: + original_text = file.read() + with open(dir, "r+") as file: + content = file.read().replace("TickDelay", "EventDelay.ticks").replace("tick(", "EventDelay.ticks(").replace("RenderTickDelay", "EventDelay.render").replace("renderTick(", "EventDelay.render(") + add_import("org.polyfrost.oneconfig.api.event.v1.EventDelay") + + with open(dir, "r") as file: + original_text = file.read() + with open(dir, "r+") as file: + content = file.read().replace("OneColor", "PolyColor") + + with open(dir, "r") as file: + original_text = file.read() + with open(dir, "r+") as file: + content = file.read().replace("OneKeyBind", "Keybinder.Bind") + +input("Done! (press enter to exit)") \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 6973a98..401cef3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,7 +7,7 @@ pluginManagement { maven("https://repo.polyfrost.org/releases") // Adds the Polyfrost maven repository to get Polyfrost Gradle Toolkit } plugins { - val pgtVersion = "0.6.2" // Sets the default versions for Polyfrost Gradle Toolkit + val pgtVersion = "0.6.5" // Sets the default versions for Polyfrost Gradle Toolkit id("org.polyfrost.multi-version.root") version pgtVersion } } diff --git a/src/main/java/org/polyfrost/crashpatch/hooks/ModsCheckerPlugin.java b/src/main/java/org/polyfrost/crashpatch/hooks/ModsCheckerPlugin.java index bb00501..0e6d6f5 100644 --- a/src/main/java/org/polyfrost/crashpatch/hooks/ModsCheckerPlugin.java +++ b/src/main/java/org/polyfrost/crashpatch/hooks/ModsCheckerPlugin.java @@ -1,6 +1,5 @@ package org.polyfrost.crashpatch.hooks; -import cc.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker; import com.google.common.collect.Lists; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -32,10 +31,15 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -public class ModsCheckerPlugin extends LaunchWrapperTweaker { +public class ModsCheckerPlugin implements ITweaker { //todo private static final JsonParser PARSER = new JsonParser(); public static final HashMap> modsMap = new HashMap<>(); //modid : file, version, name + @Override + public void acceptOptions(List args, File gameDir, File assetsDir, String profile) { + + } + @Override public void injectIntoClassLoader(LaunchClassLoader classLoader) { try { @@ -127,51 +131,63 @@ public void injectIntoClassLoader(LaunchClassLoader classLoader) { e.printStackTrace(); } - CodeSource codeSource = this.getClass().getProtectionDomain().getCodeSource(); - if (codeSource != null) { - URL location = codeSource.getLocation(); - try { - File file = new File(location.toURI()); - if (file.isFile()) { - CoreModManager.getIgnoredMods().remove(file.getName()); - CoreModManager.getReparseableCoremods().add(file.getName()); - try { - try { - List tweakClasses = (List) Launch.blackboard.get("TweakClasses"); // tweak classes before other mod trolling - if (tweakClasses.contains("org.spongepowered.asm.launch.MixinTweaker")) { // if there's already a mixin tweaker, we'll just load it like "usual" - new MixinTweaker(); // also we might not need to make a new mixin tweawker all the time but im just making sure - } else if (!Launch.blackboard.containsKey("mixin.initialised")) { // if there isnt, we do our own trolling - List tweaks = (List) Launch.blackboard.get("Tweaks"); - tweaks.add(new MixinTweaker()); - } - } catch (Exception ignored) { - // if it fails i *think* we can just ignore it - } - try { - MixinBootstrap.getPlatform().addContainer(location.toURI()); - } catch (Exception ignore) { - // fuck you essential - try { - Class containerClass = Class.forName("org.spongepowered.asm.launch.platform.container.IContainerHandle"); - Class urlContainerClass = Class.forName("org.spongepowered.asm.launch.platform.container.ContainerHandleURI"); - Object container = urlContainerClass.getConstructor(URI.class).newInstance(location.toURI()); - MixinBootstrap.getPlatform().getClass().getDeclaredMethod("addContainer", containerClass).invoke(MixinBootstrap.getPlatform(), container); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("OneConfig's Mixin loading failed. Please contact https://polyfrost.cc/discord to resolve this issue!"); - } - } - } catch (Exception ignored) { + //todo terrible + + //CodeSource codeSource = this.getClass().getProtectionDomain().getCodeSource(); + //if (codeSource != null) { + // URL location = codeSource.getLocation(); + // try { + // File file = new File(location.toURI()); + // if (file.isFile()) { + // CoreModManager.getIgnoredMods().remove(file.getName()); + // CoreModManager.getReparseableCoremods().add(file.getName()); + // try { + // try { + // List tweakClasses = (List) Launch.blackboard.get("TweakClasses"); // tweak classes before other mod trolling + // if (tweakClasses.contains("org.spongepowered.asm.launch.MixinTweaker")) { // if there's already a mixin tweaker, we'll just load it like "usual" + // new MixinTweaker(); // also we might not need to make a new mixin tweawker all the time but im just making sure + // } else if (!Launch.blackboard.containsKey("mixin.initialised")) { // if there isnt, we do our own trolling + // List tweaks = (List) Launch.blackboard.get("Tweaks"); + // tweaks.add(new MixinTweaker()); + // } + // } catch (Exception ignored) { + // // if it fails i *think* we can just ignore it + // } + // try { + // MixinBootstrap.getPlatform().addContainer(location.toURI()); + // } catch (Exception ignore) { + // // fuck you essential + // try { + // Class containerClass = Class.forName("org.spongepowered.asm.launch.platform.container.IContainerHandle"); + // Class urlContainerClass = Class.forName("org.spongepowered.asm.launch.platform.container.ContainerHandleURI"); + // Object container = urlContainerClass.getConstructor(URI.class).newInstance(location.toURI()); + // MixinBootstrap.getPlatform().getClass().getDeclaredMethod("addContainer", containerClass).invoke(MixinBootstrap.getPlatform(), container); + // } catch (Exception e) { + // e.printStackTrace(); + // throw new RuntimeException("OneConfig's Mixin loading failed. Please contact https://polyfrost.cc/discord to resolve this issue!"); + // } + // } + // } catch (Exception ignored) { +// + // } + // } + // } catch (URISyntaxException ignored) {} + //} else { + // LogManager.getLogger().warn("No CodeSource, if this is not a development environment we might run into problems!"); + // LogManager.getLogger().warn(this.getClass().getProtectionDomain()); + //} +// + //super.injectIntoClassLoader(classLoader); + } - } - } - } catch (URISyntaxException ignored) {} - } else { - LogManager.getLogger().warn("No CodeSource, if this is not a development environment we might run into problems!"); - LogManager.getLogger().warn(this.getClass().getProtectionDomain()); - } + @Override + public String getLaunchTarget() { + return null; + } - super.injectIntoClassLoader(classLoader); + @Override + public String[] getLaunchArguments() { + return new String[0]; } private static void doThatPopupThing(File modsFolder, String message) { diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java index 86acce2..420c592 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -1,7 +1,7 @@ package org.polyfrost.crashpatch.mixin; -import cc.polyfrost.oneconfig.libs.universal.ChatColor; -import cc.polyfrost.oneconfig.libs.universal.UDesktop; +import org.polyfrost.universal.ChatColor; +import org.polyfrost.universal.UDesktop; import org.polyfrost.crashpatch.CrashPatch; import org.polyfrost.crashpatch.hooks.MinecraftHook; import net.minecraft.client.Minecraft; diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java index 0227110..9241d96 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java @@ -1,6 +1,6 @@ package org.polyfrost.crashpatch.mixin; -import cc.polyfrost.oneconfig.libs.universal.UDesktop; +import org.polyfrost.universal.UDesktop; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiErrorScreen; diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index 91b6d7f..7cfd1aa 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -1,24 +1,14 @@ package org.polyfrost.crashpatch.mixin; -import cc.polyfrost.oneconfig.events.EventManager; -import cc.polyfrost.oneconfig.events.event.RenderEvent; -import cc.polyfrost.oneconfig.events.event.Stage; -import cc.polyfrost.oneconfig.utils.gui.GuiUtils; -import net.minecraft.client.multiplayer.WorldClient; -import net.minecraft.client.network.NetHandlerPlayClient; -import net.minecraft.client.renderer.EntityRenderer; -import net.minecraft.util.ChatComponentText; -import org.polyfrost.crashpatch.config.CrashPatchConfig; -import org.polyfrost.crashpatch.crashes.StateManager; -import org.polyfrost.crashpatch.gui.CrashGui; -import org.polyfrost.crashpatch.hooks.MinecraftHook; -import org.polyfrost.crashpatch.utils.GuiDisconnectedHook; import net.minecraft.client.Minecraft; import net.minecraft.client.audio.SoundHandler; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.client.renderer.EntityRenderer; import net.minecraft.client.renderer.GlStateManager; import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.texture.TextureManager; @@ -29,6 +19,7 @@ import net.minecraft.client.settings.GameSettings; import net.minecraft.client.shader.Framebuffer; import net.minecraft.crash.CrashReport; +import net.minecraft.util.ChatComponentText; import net.minecraft.util.MinecraftError; import net.minecraft.util.ReportedException; import net.minecraft.util.ResourceLocation; @@ -41,6 +32,11 @@ import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL14; +import org.polyfrost.crashpatch.config.CrashPatchConfig; +import org.polyfrost.crashpatch.crashes.StateManager; +import org.polyfrost.crashpatch.gui.CrashGuiRewrite; +import org.polyfrost.crashpatch.hooks.MinecraftHook; +import org.polyfrost.crashpatch.utils.GuiDisconnectedHook; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -228,7 +224,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { // Display the crash screen // crashpatch$runGUILoop(new GuiCrashScreen(report)); - displayGuiScreen(new CrashGui(report)); + displayGuiScreen(new CrashGuiRewrite(report).create()); } catch (Throwable t) { // The crash screen has crashed. Report it normally instead. logger.error("An uncaught exception occured while displaying the crash screen, making normal report instead", t); @@ -261,7 +257,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { if (crashpatch$clientCrashCount >= CrashPatchConfig.INSTANCE.getLeaveLimit() || crashpatch$serverCrashCount >= CrashPatchConfig.INSTANCE.getLeaveLimit()) { logger.error("Crash limit reached, exiting world"); - CrashGui.Companion.setLeaveWorldCrash$CrashPatch_1_8_9_forge(true); + CrashGuiRewrite.Companion.setLeaveWorldCrash(true); if (getNetHandler() != null) { getNetHandler().getNetworkManager().closeChannel(new ChatComponentText("[CrashPatch] Client crashed")); } @@ -315,12 +311,13 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { mcSoundHandler = new SoundHandler(mcResourceManager, gameSettings); mcResourceManager.registerReloadListener(mcSoundHandler); - try { // this is necessary for some GUI stuff. if it works, cool, if not, it's not a big deal - //EventManager.INSTANCE.register(Notifications.INSTANCE); - GuiUtils.getDeltaTime(); // make sure static initialization is called - } catch (Exception e) { - e.printStackTrace(); - } + //try { // this is necessary for some GUI stuff. if it works, cool, if not, it's not a big deal + // //EventManager.INSTANCE.register(Notifications.INSTANCE); + // GuiUtils.getDeltaTime(); // make sure static initialization is called + //} catch (Exception e) { + // e.printStackTrace(); + //} + //todo do we need a polyui equivalent running = true; try { @@ -330,7 +327,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { GlStateManager.enableTexture2D(); } catch (Throwable ignored) { } - crashpatch$runGUILoop(new CrashGui(report, CrashGui.GuiType.INIT)); + crashpatch$runGUILoop(new CrashGuiRewrite(report, CrashGuiRewrite.GuiType.INIT)); } catch (Throwable t) { if (!crashpatch$letDie) { logger.error("An uncaught exception occured while displaying the init error screen, making normal report instead", t); @@ -343,13 +340,14 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { /** * @author Runemoro */ - private void crashpatch$runGUILoop(CrashGui screen) throws Throwable { + private void crashpatch$runGUILoop(CrashGuiRewrite crashGui) throws Throwable { + GuiScreen screen = crashGui.create(); displayGuiScreen(screen); while (running && currentScreen != null) { if (Display.isCreated() && Display.isCloseRequested()) { System.exit(0); } - EventManager.INSTANCE.post(new RenderEvent(Stage.START, 0)); + //EventManager.INSTANCE.post(new RenderEvent.Start()); todo leftClickCounter = 10000; currentScreen.handleInput(); currentScreen.updateScreen(); @@ -377,9 +375,9 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { int mouseY = height - Mouse.getY() * height / displayHeight - 1; Gui.drawRect(0, 0, width, height, Color.WHITE.getRGB()); // DO NOT REMOVE THIS! FOR SOME REASON NANOVG DOESN'T RENDER WITHOUT IT currentScreen.drawScreen(mouseX, mouseY, 0); - if (screen.getShouldCrash()) { + if (crashGui.getShouldCrash()) { crashpatch$letDie = true; - throw Objects.requireNonNull(screen.getThrowable()); + throw Objects.requireNonNull(crashGui.getThrowable()); } framebufferMc.unbindFramebuffer(); @@ -389,7 +387,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { framebufferMc.framebufferRender(displayWidth, displayHeight); GlStateManager.popMatrix(); - EventManager.INSTANCE.post(new RenderEvent(Stage.END, 0)); + //EventManager.INSTANCE.post(new RenderEvent(Stage.END, 0)); todo updateDisplay(); Thread.yield(); diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index b916902..50ef5b2 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -1,13 +1,10 @@ package org.polyfrost.crashpatch -import cc.polyfrost.oneconfig.libs.universal.ChatColor -import cc.polyfrost.oneconfig.libs.universal.UMinecraft -import cc.polyfrost.oneconfig.utils.Multithreading -import cc.polyfrost.oneconfig.utils.commands.CommandManager -import cc.polyfrost.oneconfig.utils.commands.annotations.Command -import cc.polyfrost.oneconfig.utils.commands.annotations.Main -import cc.polyfrost.oneconfig.utils.commands.annotations.SubCommand -import cc.polyfrost.oneconfig.utils.dsl.tick +import org.polyfrost.universal.ChatColor +import org.polyfrost.universal.UMinecraft +import org.polyfrost.oneconfig.utils.v1.Multithreading +import org.polyfrost.oneconfig.api.commands.v1.CommandManager +import org.polyfrost.oneconfig.api.commands.v1.factories.annotated.Command import org.polyfrost.crashpatch.config.CrashPatchConfig import org.polyfrost.crashpatch.crashes.CrashHelper import org.polyfrost.crashpatch.crashes.DeobfuscatingRewritePolicy @@ -21,7 +18,7 @@ import org.apache.logging.log4j.Logger import java.io.File -@Mod(modid = CrashPatch.MODID, version = CrashPatch.VERSION, name = CrashPatch.NAME, modLanguageAdapter = "cc.polyfrost.oneconfig.utils.KotlinLanguageAdapter") +@Mod(modid = CrashPatch.MODID, version = CrashPatch.VERSION, name = CrashPatch.NAME, modLanguageAdapter = "org.polyfrost.oneconfig.utils.v1.forge.KotlinLanguageAdapter") object CrashPatch { const val MODID = "@ID@" const val NAME = "@NAME@" @@ -32,7 +29,7 @@ object CrashPatch { @Mod.EventHandler fun onPreInit(e: FMLPreInitializationEvent) { DeobfuscatingRewritePolicy.install() - Multithreading.runAsync { + Multithreading.submit { logger.info("Is SkyClient: $isSkyclient") if (!CrashHelper.loadJson()) { logger.error("CrashHelper failed to preload crash data JSON!") @@ -42,20 +39,20 @@ object CrashPatch { @Mod.EventHandler fun onInit(e: FMLInitializationEvent) { - CommandManager.INSTANCE.registerCommand(CrashPatchCommand()) + CommandManager.registerCommand(CrashPatchCommand()) CrashPatchConfig // uncomment to test init screen crashes // throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") } - @Command(value = "crashpatch") + @Command("crashpatch") class CrashPatchCommand { - @Main + @Command fun main() { - CrashPatchConfig.openGui() + //CrashPatchConfig.openGui() } - @SubCommand + @Command fun reload() { if (CrashHelper.loadJson()) { CrashHelper.simpleCache.clear() @@ -64,21 +61,6 @@ object CrashPatch { UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Failed to reload the JSON file!")) } } - - var a = false - - @SubCommand - fun crash() { - UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Crashing...")) - tick(1) { - if (!a) { - a = true - throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") - } else { - a = false - } - } - } } } val logger: Logger = LogManager.getLogger(CrashPatch) @@ -91,4 +73,6 @@ val gameDir: File by lazy(LazyThreadSafetyMode.PUBLICATION) { file } } -val mcDir = File(System.getProperty("user.dir")) \ No newline at end of file +val mcDir = File(System.getProperty("user.dir")) +val mc + get() = UMinecraft.getMinecraft() // todo replace with oneconfig in alpha20 \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt b/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt index 93ffad5..13b3428 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt @@ -1,75 +1,71 @@ package org.polyfrost.crashpatch.config -import cc.polyfrost.oneconfig.config.Config -import cc.polyfrost.oneconfig.config.annotations.* -import cc.polyfrost.oneconfig.config.data.InfoType -import cc.polyfrost.oneconfig.config.data.Mod -import cc.polyfrost.oneconfig.config.data.ModType -import cc.polyfrost.oneconfig.libs.universal.UDesktop.browse +import org.polyfrost.oneconfig.api.config.v1.Config +import org.polyfrost.oneconfig.api.config.v1.annotations.* +import org.polyfrost.universal.UDesktop.browse import java.net.URI -object CrashPatchConfig : Config(Mod("CrashPatch", ModType.UTIL_QOL, "/assets/crashpatch/crashpatch_dark.svg"), "crashpatch.json") { +object CrashPatchConfig : Config("crashpatch.json", "/assets/crashpatch/crashpatch_dark.svg", "CrashPatch", Category.QOL) { // Toggles @Switch( - name = "Catch crashes during gameplay", + title = "Catch crashes during gameplay", description = "Catch crashes whilst in-game, and prevent the game from closing", subcategory = "Patches" ) var inGameCrashPatch = true @Switch( - name = "Patch crashes during launch", + title = "Patch crashes during launch", description = "Catch crashes during initialization, & display a message.", subcategory = "Patches" ) var initCrashPatch = true @Switch( - name = "Display disconnection causes", + title = "Display disconnection causes", description = "Display a message when a reason is found for a disconnect.", subcategory = "Patches" ) var disconnectCrashPatch = true // Limits - @Info( - text = "It's recommended to leave the world after a few crashes, and outright quit the game if there are more; this is to avoid severe instability", - type = InfoType.WARNING, - size = 2, - subcategory = "Limits" - ) - var ignored: Boolean = false + //@Info( // todo + // text = "It's recommended to leave the world after a few crashes, and outright quit the game if there are more; this is to avoid severe instability", + // type = InfoType.WARNING, + // size = 2, + // subcategory = "Limits" + //) + //var ignored: Boolean = false @Slider( - name = "World Leave Limit", + title = "World Leave Limit", min = 1f, max = 20f, - step = 1, + step = 1F, subcategory = "Limits" ) var leaveLimit = 3 @Slider( - name = "Crash Limit", + title = "Crash Limit", min = 1f, max = 20f, - step = 1, + step = 1F, subcategory = "Limits" ) var crashLimit = 5 @Switch( - name = "Deobfuscate Crash Log", + title = "Deobfuscate Crash Log", description = "Makes certain class names more readable through deobfuscation", - size = 2, subcategory = "Logs" ) var deobfuscateCrashLog = true @Dropdown( - name = "Log uploader", + title = "Log uploader", description = "The method used to upload the crash log.", options = ["hst.sh", "mclo.gs (Aternos)"], subcategory = "Logs" @@ -77,12 +73,14 @@ object CrashPatchConfig : Config(Mod("CrashPatch", ModType.UTIL_QOL, "/assets/cr var crashLogUploadMethod = 0 @Button( - name = "Polyfrost support", + title = "Polyfrost support", text = "Discord" ) var supportDiscord = Runnable { browse(URI.create("https://polyfrost.cc/discord/")) } - init { - initialize() - } + @Button( + title = "Crash game", + text = "Crash" + ) + var crashGame = Runnable { throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") } } \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt index fef026c..b5924aa 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt @@ -1,10 +1,10 @@ package org.polyfrost.crashpatch.crashes -import cc.polyfrost.oneconfig.libs.universal.wrappers.message.UTextComponent -import cc.polyfrost.oneconfig.utils.NetworkUtils +import org.polyfrost.universal.wrappers.message.UTextComponent import org.polyfrost.crashpatch.gameDir import org.polyfrost.crashpatch.mcDir import com.google.gson.JsonObject +import org.polyfrost.oneconfig.utils.v1.JsonUtils import java.io.File import kotlin.collections.set @@ -17,7 +17,7 @@ object CrashHelper { fun loadJson(): Boolean { return try { skyclientJson = - NetworkUtils.getJsonElement("https://raw.githubusercontent.com/SkyblockClient/CrashData/main/crashes.json").asJsonObject + JsonUtils.parseFromUrl("https://raw.githubusercontent.com/SkyblockClient/CrashData/main/crashes.json")?.asJsonObject ?: return false true } catch (e: Exception) { e.printStackTrace() diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGui.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGui.kt index 53708e9..5894de7 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGui.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGui.kt @@ -1,454 +1,420 @@ package org.polyfrost.crashpatch.gui -import cc.polyfrost.oneconfig.gui.OneConfigGui -import cc.polyfrost.oneconfig.gui.animations.Animation -import cc.polyfrost.oneconfig.gui.animations.ColorAnimation -import cc.polyfrost.oneconfig.gui.animations.EaseInOutQuad -import cc.polyfrost.oneconfig.gui.animations.EaseOutQuad -import cc.polyfrost.oneconfig.gui.elements.BasicButton -import cc.polyfrost.oneconfig.libs.universal.UDesktop -import cc.polyfrost.oneconfig.libs.universal.UMatrixStack -import cc.polyfrost.oneconfig.libs.universal.UResolution.windowHeight -import cc.polyfrost.oneconfig.libs.universal.UResolution.windowWidth -import cc.polyfrost.oneconfig.libs.universal.UScreen -import cc.polyfrost.oneconfig.platform.Platform -import cc.polyfrost.oneconfig.renderer.NanoVGHelper -import cc.polyfrost.oneconfig.renderer.asset.Icon -import cc.polyfrost.oneconfig.renderer.asset.SVG -import cc.polyfrost.oneconfig.renderer.font.Font -import cc.polyfrost.oneconfig.renderer.font.FontHelper -import cc.polyfrost.oneconfig.renderer.font.Fonts -import cc.polyfrost.oneconfig.renderer.scissor.ScissorHelper -import cc.polyfrost.oneconfig.utils.InputHandler -import cc.polyfrost.oneconfig.utils.NetworkUtils -import cc.polyfrost.oneconfig.utils.Notifications -import cc.polyfrost.oneconfig.utils.color.ColorPalette -import cc.polyfrost.oneconfig.utils.color.ColorUtils -import cc.polyfrost.oneconfig.utils.dsl.* -import net.minecraft.crash.CrashReport -import org.polyfrost.crashpatch.CrashPatch -import org.polyfrost.crashpatch.crashes.CrashHelper -import org.polyfrost.crashpatch.crashes.CrashScan -import org.polyfrost.crashpatch.hooks.CrashReportHook -import org.polyfrost.crashpatch.utils.InternetUtils -import java.io.File -import java.net.URI - -class CrashGui @JvmOverloads constructor( - private val scanText: String, - private val file: File?, - private val susThing: String, - private val type: GuiType = GuiType.NORMAL, - val throwable: Throwable? = null -) : UScreen(false) { - @JvmOverloads - constructor(report: CrashReport, type: GuiType = GuiType.NORMAL) : this( - report.completeReport, - report.file, - (report as CrashReportHook).suspectedCrashPatchMods, - type, - report.crashCause - ) - - private val crashPatchLogo = Icon("/assets/crashpatch/crashpatch_dark.svg") - - private val crashScan: CrashScan? by lazy { - return@lazy CrashHelper.scanReport(scanText, type == GuiType.DISCONNECT) - .let { return@let if (it != null && it.solutions.isNotEmpty()) it else null } - } - var shouldCrash = false - - private val subtitle by lazy { - when (type) { - GuiType.INIT -> listOf(SUBTITLE_INIT_1 + (if (crashScan != null) SUBTITLE_INIT_2 else "") + SUBTITLE_INIT_3) - GuiType.NORMAL -> listOf(SUBTITLE_1, SUBTITLE_2) - GuiType.DISCONNECT -> listOf(SUBTITLE_DISCONNECTED, SUBTITLE_DISCONNECTED_2) - } - } - - private val buttonFontSizeField = BasicButton::class.java.getDeclaredField("fontSize").apply { isAccessible = true } - - private val returnToGameButton by lazy { - val button = BasicButton( - NanoVGHelper.INSTANCE.getTextWidth(vg, RETURN_TO_GAME, 14f, Fonts.MEDIUM).toInt() + 40, - 40, - RETURN_TO_GAME, - 2, - ColorPalette.PRIMARY - ) - button.setClickAction { - if (type == GuiType.INIT) { - shouldCrash = true - } else { - restorePreviousScreen() - } - } - buttonFontSizeField.setFloat(button, 14f) - button - } - - private val openCrashLogButton by lazy { - val button = BasicButton( - NanoVGHelper.INSTANCE.getTextWidth(vg, OPEN_CRASH_LOG, 14f, Fonts.MEDIUM).toInt() + 40 + 5 + 35, - 40, - OPEN_CRASH_LOG, - null, - SVG("/assets/crashpatch/open-external.svg"), - 2, - ColorPalette.TERTIARY - ) - button.setClickAction { - file?.let { - UDesktop.open(it) - } - } - buttonFontSizeField.setFloat(button, 14f) - button - } - - private val uploadLogButton by lazy { - val button = BasicButton( - 30, - 30, - SVG("/assets/crashpatch/upload.svg"), - 2, - ColorPalette(GRAY_600, GRAY_700, GRAY_800) - ) - button.setClickAction { - selectedSolution?.let { solution -> - val link = - InternetUtils.upload(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) - setClipboardString(link) - if (UDesktop.browse(URI.create(link))) { - Notifications.INSTANCE.send( - "CrashPatch", "Link copied to clipboard and opened in browser", crashPatchLogo - ) - } else { - Notifications.INSTANCE.send( - "CrashPatch", "Couldn't open link in browser, copied to clipboard instead.", crashPatchLogo - ) - } - } - } - button - } - - private val copyLogButton by lazy { - val button = BasicButton( - 30, - 30, - SVG("/assets/crashpatch/copy.svg"), - 2, - ColorPalette(GRAY_600, GRAY_700, GRAY_800) - ) - button.setClickAction { - selectedSolution?.let { solution -> - setClipboardString(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) - Notifications.INSTANCE.send("CrashPatch", "Copied to clipboard", crashPatchLogo) - } - } - button - } - - private var scrollAnimation: Animation? = null - private val colorAnimation = ColorAnimation( - ColorPalette( - ColorUtils.getColor(55, 59, 69, 76), - ColorUtils.getColor(55, 59, 69, 153), - ColorUtils.getColor(55, 59, 69, 255) - ), 200 - ) - private var hyperlinkAnimation: EaseInOutQuad? = EaseInOutQuad(200, 0f, 1f, false) - private var scrollTarget = 0f - private var scrollTime = 0L - private var mouseWasDown = false - private var dragging = false - private var yStart = 0f - private var scroll = 0f - - private var lastHeight = 0f - - private var selectedSolution: CrashScan.Solution? = null - - private var vg = -1L - - private val inputHandler = InputHandler() - - override fun onDrawScreen(matrixStack: UMatrixStack, mouseX: Int, mouseY: Int, partialTicks: Float) { - super.onDrawScreen(matrixStack, mouseX, mouseY, partialTicks) - if (mc.theWorld == null && leaveWorldCrash) { - drawDefaultBackground() - } - NanoVGHelper.INSTANCE.setupAndDraw { draw(it, inputHandler) } - } - - override fun onScreenClose() { - super.onScreenClose() - leaveWorldCrash = false - } - - fun draw(vg: Long, inputHandler: InputHandler) { - this.vg = vg - nanoVG(vg) { - FontHelper.INSTANCE.loadFont(vg, JETBRAINS_MONO) - val scale = OneConfigGui.getScaleFactor() - val x = ((windowWidth - 650 * scale) / 2f / scale).toInt() - val y = ((windowHeight - 600 * scale) / 2f / scale).toInt() - scale(scale, scale) - inputHandler.scale(scale.toDouble(), scale.toDouble()) - drawRoundedRect(x, y, 650, 600, 20, GRAY_800) - drawSVG("/assets/crashpatch/WarningTriangle.svg", x + 305 + 10, y + 24 + 10, 20, 20, javaClass) - drawText( - if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, - (windowWidth / 2f / scale) - (getTextWidth( - if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, - 24, - Fonts.MEDIUM - ) / 2f), - y + 56 + 22, - WHITE_90, - 24, - Fonts.MEDIUM - ) - subtitle.forEachIndexed { index, s -> - drawText( - s, - (windowWidth / 2f / scale) - (getTextWidth(s, 14, Fonts.REGULAR) / 2f), - y + 56 + 87 + ((index - 1) * (14 * 1.75)), - WHITE_80, - 14, - Fonts.REGULAR - ) - } - - drawText( - if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, - (windowWidth / 2f / scale) - (getTextWidth( - if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, - 16, - Fonts.REGULAR - ) / 2f), - y + 56 + 87 + 10 + (subtitle.size * (14 * 1.75)), - WHITE_80, - 16, - Fonts.REGULAR - ) - drawText( - susThing, (windowWidth / 2f / scale) - (getTextWidth( - susThing, 18, Fonts.SEMIBOLD - ) / 2f), y + 56 + 87 + 10 + (subtitle.size * (14 * 1.75)) + 30, BLUE_400, 18, Fonts.SEMIBOLD - ) - - drawRoundedRect(x + 50, y + 273, 550, 158, 12, GRAY_700) - ScissorHelper.INSTANCE.scissor(vg, x + 50f, y + 273f, 550f, 37f).let { - drawRoundedRect(x + 50, y + 273, 550, 158, 12, GRAY_600) - ScissorHelper.INSTANCE.resetScissor(vg, it) - } - - crashScan?.solutions?.let { solutions -> - var i = 0 - var lastTextWidth = 0f - solutions.forEach { solution -> - i++ - if (i == 1 && selectedSolution == null) { - selectedSolution = solution - } - drawText( - solution.name, - x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth else 0f), - y + 273 + 18.5, - WHITE_90, - 12, - Fonts.MEDIUM - ) - val textWidth = getTextWidth(solution.name, 12, Fonts.MEDIUM) - if (selectedSolution != solution) { - if (inputHandler.isAreaClicked( - x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth else 0f), - y + 273f, - textWidth, - 37f, - ) - ) { - selectedSolution = solution - scroll = 0f - scrollTarget = 0f - scrollTime = 0 - scrollAnimation = null - } - } - val hovered = inputHandler.isAreaHovered( - x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth else 0f), - y + 273f, - textWidth, - 37f, - ) - lastTextWidth += textWidth - if (selectedSolution == solution || hovered) { - drawRoundedRect( - x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth - textWidth else 0f), - y + 273 + 35, - textWidth, - 2, - 1, - if (selectedSolution == solution) BLUE_600 else ColorUtils.setAlpha(BLUE_600, 128) - ) - } - if (selectedSolution == solution) { - ScissorHelper.INSTANCE.scissor(vg, x + 50f + 20f, y + 310f, 550f - 20 - 20, 121f) - .let { scissor -> - val scrollBarLength = 89 / lastHeight * 89 - if (lastHeight > 89) { - scroll = scrollAnimation?.get() ?: scrollTarget - val dWheel = Platform.getMousePlatform().dWheel.toFloat() * 0.3f - if (dWheel != 0f) { - scrollTarget += dWheel - if (scrollTarget > 0f) scrollTarget = - 0f else if (scrollTarget < -lastHeight + 89) scrollTarget = -lastHeight + 89 - scrollAnimation = EaseOutQuad(150, scroll, scrollTarget, false) - scrollTime = System.currentTimeMillis() - } else if (scrollAnimation != null && scrollAnimation!!.isFinished) scrollAnimation = - null - if (dragging && inputHandler.isClicked(true)) { - dragging = false - } - } - var height = 0F - translate(0f, scroll) - solution.solutions.forEach { - height += 12 * 1.25f - drawWrappedString( - it, - x + 50 + 20, - y + 310f + height, - 550 - 20 - 20, - WHITE_60, - 12, - 1.25f, - JETBRAINS_MONO - ) - height += NanoVGHelper.INSTANCE.getWrappedStringHeight( - vg, it, 550F, 12F, 1.25f, JETBRAINS_MONO - ) - } - height += 12 * 1.25f - translate(0f, -scroll) - lastHeight = height - ScissorHelper.INSTANCE.resetScissor(vg, scissor) - if (lastHeight > 89) { - val scrollBarY = scroll / lastHeight * 81 - val isMouseDown = Platform.getMousePlatform().isButtonDown(0) - val scrollHover = inputHandler.isAreaHovered( - (x + 50f + 20f + 530f - 14f), - (y + 310f + 16 - scrollBarY), - 12f, - scrollBarLength.toInt().toFloat() - ) - val scrollTimePeriod = System.currentTimeMillis() - scrollTime < 1000 - if (scrollHover && isMouseDown && !mouseWasDown) { - yStart = inputHandler.mouseY() - dragging = true - } - mouseWasDown = isMouseDown - if (dragging) { - scrollTarget = -(inputHandler.mouseY() - yStart) * lastHeight / 89f - if (scrollTarget > 0f) scrollTarget = - 0f else if (scrollTarget < -lastHeight + 89) scrollTarget = - -lastHeight + 89f - scrollAnimation = EaseOutQuad(150, scroll, scrollTarget, false) - } - NanoVGHelper.INSTANCE.drawRoundedRect( - vg, - (x + 50f + 20f + 530f - 14f), - (y + 310f + 16 - scrollBarY), - 4f, - scrollBarLength, - colorAnimation.getColor(scrollHover || scrollTimePeriod, dragging), - 4f - ) - } - } - } - } - } - uploadLogButton.draw(vg, x + 600 - 8 - 11 - 30f, y + 273f + 3.5f, inputHandler) - copyLogButton.draw(vg, x + 600 - 8 - 11 - 15 - 8 - 11 - 30f, y + 273f + 3.5f, inputHandler) - - drawText( - "If the solution above doesn't help, join", (windowWidth / 2f / scale) - (getTextWidth( - "If the solution above doesn't help, join", 16, Fonts.REGULAR - ) / 2f), y + 273 + 158 + 24 + 20, WHITE_80, 16, Fonts.REGULAR - ) - val discordMessageWidth = 20 + 15 + getTextWidth("https://inv.wtf/skyclient", 16, Fonts.REGULAR) - drawSVG( - "/assets/crashpatch/discord.svg", - (windowWidth / 2f / scale) - (discordMessageWidth / 2), - y + 273 + 158 + 24 + 20 + 15, - 20, - 20 - ) - drawURL( - if (CrashPatch.isSkyclient) SKYCLIENT_DISCORD else POLYFROST_DISCORD, - (windowWidth / 2f / scale) - (discordMessageWidth / 2) + 20 + 15, - y + 273 + 158 + 24 + 20 + 15 + 11, - 16, - Fonts.REGULAR, - inputHandler - ) - - val buttonsWidth = - returnToGameButton.width + if (type != GuiType.DISCONNECT) (getTextWidth( - OPEN_CRASH_LOG, - 14, - Fonts.MEDIUM - ) + 16 + 20) + 10 else 0f - returnToGameButton.update((windowWidth / 2f / scale) - (buttonsWidth / 2), y + 600 - 16 - 36f, inputHandler) - returnToGameButton.draw( - vg, (windowWidth / 2f / scale) - (buttonsWidth / 2), y + 600 - 16 - 36f, inputHandler - ) - if (type != GuiType.DISCONNECT) { - openCrashLogButton.update( - (windowWidth / 2f / scale) - (buttonsWidth / 2) + returnToGameButton.width + 10, - y + 600 - 16 - 36f, - inputHandler - ) - openCrashLogButton.draw( - vg, - (windowWidth / 2f / scale) - (buttonsWidth / 2) + returnToGameButton.width + 10, - y + 600 - 16 - 36f, - inputHandler - ) - } - } - } - - private fun VG.drawURL(url: String, x: Number, y: Number, size: Int, font: Font, inputHandler: InputHandler) { - drawText(url, x, y, HYPERLINK_BLUE, size, font) - val length = getTextWidth(url, size, font) - val hovered = inputHandler.isAreaHovered( - (x.toFloat() - 2), (y.toFloat() - size.toFloat()), (length + 4), (size.toFloat() * 2 + 2) - ) - if (hovered || (hyperlinkAnimation != null && (hyperlinkAnimation?.isReversed == false || !hyperlinkAnimation!!.isFinished))) { - if (!hovered && hyperlinkAnimation?.isReversed == false) { - hyperlinkAnimation = EaseInOutQuad(100, 0f, hyperlinkAnimation!!.get(), true) - } - if (hyperlinkAnimation == null) { - hyperlinkAnimation = EaseInOutQuad(100, 0f, 1f, false) - } - drawRect(x, y.toFloat() + size.toFloat() / 2, length, 2, ColorUtils.setAlpha(BLUE_600, (hyperlinkAnimation!!.get() * 255).toInt())) - if (hovered && inputHandler.isClicked) { - NetworkUtils.browseLink(url) - } - } else { - hyperlinkAnimation = null - } - } - - enum class GuiType { - INIT, NORMAL, DISCONNECT - } - - companion object { - internal var leaveWorldCrash = false - } -} \ No newline at end of file +//class CrashGui @JvmOverloads constructor( +// private val scanText: String, +// private val file: File?, +// private val susThing: String, +// private val type: GuiType = GuiType.NORMAL, +// val throwable: Throwable? = null +//) : UScreen(false) { +// @JvmOverloads +// constructor(report: CrashReport, type: GuiType = GuiType.NORMAL) : this( +// report.completeReport, +// report.file, +// (report as CrashReportHook).suspectedCrashPatchMods, +// type, +// report.crashCause +// ) +// +// private val crashPatchLogo = Icon("/assets/crashpatch/crashpatch_dark.svg") +// +// private val crashScan: CrashScan? by lazy { +// return@lazy CrashHelper.scanReport(scanText, type == GuiType.DISCONNECT) +// .let { return@let if (it != null && it.solutions.isNotEmpty()) it else null } +// } +// var shouldCrash = false +// +// private val subtitle by lazy { +// when (type) { +// GuiType.INIT -> listOf(SUBTITLE_INIT_1 + (if (crashScan != null) SUBTITLE_INIT_2 else "") + SUBTITLE_INIT_3) +// GuiType.NORMAL -> listOf(SUBTITLE_1, SUBTITLE_2) +// GuiType.DISCONNECT -> listOf(SUBTITLE_DISCONNECTED, SUBTITLE_DISCONNECTED_2) +// } +// } +// +// private val buttonFontSizeField = BasicButton::class.java.getDeclaredField("fontSize").apply { isAccessible = true } +// +// private val returnToGameButton by lazy { +// val button = BasicButton( +// NanoVGHelper.INSTANCE.getTextWidth(vg, RETURN_TO_GAME, 14f, Fonts.MEDIUM).toInt() + 40, +// 40, +// RETURN_TO_GAME, +// 2, +// ColorPalette.PRIMARY +// ) +// button.setClickAction { +// if (type == GuiType.INIT) { +// shouldCrash = true +// } else { +// restorePreviousScreen() +// } +// } +// buttonFontSizeField.setFloat(button, 14f) +// button +// } +// +// private val openCrashLogButton by lazy { +// val button = BasicButton( +// NanoVGHelper.INSTANCE.getTextWidth(vg, OPEN_CRASH_LOG, 14f, Fonts.MEDIUM).toInt() + 40 + 5 + 35, +// 40, +// OPEN_CRASH_LOG, +// null, +// SVG("/assets/crashpatch/open-external.svg"), +// 2, +// ColorPalette.TERTIARY +// ) +// button.setClickAction { +// file?.let { +// UDesktop.open(it) +// } +// } +// buttonFontSizeField.setFloat(button, 14f) +// button +// } +// +// private val uploadLogButton by lazy { +// val button = BasicButton( +// 30, +// 30, +// SVG("/assets/crashpatch/upload.svg"), +// 2, +// ColorPalette(GRAY_600, GRAY_700, GRAY_800) +// ) +// button.setClickAction { +// selectedSolution?.let { solution -> +// val link = +// InternetUtils.upload(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) +// setClipboardString(link) +// if (UDesktop.browse(URI.create(link))) { +// Notifications.INSTANCE.send( +// "CrashPatch", "Link copied to clipboard and opened in browser", crashPatchLogo +// ) +// } else { +// Notifications.INSTANCE.send( +// "CrashPatch", "Couldn't open link in browser, copied to clipboard instead.", crashPatchLogo +// ) +// } +// } +// } +// button +// } +// +// private val copyLogButton by lazy { +// val button = BasicButton( +// 30, +// 30, +// SVG("/assets/crashpatch/copy.svg"), +// 2, +// ColorPalette(GRAY_600, GRAY_700, GRAY_800) +// ) +// button.setClickAction { +// selectedSolution?.let { solution -> +// setClipboardString(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) +// Notifications.INSTANCE.send("CrashPatch", "Copied to clipboard", crashPatchLogo) +// } +// } +// button +// } +// +// private var scrollAnimation: Animation? = null +// private val colorAnimation = ColorAnimation( +// ColorPalette( +// ColorUtils.getColor(55, 59, 69, 76), +// ColorUtils.getColor(55, 59, 69, 153), +// ColorUtils.getColor(55, 59, 69, 255) +// ), 200 +// ) +// private var hyperlinkAnimation: EaseInOutQuad? = EaseInOutQuad(200, 0f, 1f, false) +// private var scrollTarget = 0f +// private var scrollTime = 0L +// private var mouseWasDown = false +// private var dragging = false +// private var yStart = 0f +// private var scroll = 0f +// +// private var lastHeight = 0f +// +// private var selectedSolution: CrashScan.Solution? = null +// +// private var vg = -1L +// +// private val inputHandler = InputHandler() +// +// override fun onDrawScreen(matrixStack: UMatrixStack, mouseX: Int, mouseY: Int, partialTicks: Float) { +// super.onDrawScreen(matrixStack, mouseX, mouseY, partialTicks) +// if (mc.theWorld == null && leaveWorldCrash) { +// drawDefaultBackground() +// } +// NanoVGHelper.INSTANCE.setupAndDraw { draw(it, inputHandler) } +// } +// +// override fun onScreenClose() { +// super.onScreenClose() +// leaveWorldCrash = false +// } +// +// fun draw(vg: Long, inputHandler: InputHandler) { +// this.vg = vg +// nanoVG(vg) { +// FontHelper.INSTANCE.loadFont(vg, JETBRAINS_MONO) +// val scale = OneConfigGui.getScaleFactor() +// val x = ((windowWidth - 650 * scale) / 2f / scale).toInt() +// val y = ((windowHeight - 600 * scale) / 2f / scale).toInt() +// scale(scale, scale) +// inputHandler.scale(scale.toDouble(), scale.toDouble()) +// drawRoundedRect(x, y, 650, 600, 20, GRAY_800) +// drawSVG("/assets/crashpatch/WarningTriangle.svg", x + 305 + 10, y + 24 + 10, 20, 20, javaClass) +// drawText( +// if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, +// (windowWidth / 2f / scale) - (getTextWidth( +// if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, +// 24, +// Fonts.MEDIUM +// ) / 2f), +// y + 56 + 22, +// WHITE_90, +// 24, +// Fonts.MEDIUM +// ) +// subtitle.forEachIndexed { index, s -> +// drawText( +// s, +// (windowWidth / 2f / scale) - (getTextWidth(s, 14, Fonts.REGULAR) / 2f), +// y + 56 + 87 + ((index - 1) * (14 * 1.75)), +// WHITE_80, +// 14, +// Fonts.REGULAR +// ) +// } +// +// drawText( +// if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, +// (windowWidth / 2f / scale) - (getTextWidth( +// if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, +// 16, +// Fonts.REGULAR +// ) / 2f), +// y + 56 + 87 + 10 + (subtitle.size * (14 * 1.75)), +// WHITE_80, +// 16, +// Fonts.REGULAR +// ) +// drawText( +// susThing, (windowWidth / 2f / scale) - (getTextWidth( +// susThing, 18, Fonts.SEMIBOLD +// ) / 2f), y + 56 + 87 + 10 + (subtitle.size * (14 * 1.75)) + 30, BLUE_400, 18, Fonts.SEMIBOLD +// ) +// +// drawRoundedRect(x + 50, y + 273, 550, 158, 12, GRAY_700) +// ScissorHelper.INSTANCE.scissor(vg, x + 50f, y + 273f, 550f, 37f).let { +// drawRoundedRect(x + 50, y + 273, 550, 158, 12, GRAY_600) +// ScissorHelper.INSTANCE.resetScissor(vg, it) +// } +// +// crashScan?.solutions?.let { solutions -> +// var i = 0 +// var lastTextWidth = 0f +// solutions.forEach { solution -> +// i++ +// if (i == 1 && selectedSolution == null) { +// selectedSolution = solution +// } +// drawText( +// solution.name, +// x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth else 0f), +// y + 273 + 18.5, +// WHITE_90, +// 12, +// Fonts.MEDIUM +// ) +// val textWidth = getTextWidth(solution.name, 12, Fonts.MEDIUM) +// if (selectedSolution != solution) { +// if (inputHandler.isAreaClicked( +// x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth else 0f), +// y + 273f, +// textWidth, +// 37f, +// ) +// ) { +// selectedSolution = solution +// scroll = 0f +// scrollTarget = 0f +// scrollTime = 0 +// scrollAnimation = null +// } +// } +// val hovered = inputHandler.isAreaHovered( +// x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth else 0f), +// y + 273f, +// textWidth, +// 37f, +// ) +// lastTextWidth += textWidth +// if (selectedSolution == solution || hovered) { +// drawRoundedRect( +// x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth - textWidth else 0f), +// y + 273 + 35, +// textWidth, +// 2, +// 1, +// if (selectedSolution == solution) BLUE_600 else ColorUtils.setAlpha(BLUE_600, 128) +// ) +// } +// if (selectedSolution == solution) { +// ScissorHelper.INSTANCE.scissor(vg, x + 50f + 20f, y + 310f, 550f - 20 - 20, 121f) +// .let { scissor -> +// val scrollBarLength = 89 / lastHeight * 89 +// if (lastHeight > 89) { +// scroll = scrollAnimation?.get() ?: scrollTarget +// val dWheel = Platform.getMousePlatform().dWheel.toFloat() * 0.3f +// if (dWheel != 0f) { +// scrollTarget += dWheel +// if (scrollTarget > 0f) scrollTarget = +// 0f else if (scrollTarget < -lastHeight + 89) scrollTarget = -lastHeight + 89 +// scrollAnimation = EaseOutQuad(150, scroll, scrollTarget, false) +// scrollTime = System.currentTimeMillis() +// } else if (scrollAnimation != null && scrollAnimation!!.isFinished) scrollAnimation = +// null +// if (dragging && inputHandler.isClicked(true)) { +// dragging = false +// } +// } +// var height = 0F +// translate(0f, scroll) +// solution.solutions.forEach { +// height += 12 * 1.25f +// drawWrappedString( +// it, +// x + 50 + 20, +// y + 310f + height, +// 550 - 20 - 20, +// WHITE_60, +// 12, +// 1.25f, +// JETBRAINS_MONO +// ) +// height += NanoVGHelper.INSTANCE.getWrappedStringHeight( +// vg, it, 550F, 12F, 1.25f, JETBRAINS_MONO +// ) +// } +// height += 12 * 1.25f +// translate(0f, -scroll) +// lastHeight = height +// ScissorHelper.INSTANCE.resetScissor(vg, scissor) +// if (lastHeight > 89) { +// val scrollBarY = scroll / lastHeight * 81 +// val isMouseDown = Platform.getMousePlatform().isButtonDown(0) +// val scrollHover = inputHandler.isAreaHovered( +// (x + 50f + 20f + 530f - 14f), +// (y + 310f + 16 - scrollBarY), +// 12f, +// scrollBarLength.toInt().toFloat() +// ) +// val scrollTimePeriod = System.currentTimeMillis() - scrollTime < 1000 +// if (scrollHover && isMouseDown && !mouseWasDown) { +// yStart = inputHandler.mouseY() +// dragging = true +// } +// mouseWasDown = isMouseDown +// if (dragging) { +// scrollTarget = -(inputHandler.mouseY() - yStart) * lastHeight / 89f +// if (scrollTarget > 0f) scrollTarget = +// 0f else if (scrollTarget < -lastHeight + 89) scrollTarget = +// -lastHeight + 89f +// scrollAnimation = EaseOutQuad(150, scroll, scrollTarget, false) +// } +// NanoVGHelper.INSTANCE.drawRoundedRect( +// vg, +// (x + 50f + 20f + 530f - 14f), +// (y + 310f + 16 - scrollBarY), +// 4f, +// scrollBarLength, +// colorAnimation.getColor(scrollHover || scrollTimePeriod, dragging), +// 4f +// ) +// } +// } +// } +// } +// } +// uploadLogButton.draw(vg, x + 600 - 8 - 11 - 30f, y + 273f + 3.5f, inputHandler) +// copyLogButton.draw(vg, x + 600 - 8 - 11 - 15 - 8 - 11 - 30f, y + 273f + 3.5f, inputHandler) +// +// drawText( +// "If the solution above doesn't help, join", (windowWidth / 2f / scale) - (getTextWidth( +// "If the solution above doesn't help, join", 16, Fonts.REGULAR +// ) / 2f), y + 273 + 158 + 24 + 20, WHITE_80, 16, Fonts.REGULAR +// ) +// val discordMessageWidth = 20 + 15 + getTextWidth("https://inv.wtf/skyclient", 16, Fonts.REGULAR) +// drawSVG( +// "/assets/crashpatch/discord.svg", +// (windowWidth / 2f / scale) - (discordMessageWidth / 2), +// y + 273 + 158 + 24 + 20 + 15, +// 20, +// 20 +// ) +// drawURL( +// if (CrashPatch.isSkyclient) SKYCLIENT_DISCORD else POLYFROST_DISCORD, +// (windowWidth / 2f / scale) - (discordMessageWidth / 2) + 20 + 15, +// y + 273 + 158 + 24 + 20 + 15 + 11, +// 16, +// Fonts.REGULAR, +// inputHandler +// ) +// +// val buttonsWidth = +// returnToGameButton.width + if (type != GuiType.DISCONNECT) (getTextWidth( +// OPEN_CRASH_LOG, +// 14, +// Fonts.MEDIUM +// ) + 16 + 20) + 10 else 0f +// returnToGameButton.update((windowWidth / 2f / scale) - (buttonsWidth / 2), y + 600 - 16 - 36f, inputHandler) +// returnToGameButton.draw( +// vg, (windowWidth / 2f / scale) - (buttonsWidth / 2), y + 600 - 16 - 36f, inputHandler +// ) +// if (type != GuiType.DISCONNECT) { +// openCrashLogButton.update( +// (windowWidth / 2f / scale) - (buttonsWidth / 2) + returnToGameButton.width + 10, +// y + 600 - 16 - 36f, +// inputHandler +// ) +// openCrashLogButton.draw( +// vg, +// (windowWidth / 2f / scale) - (buttonsWidth / 2) + returnToGameButton.width + 10, +// y + 600 - 16 - 36f, +// inputHandler +// ) +// } +// } +// } +// +// private fun VG.drawURL(url: String, x: Number, y: Number, size: Int, font: Font, inputHandler: InputHandler) { +// drawText(url, x, y, HYPERLINK_BLUE, size, font) +// val length = getTextWidth(url, size, font) +// val hovered = inputHandler.isAreaHovered( +// (x.toFloat() - 2), (y.toFloat() - size.toFloat()), (length + 4), (size.toFloat() * 2 + 2) +// ) +// if (hovered || (hyperlinkAnimation != null && (hyperlinkAnimation?.isReversed == false || !hyperlinkAnimation!!.isFinished))) { +// if (!hovered && hyperlinkAnimation?.isReversed == false) { +// hyperlinkAnimation = EaseInOutQuad(100, 0f, hyperlinkAnimation!!.get(), true) +// } +// if (hyperlinkAnimation == null) { +// hyperlinkAnimation = EaseInOutQuad(100, 0f, 1f, false) +// } +// drawRect(x, y.toFloat() + size.toFloat() / 2, length, 2, ColorUtils.setAlpha(BLUE_600, (hyperlinkAnimation!!.get() * 255).toInt())) +// if (hovered && inputHandler.isClicked) { +// NetworkUtils.browseLink(url) +// } +// } else { +// hyperlinkAnimation = null +// } +// } +// +// enum class GuiType { +// INIT, NORMAL, DISCONNECT +// } +// +// companion object { +// internal var leaveWorldCrash = false +// } +//} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt new file mode 100644 index 0000000..41a0ea7 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt @@ -0,0 +1,100 @@ +package org.polyfrost.crashpatch.gui + +import net.minecraft.client.gui.GuiScreen +import net.minecraft.crash.CrashReport +import org.polyfrost.crashpatch.crashes.CrashHelper +import org.polyfrost.crashpatch.crashes.CrashScan +import org.polyfrost.crashpatch.hooks.CrashReportHook +import org.polyfrost.oneconfig.api.ui.v1.PolyUIBuilder +import org.polyfrost.oneconfig.api.ui.v1.UIManager +import org.polyfrost.polyui.PolyUI +import org.polyfrost.polyui.component.impl.Block +import org.polyfrost.polyui.component.impl.Group +import org.polyfrost.polyui.component.impl.Image +import org.polyfrost.polyui.component.impl.Text +import org.polyfrost.polyui.component.named +import org.polyfrost.polyui.component.padded +import org.polyfrost.polyui.component.setPalette +import org.polyfrost.polyui.unit.Align +import org.polyfrost.polyui.unit.Vec2 +import org.polyfrost.polyui.utils.image +import org.polyfrost.polyui.utils.rgba +import java.io.File +import java.util.function.Consumer + +class CrashGuiRewrite @JvmOverloads constructor( + private val scanText: String, + private val file: File?, + private val susThing: String, + private val type: GuiType = GuiType.NORMAL, + val throwable: Throwable? = null +) { + + @JvmOverloads + constructor(report: CrashReport, type: GuiType = GuiType.NORMAL) : this( + report.completeReport, + report.file, + (report as CrashReportHook).suspectedCrashPatchMods, + type, + report.crashCause + ) + + companion object { + var leaveWorldCrash = false + } + + private val crashScan: CrashScan? by lazy { + return@lazy CrashHelper.scanReport(scanText, type == GuiType.DISCONNECT) + .let { return@let if (it != null && it.solutions.isNotEmpty()) it else null } + } + var shouldCrash = false + + private val subtitle by lazy { + when (type) { + GuiType.INIT -> listOf(SUBTITLE_INIT_1 + (if (crashScan != null) SUBTITLE_INIT_2 else "") + SUBTITLE_INIT_3, "") + GuiType.NORMAL -> listOf(SUBTITLE_1, SUBTITLE_2) + GuiType.DISCONNECT -> listOf(SUBTITLE_DISCONNECTED, SUBTITLE_DISCONNECTED_2) + } + } + + fun create(): GuiScreen { + val builder = PolyUIBuilder.builder().blurs().backgroundColor(rgba(21, 21, 21)).atResolution(1920f, 1080f).size(650f, 600f) + + val onClose: Consumer = Consumer { _: PolyUI -> + leaveWorldCrash = false + } + + builder.onClose(onClose) + + val polyUI = builder.make( + Group( + Image("/assets/crashpatch/WarningTriangle.svg".image(Vec2(20F, 20F))).named("WarningTriangle").padded( + 0F, + 34F, + 0F, + 0F + ), + Text(if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, fontSize = 24F, font = PolyUI.defaultFonts.medium).setPalette { text.primary }.padded(0f, 10F, 0f, 0f), + Text(subtitle[0], fontSize = 14F, font = PolyUI.defaultFonts.regular).setPalette { text.secondary }.padded(0f, 16f, 0f, 0f), + Text(subtitle[1], fontSize = 14F, font = PolyUI.defaultFonts.regular).setPalette { text.secondary }.padded(0f, 0F, 0f, 0f), + Text(if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, fontSize = 16F, font = PolyUI.defaultFonts.regular).setPalette { text.primary }.padded(0f, 24F, 0f, 0f), + Text(susThing, fontSize = 18F, font = PolyUI.defaultFonts.semiBold).setPalette { brand.fg }.padded(0f, 8f, 0f, 0f), + //Group( + // Block( +// + // ) + //).padded(0f, 40f, 0f, 0f), + size = Vec2(650f, 600f), + alignment = Align(mode = Align.Mode.Vertical) + ), + ) + val screen = + UIManager.INSTANCE.createPolyUIScreen(polyUI, 1920f, 1080f, false, true, onClose) + polyUI.window = UIManager.INSTANCE.createWindow() + return screen as GuiScreen + } + + enum class GuiType { + INIT, NORMAL, DISCONNECT + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt index 91aff29..d35dbc2 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt @@ -1,20 +1,19 @@ package org.polyfrost.crashpatch.gui -import cc.polyfrost.oneconfig.renderer.font.Font -import cc.polyfrost.oneconfig.utils.color.ColorUtils +import org.polyfrost.polyui.utils.rgba -internal val GRAY_800 = ColorUtils.getColor(21, 22, 23, 255) // general background -internal val GRAY_700 = ColorUtils.getColor(34, 35, 38, 255) // log background -internal val GRAY_600 = ColorUtils.getColor(42, 44, 48, 255) // log header +internal val GRAY_800 = rgba(21, 22, 23, 1f) // general background +internal val GRAY_700 = rgba(34, 35, 38, 1f) // log background +internal val GRAY_600 = rgba(42, 44, 48, 1f) // log header -internal val WHITE_90 = ColorUtils.getColor(255, 255, 255, 229) // text -internal val WHITE_80 = ColorUtils.getColor(255, 255, 255, 204) // subtext -internal val WHITE_60 = ColorUtils.getColor(255, 255, 255, 153) // logs +internal val WHITE_90 = rgba(255, 255, 255, 229 / 255f) // text +internal val WHITE_80 = rgba(255, 255, 255, 204 / 255f) // subtext +internal val WHITE_60 = rgba(255, 255, 255, 153 / 255f) // logs -internal val BLUE_400 = ColorUtils.getColor(77, 135, 229) // yeah -internal val BLUE_600 = ColorUtils.getColor(20, 82, 204, 255) // brand.hover +internal val BLUE_400 = rgba(77, 135, 229) // yeah +internal val BLUE_600 = rgba(20, 82, 204, 1F) // brand.hover -internal val HYPERLINK_BLUE = ColorUtils.getColor(48, 129, 242) +internal val HYPERLINK_BLUE = rgba(48, 129, 242) internal val TITLE = "Uh-oh. Your game crashed!" internal val DISCONNECTED_TITLE = "Uh-oh. You were disconnected from the server!" @@ -33,6 +32,4 @@ internal val RETURN_TO_GAME = "Return to game" internal val OPEN_CRASH_LOG = "Crash log" internal val SKYCLIENT_DISCORD = "https://inv.wtf/skyclient" -internal val POLYFROST_DISCORD = "https://polyfrost.cc/discord" - -internal val JETBRAINS_MONO = Font("jetbrains-mono-regular", "/assets/crashpatch/fonts/JetBrainsMono-Regular.ttf") \ No newline at end of file +internal val POLYFROST_DISCORD = "https://polyfrost.cc/discord" \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt index 7e9b395..456d408 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt @@ -5,9 +5,9 @@ import org.polyfrost.crashpatch.mixin.AccessorGuiDisconnected import net.minecraft.client.gui.GuiDisconnected import net.minecraft.client.gui.GuiScreen import org.spongepowered.asm.mixin.injection.callback.CallbackInfo -import cc.polyfrost.oneconfig.utils.dsl.mc import org.polyfrost.crashpatch.config.CrashPatchConfig -import org.polyfrost.crashpatch.gui.CrashGui +import org.polyfrost.crashpatch.gui.CrashGuiRewrite +import org.polyfrost.crashpatch.mc object GuiDisconnectedHook { fun onGUIDisplay(i: GuiScreen?, ci: CallbackInfo) { @@ -16,7 +16,7 @@ object GuiDisconnectedHook { val scan = scanReport(gui.message.formattedText, true) if (scan != null && scan.solutions.size > 1) { ci.cancel() - mc.displayGuiScreen(CrashGui(gui.message.formattedText, null, gui.reason, CrashGui.GuiType.DISCONNECT)) + mc.displayGuiScreen(CrashGuiRewrite(gui.message.formattedText, null, gui.reason, CrashGuiRewrite.GuiType.DISCONNECT).create()) } } } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/utils/InternetUtils.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/InternetUtils.kt index 2d14210..7d5cf58 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/utils/InternetUtils.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/InternetUtils.kt @@ -14,7 +14,7 @@ import javax.net.ssl.HttpsURLConnection object InternetUtils { - private val sessionIdRegex = Regex("((Session ID is|--accessToken|Your new API key is) (?:\\S+))") + private val sessionIdRegex = Regex("((Session ID is|--accessToken|Your new API key is) (\\S+))") fun upload(text: String): String { val log = Log(text.replace(sessionIdRegex, "[SENSITIVE INFORMATION]")) return when (CrashPatchConfig.crashLogUploadMethod) { From 89b51122adc50b16e774d541459b18a4b55e457f Mon Sep 17 00:00:00 2001 From: Deftu Date: Sat, 12 Oct 2024 16:10:24 +0200 Subject: [PATCH 02/37] Convert project from PGT to DGT --- build.gradle.kts | 181 ++++------------------- gradle.properties | 13 +- gradle/wrapper/gradle-wrapper.properties | 2 +- root.gradle.kts | 4 +- settings.gradle.kts | 29 +++- 5 files changed, 55 insertions(+), 174 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f88d63b..8f0fc63 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,78 +1,49 @@ @file:Suppress("UnstableApiUsage", "PropertyName") -import org.polyfrost.gradle.util.noServerRunConfigs -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import dev.deftu.gradle.utils.GameSide -// Adds support for kotlin, and adds the Polyfrost Gradle Toolkit -// which we use to prepare the environment. plugins { - kotlin("jvm") - id("org.polyfrost.multi-version") - id("org.polyfrost.defaults.repo") - id("org.polyfrost.defaults.java") - id("org.polyfrost.defaults.loom") - id("com.github.johnrengelman.shadow") - id("net.kyori.blossom") version "1.3.2" - id("signing") java + kotlin("jvm") + id("dev.deftu.gradle.multiversion") + id("dev.deftu.gradle.tools") + id("dev.deftu.gradle.tools.resources") + id("dev.deftu.gradle.tools.bloom") + id("dev.deftu.gradle.tools.shadow") + id("dev.deftu.gradle.tools.minecraft.loom") } -// Gets the mod name, version and id from the `gradle.properties` file. -val mod_name: String by project -val mod_version: String by project -val mod_id: String by project -val mod_archives_name: String by project +toolkitLoomHelper { + // Adds OneConfig to our project + useOneConfig(mcData.version, mcData.loader, "commands", "config-impl", "events", "internal", "ui") -// Replaces the variables in `ExampleMod.java` to the ones specified in `gradle.properties`. -blossom { - replaceToken("@VER@", mod_version) - replaceToken("@NAME@", mod_name) - replaceToken("@ID@", mod_id) -} + // Removes the server configs from IntelliJ IDEA, leaving only client runs. + // If you're developing a server-side mod, you can remove this line. + disableRunConfigs(GameSide.SERVER) -// Sets the mod version to the one specified in `gradle.properties`. Make sure to change this following semver! -version = mod_version -// Sets the group, make sure to change this to your own. It can be a website you own backwards or your GitHub username. -// e.g. com.github. or com. -group = "org.polyfrost" + // Sets up our Mixin refmap naming + if (!mcData.isNeoForge) { + useMixinRefMap(modData.id) + } -// Sets the name of the output jar (the one you put in your mods folder and send to other people) -// It outputs all versions of the mod into the `build` directory. -base { - archivesName.set("$mod_archives_name-$platform") + // Adds the tweak class if we are building legacy version of forge as per the documentation (https://docs.polyfrost.org) + if (mcData.isLegacyForge) { + useTweaker("org.polyfrost.crashpatch.hooks.ModsCheckerPlugin", GameSide.CLIENT) + useForgeMixin(modData.id) // Configures the mixins if we are building for forge, useful for when we are dealing with cross-platform projects. + } } -// Configures the Polyfrost Loom, our plugin fork to easily set up the programming environment. loom { - // Removes the server configs from IntelliJ IDEA, leaving only client runs. - // If you're developing a server-side mod, you can remove this line. - noServerRunConfigs() - - // Adds the tweak class if we are building legacy version of forge as per the documentation (https://docs.polyfrost.org) - if (project.platform.isLegacyForge) { + if (mcData.isLegacyForge) { runConfigs { "client" { - programArgs("--tweakClass", "org.polyfrost.crashpatch.hooks.ModsCheckerPlugin") programArgs("--tweakClass", "org.polyfrost.oneconfig.internal.legacy.OneConfigTweaker") programArgs("--tweakClass", "org.spongepowered.asm.launch.MixinTweaker") - //property("fml.coreMods.load", "") property("mixin.debug.export", "true") + //property("fml.coreMods.load", "") } } } - // Configures the mixins if we are building for forge, useful for when we are dealing with cross-platform projects. - if (project.platform.isForge) { - forge { - mixinConfig("mixin.${mod_id}.json") - } - } - // Configures the name of the mixin "refmap" using an experimental loom api. - mixin.defaultRefmapName.set("mixin.${mod_id}.refmap.json") -} - -// Creates the shade/shadow configuration, so we can include libraries inside our mod, rather than having to add them separately. -val shade: Configuration by configurations.creating { - configurations.implementation.get().extendsFrom(this) } // Configures the output directory for when building from the `src/resources` directory. @@ -90,109 +61,9 @@ repositories { // Configures the libraries/dependencies for your mod. dependencies { - // Adds the OneConfig library, so we can develop with it. - val oneconfig = "1.0.0-alpha.19" - implementation("org.polyfrost.oneconfig:config-impl:$oneconfig") - implementation("org.polyfrost.oneconfig:commands:$oneconfig") - implementation("org.polyfrost.oneconfig:events:$oneconfig") - implementation("org.polyfrost.oneconfig:ui:$oneconfig") - implementation("org.polyfrost.oneconfig:internal:$oneconfig") - modImplementation("org.polyfrost.oneconfig:$platform:$oneconfig") - - modRuntimeOnly("me.djtheredstoner:DevAuth-${if (platform.isFabric) "fabric" else if (platform.isLegacyForge) "forge-legacy" else "forge-latest"}:1.2.0") shade("gs.mclo:api:3.0.1") // If we are building for legacy forge, includes the launch wrapper with `shade` as we configured earlier. - if (platform.isLegacyForge) { + if (mcData.isLegacyForge) { compileOnly("org.spongepowered:mixin:0.7.11-SNAPSHOT") } -} - -tasks { - // Processes the `src/resources/mcmod.info or fabric.mod.json` and replaces - // the mod id, name and version with the ones in `gradle.properties` - processResources { - inputs.property("id", mod_id) - inputs.property("name", mod_name) - val java = if (project.platform.mcMinor >= 18) { - 17 // If we are playing on version 1.18, set the java version to 17 - } else { - // Else if we are playing on version 1.17, use java 16. - if (project.platform.mcMinor == 17) - 16 - else - 8 // For all previous versions, we **need** java 8 (for Forge support). - } - val compatLevel = "JAVA_${java}" - inputs.property("java", java) - inputs.property("java_level", compatLevel) - inputs.property("version", mod_version) - inputs.property("mcVersionStr", project.platform.mcVersionStr) - filesMatching(listOf("mcmod.info", "mixin.${mod_id}.json", "mods.toml")) { - expand( - mapOf( - "id" to mod_id, - "name" to mod_name, - "java" to java, - "java_level" to compatLevel, - "version" to mod_version, - "mcVersionStr" to project.platform.mcVersionStr - ) - ) - } - filesMatching("fabric.mod.json") { - expand( - mapOf( - "id" to mod_id, - "name" to mod_name, - "java" to java, - "java_level" to compatLevel, - "version" to mod_version, - "mcVersionStr" to project.platform.mcVersionStr.substringBeforeLast(".") + ".x" - ) - ) - } - } - - // Configures the resources to include if we are building for forge or fabric. - withType(Jar::class.java) { - if (project.platform.isFabric) { - exclude("mcmod.info", "mods.toml") - } else { - exclude("fabric.mod.json") - if (project.platform.isLegacyForge) { - exclude("mods.toml") - } else { - exclude("mcmod.info") - } - } - } - - // Configures our shadow/shade configuration, so we can - // include some dependencies within our mod jar file. - named("shadowJar") { - archiveClassifier.set("dev") // TODO: machete gets confused by the `dev` prefix. - configurations = listOf(shade) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - } - - remapJar { - inputFile.set(shadowJar.get().archiveFile) - archiveClassifier.set("") - } - - jar { - // Sets the jar manifest attributes. - if (platform.isLegacyForge) { - manifest.attributes += mapOf( - "ModSide" to "CLIENT", // We aren't developing a server-side mod, so this is fine. - "ForceLoadAsMod" to true, // We want to load this jar as a mod, so we force Forge to do so. - "TweakOrder" to "0", // Makes sure that the OneConfig launch wrapper is loaded as soon as possible. - "MixinConfigs" to "mixin.${mod_id}.json", // We want to use our mixin configuration, so we specify it here. - "TweakClass" to "org.polyfrost.crashpatch.hooks.ModsCheckerPlugin" // Loads the OneConfig launch wrapper. - ) - } - dependsOn(shadowJar) - archiveClassifier.set("") - enabled = false - } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 5c335ba..80716ed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,11 @@ -mod_id=crashpatch -mod_name=CrashPatch -mod_version=2.0.1 -mod_archives_name=CrashPatch - # Gradle Configuration -- DO NOT TOUCH THESE VALUES. -polyfrost.defaults.loom=3 org.gradle.daemon=true org.gradle.parallel=true org.gradle.configureoncommand=true org.gradle.parallel.threads=4 -org.gradle.jvmargs=-Xmx2G \ No newline at end of file +org.gradle.jvmargs=-Xmx2G + +mod.group=org.polyfrost +mod.id=crashpatch +mod.name=CrashPatch +mod.version=2.0.1 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f136d42..1672508 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip diff --git a/root.gradle.kts b/root.gradle.kts index 0234352..234330e 100644 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -1,7 +1,5 @@ plugins { - kotlin("jvm") version "1.9.10" apply false - id("org.polyfrost.multi-version.root") - id("com.github.johnrengelman.shadow") version "8.1.1" apply false + id("dev.deftu.gradle.multiversion-root") } preprocess { diff --git a/settings.gradle.kts b/settings.gradle.kts index 401cef3..7b8a90e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,21 +1,34 @@ -@file:Suppress("PropertyName") +import groovy.lang.MissingPropertyException pluginManagement { repositories { + // Repositories + maven("https://maven.deftu.dev/releases") + maven("https://maven.fabricmc.net") + maven("https://maven.architectury.dev/") + maven("https://maven.minecraftforge.net") + maven("https://repo.essential.gg/repository/maven-public") + maven("https://server.bbkr.space/artifactory/libs-release/") + maven("https://jitpack.io/") + + // Snapshots + maven("https://maven.deftu.dev/snapshots") + mavenLocal() + + // Default repositories gradlePluginPortal() mavenCentral() - maven("https://repo.polyfrost.org/releases") // Adds the Polyfrost maven repository to get Polyfrost Gradle Toolkit } + plugins { - val pgtVersion = "0.6.5" // Sets the default versions for Polyfrost Gradle Toolkit - id("org.polyfrost.multi-version.root") version pgtVersion + kotlin("jvm") version("2.0.0") + id("dev.deftu.gradle.multiversion-root") version("2.11.0") } } -val mod_name: String by settings - -// Configures the root project Gradle name based on the value in `gradle.properties` -rootProject.name = mod_name +val projectName: String = extra["mod.name"]?.toString() + ?: throw MissingPropertyException("mod.name has not been set.") +rootProject.name = projectName rootProject.buildFileName = "root.gradle.kts" // Adds all of our build target versions to the classpath if we need to add version-specific code. From c17e829cd0b268fa4d1e409abcbf318bfc88f5b4 Mon Sep 17 00:00:00 2001 From: Deftu Date: Sat, 12 Oct 2024 16:11:29 +0200 Subject: [PATCH 03/37] Actually add mclo.gs as a dependency --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 8f0fc63..b6ecb48 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -61,7 +61,7 @@ repositories { // Configures the libraries/dependencies for your mod. dependencies { - shade("gs.mclo:api:3.0.1") + implementation(shade("gs.mclo:api:3.0.1")!!) // If we are building for legacy forge, includes the launch wrapper with `shade` as we configured earlier. if (mcData.isLegacyForge) { compileOnly("org.spongepowered:mixin:0.7.11-SNAPSHOT") From 1d3e6e9bc0c05f2b8578187db33b71cf38e1f8c0 Mon Sep 17 00:00:00 2001 From: Deftu Date: Sun, 13 Oct 2024 11:21:37 +0200 Subject: [PATCH 04/37] Use correct templating syntax --- src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt | 8 ++++---- src/main/resources/mcmod.info | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index 50ef5b2..f4b9eec 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -20,9 +20,9 @@ import java.io.File @Mod(modid = CrashPatch.MODID, version = CrashPatch.VERSION, name = CrashPatch.NAME, modLanguageAdapter = "org.polyfrost.oneconfig.utils.v1.forge.KotlinLanguageAdapter") object CrashPatch { - const val MODID = "@ID@" - const val NAME = "@NAME@" - const val VERSION = "@VER@" + const val MODID = "@MOD_ID@" + const val NAME = "@MOD_NAME@" + const val VERSION = "@MOD_VERSION@" val isSkyclient by lazy(LazyThreadSafetyMode.PUBLICATION) { File(mcDir, "OneConfig/CrashPatch/SKYCLIENT").exists() || File( mcDir, "W-OVERFLOW/CrashPatch/SKYCLIENT").exists() || ModsCheckerPlugin.modsMap.keys.any { it == "skyclientcosmetics" || it == "scc" || it == "skyclientaddons" || it == "skyblockclientupdater" || it == "skyclientupdater" || it == "skyclientcore" } } @@ -42,7 +42,7 @@ object CrashPatch { CommandManager.registerCommand(CrashPatchCommand()) CrashPatchConfig // uncomment to test init screen crashes - // throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") + throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") } @Command("crashpatch") diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index d438afa..337ce8e 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -3,7 +3,7 @@ "modid": "crashpatch", "name": "CrashPatch", "description": "Stop crashes from closing your game!", - "version": "${version}", + "version": "${mod_version}", "mcversion": "1.8.9", "url": "https://github.com/Polyfrost/CrashPatch", "updateUrl": "https://github.com/Polyfrost/CrashPatch/releases/latest", From c4571bccdb286fa272461dd1e497c550b0c81a80 Mon Sep 17 00:00:00 2001 From: Deftu Date: Sun, 13 Oct 2024 11:32:11 +0200 Subject: [PATCH 05/37] Update DGT to 2.11.1 --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 7b8a90e..67ecde2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,7 +22,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.11.0") + id("dev.deftu.gradle.multiversion-root") version("2.11.1") } } From bd303842bae03b75c00e78d81d7b6e45dc402aad Mon Sep 17 00:00:00 2001 From: Deftu Date: Sun, 13 Oct 2024 11:32:26 +0200 Subject: [PATCH 06/37] Migrate to new PolyUI version --- .../crashpatch/gui/CrashGuiRewrite.kt | 19 +++++++++++-------- .../org/polyfrost/crashpatch/gui/constants.kt | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt index 41a0ea7..bdb536f 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt @@ -5,20 +5,19 @@ import net.minecraft.crash.CrashReport import org.polyfrost.crashpatch.crashes.CrashHelper import org.polyfrost.crashpatch.crashes.CrashScan import org.polyfrost.crashpatch.hooks.CrashReportHook -import org.polyfrost.oneconfig.api.ui.v1.PolyUIBuilder +import org.polyfrost.oneconfig.api.ui.v1.OCPolyUIBuilder import org.polyfrost.oneconfig.api.ui.v1.UIManager import org.polyfrost.polyui.PolyUI -import org.polyfrost.polyui.component.impl.Block +import org.polyfrost.polyui.color.rgba +import org.polyfrost.polyui.component.extensions.named +import org.polyfrost.polyui.component.extensions.padded +import org.polyfrost.polyui.component.extensions.setPalette import org.polyfrost.polyui.component.impl.Group import org.polyfrost.polyui.component.impl.Image import org.polyfrost.polyui.component.impl.Text -import org.polyfrost.polyui.component.named -import org.polyfrost.polyui.component.padded -import org.polyfrost.polyui.component.setPalette import org.polyfrost.polyui.unit.Align import org.polyfrost.polyui.unit.Vec2 import org.polyfrost.polyui.utils.image -import org.polyfrost.polyui.utils.rgba import java.io.File import java.util.function.Consumer @@ -58,13 +57,17 @@ class CrashGuiRewrite @JvmOverloads constructor( } fun create(): GuiScreen { - val builder = PolyUIBuilder.builder().blurs().backgroundColor(rgba(21, 21, 21)).atResolution(1920f, 1080f).size(650f, 600f) + val builder = OCPolyUIBuilder.create() + .atResolution(1920f, 1080f) + .blurs() + .backgroundColor(rgba(21, 21, 21)) + .size(650f, 600f) val onClose: Consumer = Consumer { _: PolyUI -> leaveWorldCrash = false } - builder.onClose(onClose) + // builder.onClose(onClose) val polyUI = builder.make( Group( diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt index d35dbc2..afd1e98 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt @@ -1,6 +1,6 @@ package org.polyfrost.crashpatch.gui -import org.polyfrost.polyui.utils.rgba +import org.polyfrost.polyui.color.rgba internal val GRAY_800 = rgba(21, 22, 23, 1f) // general background internal val GRAY_700 = rgba(34, 35, 38, 1f) // log background From 63b17cc95e3c46ab201c186f9314eac91879c198 Mon Sep 17 00:00:00 2001 From: Deftu Date: Tue, 15 Oct 2024 16:53:01 +0200 Subject: [PATCH 07/37] Porting stuff --- build.gradle.kts | 15 +- settings.gradle.kts | 2 +- .../crashpatch/hooks/ModsCheckerPlugin.java | 369 ------------------ .../crashpatch/mixin/MixinMinecraft.java | 5 + .../org/polyfrost/crashpatch/CrashPatch.kt | 15 +- .../crashpatch/config/CrashPatchConfig.kt | 4 +- ...crashpatch.json => mixins.crashpatch.json} | 0 .../resources/polyfrost/stage0.properties | 3 + 8 files changed, 23 insertions(+), 390 deletions(-) delete mode 100644 src/main/java/org/polyfrost/crashpatch/hooks/ModsCheckerPlugin.java rename src/main/resources/{mixin.crashpatch.json => mixins.crashpatch.json} (100%) create mode 100644 src/main/resources/polyfrost/stage0.properties diff --git a/build.gradle.kts b/build.gradle.kts index b6ecb48..24989b2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,24 +28,11 @@ toolkitLoomHelper { // Adds the tweak class if we are building legacy version of forge as per the documentation (https://docs.polyfrost.org) if (mcData.isLegacyForge) { - useTweaker("org.polyfrost.crashpatch.hooks.ModsCheckerPlugin", GameSide.CLIENT) + useTweaker("org.polyfrost.oneconfig.internal.legacy.OneConfigTweaker", GameSide.CLIENT) useForgeMixin(modData.id) // Configures the mixins if we are building for forge, useful for when we are dealing with cross-platform projects. } } -loom { - if (mcData.isLegacyForge) { - runConfigs { - "client" { - programArgs("--tweakClass", "org.polyfrost.oneconfig.internal.legacy.OneConfigTweaker") - programArgs("--tweakClass", "org.spongepowered.asm.launch.MixinTweaker") - property("mixin.debug.export", "true") - //property("fml.coreMods.load", "") - } - } - } -} - // Configures the output directory for when building from the `src/resources` directory. sourceSets { main { diff --git a/settings.gradle.kts b/settings.gradle.kts index 67ecde2..70cb3f4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,7 +22,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.11.1") + id("dev.deftu.gradle.multiversion-root") version("2.11.2") } } diff --git a/src/main/java/org/polyfrost/crashpatch/hooks/ModsCheckerPlugin.java b/src/main/java/org/polyfrost/crashpatch/hooks/ModsCheckerPlugin.java deleted file mode 100644 index 0e6d6f5..0000000 --- a/src/main/java/org/polyfrost/crashpatch/hooks/ModsCheckerPlugin.java +++ /dev/null @@ -1,369 +0,0 @@ -package org.polyfrost.crashpatch.hooks; - -import com.google.common.collect.Lists; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.stream.MalformedJsonException; -import net.minecraft.launchwrapper.ITweaker; -import net.minecraft.launchwrapper.Launch; -import net.minecraft.launchwrapper.LaunchClassLoader; -import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion; -import net.minecraftforge.fml.relauncher.CoreModManager; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.spongepowered.asm.launch.MixinBootstrap; -import org.spongepowered.asm.launch.MixinTweaker; - -import javax.swing.*; -import java.awt.*; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.security.CodeSource; -import java.util.List; -import java.util.*; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -public class ModsCheckerPlugin implements ITweaker { //todo - private static final JsonParser PARSER = new JsonParser(); - public static final HashMap> modsMap = new HashMap<>(); //modid : file, version, name - - @Override - public void acceptOptions(List args, File gameDir, File assetsDir, String profile) { - - } - - @Override - public void injectIntoClassLoader(LaunchClassLoader classLoader) { - try { - File modsFolder = new File(getMcDir(), "mods"); - File[] modFolder = modsFolder.listFiles((dir, name) -> name.endsWith(".jar")); - HashMap>> dupeMap = new HashMap<>(); - if (modFolder != null) { - for (File file : modFolder) { - try { - try (ZipFile mod = new ZipFile(file)) { - ZipEntry entry = mod.getEntry("mcmod.info"); - if (entry != null) { - try (InputStream inputStream = mod.getInputStream(entry)) { - byte[] availableBytes = new byte[inputStream.available()]; - inputStream.read(availableBytes, 0, inputStream.available()); - JsonObject modInfo = PARSER.parse(new String(availableBytes)).getAsJsonArray().get(0).getAsJsonObject(); - if (!modInfo.has("modid") || !modInfo.has("version")) { - continue; - } - - String modid = modInfo.get("modid").getAsString(); - if (modsMap.containsKey(modid)) { - if (dupeMap.containsKey(modid)) { - dupeMap.get(modid).add(new Triple<>(file, modInfo.get("version").getAsString(), modInfo.has("name") ? modInfo.get("name").getAsString() : modid)); - } else { - dupeMap.put(modid, Lists.newArrayList(modsMap.get(modid), new Triple<>(file, modInfo.get("version").getAsString(), modInfo.has("name") ? modInfo.get("name").getAsString() : modid))); - } - } else { - modsMap.put(modid, new Triple<>(file, modInfo.get("version").getAsString(), modInfo.has("name") ? modInfo.get("name").getAsString() : modid)); - } - } - } - } - } catch (MalformedJsonException | IllegalStateException ignored) { - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - Iterator>> iterator = dupeMap.values().iterator(); - - boolean isSkyClient = new File(getMcDir(), "W-OVERFLOW/CrashPatch/SKYCLIENT").exists() || containsAnyKey(ModsCheckerPlugin.modsMap, "skyclientcosmetics", "scc", "skyclientaddons", "skyblockclientupdater", "skyclientupdater", "skyclientcore"); - while (iterator.hasNext()) { - try { - ArrayList> next = iterator.next(); - List> blank = next.stream().sorted((a, b) -> { - if (a != null && b != null) { - try { - int value = new DefaultArtifactVersion(substringBeforeAny(a.second, "-beta", "-alpha", "-pre", "+beta", "+alpha", "+pre")).compareTo(new DefaultArtifactVersion(substringBeforeAny(b.second, "-beta", "-alpha", "-pre", "+beta", "+alpha", "+pre"))); - return -value; - } catch (Exception e) { - e.printStackTrace(); - return Long.compare(a.first.lastModified(), b.first.lastModified()) * -1; - } - } - return 0; - }).collect(Collectors.toList()); - next.clear(); - next.addAll(blank); - ListIterator> otherIterator = next.listIterator(); - int index = 0; - while (otherIterator.hasNext()) { - Triple remove = otherIterator.next(); - ++index; - if (index != 1) { - if (tryDeleting(remove.first)) { - otherIterator.remove(); - } else { - doThatPopupThing(modsFolder, "Duplicate mods have been detected! These mods are...\n" + - getStringOf(dupeMap.values()) + "\nPlease removes these mods from your mod folder, which is opened." + (isSkyClient ? " GO TO https://inv.wtf/skyclient FOR MORE INFORMATION." : "")); - } - } - } - if (next.size() <= 1) { - iterator.remove(); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - - if (!dupeMap.isEmpty()) { - doThatPopupThing(modsFolder, "Duplicate mods have been detected! These mods are...\n" + - getStringOf(dupeMap.values()) + "\nPlease removes these mods from your mod folder, which is opened." + (isSkyClient ? " GO TO https://inv.wtf/skyclient FOR MORE INFORMATION." : "")); - } - } catch (Exception e) { - e.printStackTrace(); - } - - //todo terrible - - //CodeSource codeSource = this.getClass().getProtectionDomain().getCodeSource(); - //if (codeSource != null) { - // URL location = codeSource.getLocation(); - // try { - // File file = new File(location.toURI()); - // if (file.isFile()) { - // CoreModManager.getIgnoredMods().remove(file.getName()); - // CoreModManager.getReparseableCoremods().add(file.getName()); - // try { - // try { - // List tweakClasses = (List) Launch.blackboard.get("TweakClasses"); // tweak classes before other mod trolling - // if (tweakClasses.contains("org.spongepowered.asm.launch.MixinTweaker")) { // if there's already a mixin tweaker, we'll just load it like "usual" - // new MixinTweaker(); // also we might not need to make a new mixin tweawker all the time but im just making sure - // } else if (!Launch.blackboard.containsKey("mixin.initialised")) { // if there isnt, we do our own trolling - // List tweaks = (List) Launch.blackboard.get("Tweaks"); - // tweaks.add(new MixinTweaker()); - // } - // } catch (Exception ignored) { - // // if it fails i *think* we can just ignore it - // } - // try { - // MixinBootstrap.getPlatform().addContainer(location.toURI()); - // } catch (Exception ignore) { - // // fuck you essential - // try { - // Class containerClass = Class.forName("org.spongepowered.asm.launch.platform.container.IContainerHandle"); - // Class urlContainerClass = Class.forName("org.spongepowered.asm.launch.platform.container.ContainerHandleURI"); - // Object container = urlContainerClass.getConstructor(URI.class).newInstance(location.toURI()); - // MixinBootstrap.getPlatform().getClass().getDeclaredMethod("addContainer", containerClass).invoke(MixinBootstrap.getPlatform(), container); - // } catch (Exception e) { - // e.printStackTrace(); - // throw new RuntimeException("OneConfig's Mixin loading failed. Please contact https://polyfrost.cc/discord to resolve this issue!"); - // } - // } - // } catch (Exception ignored) { -// - // } - // } - // } catch (URISyntaxException ignored) {} - //} else { - // LogManager.getLogger().warn("No CodeSource, if this is not a development environment we might run into problems!"); - // LogManager.getLogger().warn(this.getClass().getProtectionDomain()); - //} -// - //super.injectIntoClassLoader(classLoader); - } - - @Override - public String getLaunchTarget() { - return null; - } - - @Override - public String[] getLaunchArguments() { - return new String[0]; - } - - private static void doThatPopupThing(File modsFolder, String message) { - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception e) { - e.printStackTrace(); - } - - JFrame frame = new JFrame(); - frame.setUndecorated(true); - frame.setAlwaysOnTop(true); - frame.setLocationRelativeTo(null); - frame.setVisible(true); - - DesktopManager.open(modsFolder); - JOptionPane.showMessageDialog(frame, message, "Duplicate Mods Detected!", JOptionPane.ERROR_MESSAGE); - try { - Class exitClass = Class.forName("java.lang.Shutdown"); - Method exit = exitClass.getDeclaredMethod("exit", int.class); - exit.setAccessible(true); - exit.invoke(null, 0); - } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | - InvocationTargetException e) { - e.printStackTrace(); - } - } - - @SafeVarargs - private final boolean containsAnyKey(HashMap hashMap, A... any) { - for (A thing : any) { - if (hashMap.containsKey(thing)) return true; - } - return false; - } - - private String getStringOf(Collection>> dupes) { - StringBuilder builder = new StringBuilder(); - int index = 0; - for (ArrayList> list : dupes) { - ++index; - builder.append("\n"); - for (Triple triple : list) { - builder.append(" ").append(triple.first.getAbsolutePath()); - } - if (index != dupes.size()) builder.append("\nAND"); - } - return builder.toString().trim(); - } - - private String substringBeforeAny(String string, String... values) { - String returnString = string; - for (String value : values) { - if (returnString.contains(value)) { - returnString = StringUtils.substringBefore(returnString, value); - } - } - return returnString; - } - - private boolean tryDeleting(File file) { - if (!file.delete()) { - if (!file.delete()) { - if (!file.delete()) { - file.deleteOnExit(); - return false; - } - } - } - return true; - } - - public static class Triple { - public A first; - public B second; - public C third; - - public Triple(A a, B b, C c) { - first = a; - second = b; - third = c; - } - - @Override - public String toString() { - return "Triple{" + - "first=" + first + - ", second=" + second + - ", third=" + third + - '}'; - } - } - - /** - * Taken from UniversalCraft under LGPLv3 - * https://github.com/EssentialGG/UniversalCraft/blob/master/LICENSE - */ - private static class DesktopManager { - private static final boolean isLinux; - private static final boolean isXdg; - private static boolean isKde; - private static boolean isGnome; - private static final boolean isMac; - private static final boolean isWindows; - - static { - String osName; - try { - osName = System.getProperty("os.name"); - } catch (SecurityException ignored) { - osName = null; - } - isLinux = osName != null && (osName.startsWith("Linux") || osName.startsWith("LINUX")); - isMac = osName != null && osName.startsWith("Mac"); - isWindows = osName != null && osName.startsWith("Windows"); - if (isLinux) { - String xdg = System.getenv("XDG_SESSION_ID"); - isXdg = xdg != null && !xdg.isEmpty(); - String gdm = System.getenv("GDMSESSION"); - if (gdm != null) { - String lowercaseGDM = gdm.toLowerCase(Locale.ENGLISH); - isGnome = lowercaseGDM.contains("gnome"); - isKde = lowercaseGDM.contains("kde"); - } - } else { - isXdg = false; - isKde = false; - isGnome = false; - } - } - - - public static void open(File file) { - if (!openDesktop(file)) { - openSystemSpecific(file.getPath()); - } - } - - private static boolean openSystemSpecific(String file) { - return isLinux ? (isXdg ? runCommand("xdg-open \"" + file + '"') : (isKde ? runCommand("kde-open \"" + file + '"') : (isGnome ? runCommand("gnome-open \"" + file + '"') : runCommand("kde-open \"" + file + '"') || runCommand("gnome-open \"" + file + '"')))) : (isMac ? runCommand("open \"" + file + '"') : (isWindows && runCommand("explorer \"" + file + '"'))); - } - - private static boolean openDesktop(File file) { - boolean worked; - if (!Desktop.isDesktopSupported()) { - worked = false; - } else { - boolean worked2; - try { - if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) { - return false; - } - - Desktop.getDesktop().open(file); - worked2 = true; - } catch (Throwable var4) { - worked2 = false; - } - - worked = worked2; - } - - return worked; - } - - private static boolean runCommand(String command) { - try { - Process process = Runtime.getRuntime().exec(command); - return process != null && process.isAlive(); - } catch (IOException var5) { - return false; - } - } - } - - private static File getMcDir() { - return new File(System.getProperty("user.dir")); - } -} diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index 7cfd1aa..3c6f8af 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -32,6 +32,7 @@ import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL14; +import org.polyfrost.crashpatch.CrashPatch; import org.polyfrost.crashpatch.config.CrashPatchConfig; import org.polyfrost.crashpatch.crashes.StateManager; import org.polyfrost.crashpatch.gui.CrashGuiRewrite; @@ -162,6 +163,10 @@ public void run(CallbackInfo ci) { while (running) { if (!hasCrashed || crashReporter == null) { try { + if (CrashPatch.INSTANCE.getTest()) { + throw new RuntimeException("Test crash"); + } + runGameLoop(); } catch (ReportedException e) { crashpatch$clientCrashCount++; diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index f4b9eec..efe545d 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -8,13 +8,13 @@ import org.polyfrost.oneconfig.api.commands.v1.factories.annotated.Command import org.polyfrost.crashpatch.config.CrashPatchConfig import org.polyfrost.crashpatch.crashes.CrashHelper import org.polyfrost.crashpatch.crashes.DeobfuscatingRewritePolicy -import org.polyfrost.crashpatch.hooks.ModsCheckerPlugin import net.minecraft.util.ChatComponentText import net.minecraftforge.fml.common.Mod import net.minecraftforge.fml.common.event.FMLInitializationEvent import net.minecraftforge.fml.common.event.FMLPreInitializationEvent import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger +import org.polyfrost.utils.v1.dsl.openUI import java.io.File @@ -24,7 +24,9 @@ object CrashPatch { const val NAME = "@MOD_NAME@" const val VERSION = "@MOD_VERSION@" val isSkyclient by lazy(LazyThreadSafetyMode.PUBLICATION) { File(mcDir, "OneConfig/CrashPatch/SKYCLIENT").exists() || File( - mcDir, "W-OVERFLOW/CrashPatch/SKYCLIENT").exists() || ModsCheckerPlugin.modsMap.keys.any { it == "skyclientcosmetics" || it == "scc" || it == "skyclientaddons" || it == "skyblockclientupdater" || it == "skyclientupdater" || it == "skyclientcore" } } + mcDir, "W-OVERFLOW/CrashPatch/SKYCLIENT").exists() } + + var test = false @Mod.EventHandler fun onPreInit(e: FMLPreInitializationEvent) { @@ -42,14 +44,14 @@ object CrashPatch { CommandManager.registerCommand(CrashPatchCommand()) CrashPatchConfig // uncomment to test init screen crashes - throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") +// throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") } @Command("crashpatch") class CrashPatchCommand { @Command fun main() { - //CrashPatchConfig.openGui() + CrashPatchConfig.openUI() } @Command @@ -61,6 +63,11 @@ object CrashPatch { UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Failed to reload the JSON file!")) } } + + @Command + fun crash() { + test = true + } } } val logger: Logger = LogManager.getLogger(CrashPatch) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt b/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt index 13b3428..6df26d1 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt @@ -76,11 +76,11 @@ object CrashPatchConfig : Config("crashpatch.json", "/assets/crashpatch/crashpat title = "Polyfrost support", text = "Discord" ) - var supportDiscord = Runnable { browse(URI.create("https://polyfrost.cc/discord/")) } + fun supportDiscord() { browse(URI.create("https://polyfrost.cc/discord/")) } @Button( title = "Crash game", text = "Crash" ) - var crashGame = Runnable { throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") } + fun crashGame() { throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") } } \ No newline at end of file diff --git a/src/main/resources/mixin.crashpatch.json b/src/main/resources/mixins.crashpatch.json similarity index 100% rename from src/main/resources/mixin.crashpatch.json rename to src/main/resources/mixins.crashpatch.json diff --git a/src/main/resources/polyfrost/stage0.properties b/src/main/resources/polyfrost/stage0.properties new file mode 100644 index 0000000..ba1d57b --- /dev/null +++ b/src/main/resources/polyfrost/stage0.properties @@ -0,0 +1,3 @@ +oneconfig-stage1-class=org.polyfrost.oneconfig.loader.stage1.Stage1Loader +oneconfig-stage1-version=1.1.0-alpha.31 +oneconfig-maven-uri=https://repo.polyfrost.org/mirror/ From b3eb7769698950f0f76b55656f77de3f2c364cc4 Mon Sep 17 00:00:00 2001 From: Deftu Date: Tue, 15 Oct 2024 16:54:59 +0200 Subject: [PATCH 08/37] Use correct tweaker in build file --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 24989b2..692a3cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,7 @@ toolkitLoomHelper { // Adds the tweak class if we are building legacy version of forge as per the documentation (https://docs.polyfrost.org) if (mcData.isLegacyForge) { - useTweaker("org.polyfrost.oneconfig.internal.legacy.OneConfigTweaker", GameSide.CLIENT) + useTweaker("org.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker", GameSide.CLIENT) useForgeMixin(modData.id) // Configures the mixins if we are building for forge, useful for when we are dealing with cross-platform projects. } } From 637cb6e33df813ce243370c5f708ff1246a0d3bb Mon Sep 17 00:00:00 2001 From: Deftu Date: Wed, 16 Oct 2024 16:12:26 +0200 Subject: [PATCH 09/37] Bump stage1 version --- src/main/resources/polyfrost/stage0.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/polyfrost/stage0.properties b/src/main/resources/polyfrost/stage0.properties index ba1d57b..2996839 100644 --- a/src/main/resources/polyfrost/stage0.properties +++ b/src/main/resources/polyfrost/stage0.properties @@ -1,3 +1,3 @@ oneconfig-stage1-class=org.polyfrost.oneconfig.loader.stage1.Stage1Loader -oneconfig-stage1-version=1.1.0-alpha.31 +oneconfig-stage1-version=1.1.0-alpha.32 oneconfig-maven-uri=https://repo.polyfrost.org/mirror/ From 365cf97770ea7066ca0914cc05e63098b434fa20 Mon Sep 17 00:00:00 2001 From: Deftu Date: Sat, 16 Nov 2024 17:14:11 +0200 Subject: [PATCH 10/37] Continue porting UI and make some needed fixes --- build.gradle.kts | 2 +- settings.gradle.kts | 2 +- .../crashpatch/mixin/MixinMinecraft.java | 5 +-- .../org/polyfrost/crashpatch/CrashPatch.kt | 4 +-- .../crashes/DeobfuscatingRewritePolicy.kt | 6 ++-- .../crashpatch/gui/CrashGuiRewrite.kt | 33 ++++++++++++++----- .../resources/polyfrost/stage0.properties | 3 -- 7 files changed, 35 insertions(+), 20 deletions(-) delete mode 100644 src/main/resources/polyfrost/stage0.properties diff --git a/build.gradle.kts b/build.gradle.kts index 692a3cf..713909d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ plugins { toolkitLoomHelper { // Adds OneConfig to our project - useOneConfig(mcData.version, mcData.loader, "commands", "config-impl", "events", "internal", "ui") + useOneConfig(mcData, "commands", "config", "config-impl", "events", "internal", "ui") // Removes the server configs from IntelliJ IDEA, leaving only client runs. // If you're developing a server-side mod, you can remove this line. diff --git a/settings.gradle.kts b/settings.gradle.kts index 70cb3f4..37b8a30 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,7 +22,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.11.2") + id("dev.deftu.gradle.multiversion-root") version("2.12.0") } } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index 3c6f8af..1b3fa44 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -163,8 +163,9 @@ public void run(CallbackInfo ci) { while (running) { if (!hasCrashed || crashReporter == null) { try { - if (CrashPatch.INSTANCE.getTest()) { - throw new RuntimeException("Test crash"); + if (CrashPatch.INSTANCE.getRequestedCrash()) { + CrashPatch.INSTANCE.setRequestedCrash(false); + throw new RuntimeException("Crash requested by CrashPatch"); } runGameLoop(); diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index efe545d..ce380b2 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -26,7 +26,7 @@ object CrashPatch { val isSkyclient by lazy(LazyThreadSafetyMode.PUBLICATION) { File(mcDir, "OneConfig/CrashPatch/SKYCLIENT").exists() || File( mcDir, "W-OVERFLOW/CrashPatch/SKYCLIENT").exists() } - var test = false + var requestedCrash = false @Mod.EventHandler fun onPreInit(e: FMLPreInitializationEvent) { @@ -66,7 +66,7 @@ object CrashPatch { @Command fun crash() { - test = true + requestedCrash = true } } } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt index d178d49..cb615f3 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt @@ -12,9 +12,9 @@ import org.apache.logging.log4j.core.config.LoggerConfig class DeobfuscatingRewritePolicy : RewritePolicy { override fun rewrite(source: LogEvent): LogEvent { - if (CrashPatchConfig.deobfuscateCrashLog) { - source.thrown?.let { StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(it) } - } +// if (CrashPatchConfig.deobfuscateCrashLog) { +// source.thrown?.let { StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(it) } +// } return source } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt index bdb536f..db7352d 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt @@ -9,12 +9,16 @@ import org.polyfrost.oneconfig.api.ui.v1.OCPolyUIBuilder import org.polyfrost.oneconfig.api.ui.v1.UIManager import org.polyfrost.polyui.PolyUI import org.polyfrost.polyui.color.rgba +import org.polyfrost.polyui.component.Component import org.polyfrost.polyui.component.extensions.named +import org.polyfrost.polyui.component.extensions.onClick import org.polyfrost.polyui.component.extensions.padded import org.polyfrost.polyui.component.extensions.setPalette +import org.polyfrost.polyui.component.impl.Block import org.polyfrost.polyui.component.impl.Group import org.polyfrost.polyui.component.impl.Image import org.polyfrost.polyui.component.impl.Text +import org.polyfrost.polyui.data.PolyImage import org.polyfrost.polyui.unit.Align import org.polyfrost.polyui.unit.Vec2 import org.polyfrost.polyui.utils.image @@ -48,6 +52,8 @@ class CrashGuiRewrite @JvmOverloads constructor( } var shouldCrash = false + private var selectedSolution: CrashScan.Solution? = null + private val subtitle by lazy { when (type) { GuiType.INIT -> listOf(SUBTITLE_INIT_1 + (if (crashScan != null) SUBTITLE_INIT_2 else "") + SUBTITLE_INIT_3, "") @@ -58,20 +64,22 @@ class CrashGuiRewrite @JvmOverloads constructor( fun create(): GuiScreen { val builder = OCPolyUIBuilder.create() - .atResolution(1920f, 1080f) .blurs() + .atResolution(1920f, 1080f) .backgroundColor(rgba(21, 21, 21)) .size(650f, 600f) + .renderer(UIManager.INSTANCE.renderer) val onClose: Consumer = Consumer { _: PolyUI -> leaveWorldCrash = false } - // builder.onClose(onClose) +// builder.onClose(onClose) + println("sigh.") val polyUI = builder.make( Group( - Image("/assets/crashpatch/WarningTriangle.svg".image(Vec2(20F, 20F))).named("WarningTriangle").padded( + Image("/assets/crashpatch/WarningTriangle.svg".image().also { PolyImage.setImageSize(it, Vec2(20F, 20F)) }).named("WarningTriangle").padded( 0F, 34F, 0F, @@ -82,11 +90,20 @@ class CrashGuiRewrite @JvmOverloads constructor( Text(subtitle[1], fontSize = 14F, font = PolyUI.defaultFonts.regular).setPalette { text.secondary }.padded(0f, 0F, 0f, 0f), Text(if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, fontSize = 16F, font = PolyUI.defaultFonts.regular).setPalette { text.primary }.padded(0f, 24F, 0f, 0f), Text(susThing, fontSize = 18F, font = PolyUI.defaultFonts.semiBold).setPalette { brand.fg }.padded(0f, 8f, 0f, 0f), - //Group( - // Block( -// - // ) - //).padded(0f, 40f, 0f, 0f), + Block( + children = mutableListOf().apply { + crashScan?.solutions?.forEach { scanSolution -> + add(Group( + Text(scanSolution.name, fontSize = 12F, font = PolyUI.defaultFonts.medium).setPalette { text.primary }.onClick { + selectedSolution = scanSolution + } + )) + } + }.toTypedArray(), + alignment = Align(mode = Align.Mode.Vertical), + size = Vec2(550f, 158f), + color = GRAY_700 + ).padded(0f, 40f, 0f, 0f), size = Vec2(650f, 600f), alignment = Align(mode = Align.Mode.Vertical) ), diff --git a/src/main/resources/polyfrost/stage0.properties b/src/main/resources/polyfrost/stage0.properties deleted file mode 100644 index 2996839..0000000 --- a/src/main/resources/polyfrost/stage0.properties +++ /dev/null @@ -1,3 +0,0 @@ -oneconfig-stage1-class=org.polyfrost.oneconfig.loader.stage1.Stage1Loader -oneconfig-stage1-version=1.1.0-alpha.32 -oneconfig-maven-uri=https://repo.polyfrost.org/mirror/ From ae7c9592045516ec7ecbd05d2d22e479b9223a33 Mon Sep 17 00:00:00 2001 From: Deftu Date: Mon, 18 Nov 2024 13:57:24 +0200 Subject: [PATCH 11/37] Port most of the UI to PolyUI --- .../crashpatch/gui/CrashGuiRewrite.kt | 127 +++++++++++++----- 1 file changed, 92 insertions(+), 35 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt index db7352d..1740268 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt @@ -1,5 +1,6 @@ package org.polyfrost.crashpatch.gui +import dev.deftu.clipboard.Clipboard import net.minecraft.client.gui.GuiScreen import net.minecraft.crash.CrashReport import org.polyfrost.crashpatch.crashes.CrashHelper @@ -8,20 +9,17 @@ import org.polyfrost.crashpatch.hooks.CrashReportHook import org.polyfrost.oneconfig.api.ui.v1.OCPolyUIBuilder import org.polyfrost.oneconfig.api.ui.v1.UIManager import org.polyfrost.polyui.PolyUI +import org.polyfrost.polyui.color.PolyColor import org.polyfrost.polyui.color.rgba import org.polyfrost.polyui.component.Component -import org.polyfrost.polyui.component.extensions.named -import org.polyfrost.polyui.component.extensions.onClick -import org.polyfrost.polyui.component.extensions.padded -import org.polyfrost.polyui.component.extensions.setPalette -import org.polyfrost.polyui.component.impl.Block -import org.polyfrost.polyui.component.impl.Group -import org.polyfrost.polyui.component.impl.Image -import org.polyfrost.polyui.component.impl.Text +import org.polyfrost.polyui.component.extensions.* +import org.polyfrost.polyui.component.impl.* import org.polyfrost.polyui.data.PolyImage import org.polyfrost.polyui.unit.Align import org.polyfrost.polyui.unit.Vec2 +import org.polyfrost.polyui.unit.Vec4 import org.polyfrost.polyui.utils.image +import org.polyfrost.polyui.utils.mapToArray import java.io.File import java.util.function.Consumer @@ -52,11 +50,13 @@ class CrashGuiRewrite @JvmOverloads constructor( } var shouldCrash = false - private var selectedSolution: CrashScan.Solution? = null - private val subtitle by lazy { when (type) { - GuiType.INIT -> listOf(SUBTITLE_INIT_1 + (if (crashScan != null) SUBTITLE_INIT_2 else "") + SUBTITLE_INIT_3, "") + GuiType.INIT -> listOf( + SUBTITLE_INIT_1 + (if (crashScan != null) SUBTITLE_INIT_2 else "") + SUBTITLE_INIT_3, + "" + ) + GuiType.NORMAL -> listOf(SUBTITLE_1, SUBTITLE_2) GuiType.DISCONNECT -> listOf(SUBTITLE_DISCONNECTED, SUBTITLE_DISCONNECTED_2) } @@ -74,40 +74,97 @@ class CrashGuiRewrite @JvmOverloads constructor( leaveWorldCrash = false } -// builder.onClose(onClose) + var selectedSolution: CrashScan.Solution? = null + var block: Block? = null - println("sigh.") val polyUI = builder.make( Group( - Image("/assets/crashpatch/WarningTriangle.svg".image().also { PolyImage.setImageSize(it, Vec2(20F, 20F)) }).named("WarningTriangle").padded( - 0F, - 34F, - 0F, - 0F - ), - Text(if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, fontSize = 24F, font = PolyUI.defaultFonts.medium).setPalette { text.primary }.padded(0f, 10F, 0f, 0f), - Text(subtitle[0], fontSize = 14F, font = PolyUI.defaultFonts.regular).setPalette { text.secondary }.padded(0f, 16f, 0f, 0f), - Text(subtitle[1], fontSize = 14F, font = PolyUI.defaultFonts.regular).setPalette { text.secondary }.padded(0f, 0F, 0f, 0f), - Text(if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, fontSize = 16F, font = PolyUI.defaultFonts.regular).setPalette { text.primary }.padded(0f, 24F, 0f, 0f), - Text(susThing, fontSize = 18F, font = PolyUI.defaultFonts.semiBold).setPalette { brand.fg }.padded(0f, 8f, 0f, 0f), + Image("/assets/crashpatch/WarningTriangle.svg".image(), size = Vec2(20F, 20F)).named("WarningTriangle") + .padded( + 0F, + 34F, + 0F, + 0F + ), + Text( + if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, + fontSize = 24F, + ).setFont { PolyUI.defaultFonts.medium }.padded(0f, 10F, 0f, 0f), + Text(subtitle[0], fontSize = 14F).setPalette { text.secondary } + .padded(0f, 16f, 0f, 0f), + Text(subtitle[1], fontSize = 14F).setPalette { text.secondary } + .padded(0f, 0F, 0f, 0f), + Text( + if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, + fontSize = 16F + ).padded(0f, 24F, 0f, 0f), + Text(susThing, fontSize = 18F).setFont { PolyUI.defaultFonts.semiBold }.setPalette { brand.fg } + .padded(0f, 8f, 0f, 0f), Block( - children = mutableListOf().apply { - crashScan?.solutions?.forEach { scanSolution -> - add(Group( - Text(scanSolution.name, fontSize = 12F, font = PolyUI.defaultFonts.medium).setPalette { text.primary }.onClick { - selectedSolution = scanSolution + Block( + // Tabs + Group( + children = crashScan?.solutions?.mapToArray { solution -> + val rightPad = when { + solution == crashScan?.solutions?.last() -> 0f + else -> 32f } - )) - } - }.toTypedArray(), - alignment = Align(mode = Align.Mode.Vertical), + + Text(solution.name) + .setFont { PolyUI.defaultFonts.medium } + .padded(0f, 0f, rightPad, 0f) + .onClick { + selectedSolution = solution + block?.set( + 1, + Text( + solution.solutions.joinToString("\n"), + fontSize = 12f, + visibleSize = Vec2(550f, 121f), + ).padded(16f, 8f) + ) + + true + } + } ?: arrayOf(), + ).padded(24f, 0f, 0f, 0f), + + // Buttons + Group( + Button(leftImage = "/assets/crashpatch/copy.svg".image()).onClick { + selectedSolution?.solutions?.joinToString("\n")?.let(Clipboard.getInstance()::setString) + selectedSolution != null + }.setPalette { component.bg }, + Button(leftImage = "/assets/crashpatch/upload.svg".image()).padded(8f, 0f, 0f, 0f), + ), + + alignment = Align( + pad = Vec2.ZERO, + main = Align.Main.SpaceBetween, + mode = Align.Mode.Horizontal + ), + size = Vec2(550f, 37f), + color = GRAY_600 + ).radii(8f, 8f, 0f, 0f), + + // Selected solution goes here... + Group().padded(16f, 8f), + + alignment = Align( + main = Align.Main.Start, + cross = Align.Cross.Start, + mode = Align.Mode.Vertical, + pad = Vec2.ZERO + ), size = Vec2(550f, 158f), color = GRAY_700 - ).padded(0f, 40f, 0f, 0f), + ).also { block = it }.padded(0f, 40f, 0f, 0f), + size = Vec2(650f, 600f), - alignment = Align(mode = Align.Mode.Vertical) + alignment = Align(pad = Vec2.ZERO, mode = Align.Mode.Vertical) ), ) + val screen = UIManager.INSTANCE.createPolyUIScreen(polyUI, 1920f, 1080f, false, true, onClose) polyUI.window = UIManager.INSTANCE.createWindow() From 3fe06dbffcf6b5c902609a89878e4f97e38eb778 Mon Sep 17 00:00:00 2001 From: Deftu Date: Thu, 21 Nov 2024 12:17:19 +0200 Subject: [PATCH 12/37] Basically finish porting GUI --- .../crashpatch/gui/CrashGuiRewrite.kt | 151 +++++++++++++++--- .../org/polyfrost/crashpatch/gui/constants.kt | 2 + 2 files changed, 129 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt index 1740268..7f2676f 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt @@ -3,24 +3,35 @@ package org.polyfrost.crashpatch.gui import dev.deftu.clipboard.Clipboard import net.minecraft.client.gui.GuiScreen import net.minecraft.crash.CrashReport +import org.polyfrost.crashpatch.CrashPatch import org.polyfrost.crashpatch.crashes.CrashHelper import org.polyfrost.crashpatch.crashes.CrashScan import org.polyfrost.crashpatch.hooks.CrashReportHook +import org.polyfrost.crashpatch.utils.InternetUtils +import org.polyfrost.oneconfig.api.ui.v1.Notifications import org.polyfrost.oneconfig.api.ui.v1.OCPolyUIBuilder import org.polyfrost.oneconfig.api.ui.v1.UIManager import org.polyfrost.polyui.PolyUI +import org.polyfrost.polyui.animate.Animations +import org.polyfrost.polyui.color.Colors import org.polyfrost.polyui.color.PolyColor import org.polyfrost.polyui.color.rgba import org.polyfrost.polyui.component.Component import org.polyfrost.polyui.component.extensions.* import org.polyfrost.polyui.component.impl.* import org.polyfrost.polyui.data.PolyImage +import org.polyfrost.polyui.operations.Move +import org.polyfrost.polyui.operations.Resize import org.polyfrost.polyui.unit.Align import org.polyfrost.polyui.unit.Vec2 import org.polyfrost.polyui.unit.Vec4 +import org.polyfrost.polyui.unit.seconds import org.polyfrost.polyui.utils.image import org.polyfrost.polyui.utils.mapToArray +import org.polyfrost.universal.UDesktop +import org.polyfrost.universal.UScreen import java.io.File +import java.net.URI import java.util.function.Consumer class CrashGuiRewrite @JvmOverloads constructor( @@ -74,7 +85,7 @@ class CrashGuiRewrite @JvmOverloads constructor( leaveWorldCrash = false } - var selectedSolution: CrashScan.Solution? = null + var selectedSolution: CrashScan.Solution? = crashScan?.solutions?.firstOrNull() var block: Block? = null val polyUI = builder.make( @@ -86,28 +97,46 @@ class CrashGuiRewrite @JvmOverloads constructor( 0F, 0F ), + Text( if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, - fontSize = 24F, - ).setFont { PolyUI.defaultFonts.medium }.padded(0f, 10F, 0f, 0f), - Text(subtitle[0], fontSize = 14F).setPalette { text.secondary } + fontSize = 24f, + ).setFont { PolyUI.defaultFonts.medium }.padded(0f, 10f, 0f, 0f), + Text(subtitle[0], fontSize = 14f).setPalette { text.secondary } .padded(0f, 16f, 0f, 0f), - Text(subtitle[1], fontSize = 14F).setPalette { text.secondary } + Text(subtitle[1], fontSize = 14f).setPalette { text.secondary } .padded(0f, 0F, 0f, 0f), Text( if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, - fontSize = 16F - ).padded(0f, 24F, 0f, 0f), - Text(susThing, fontSize = 18F).setFont { PolyUI.defaultFonts.semiBold }.setPalette { brand.fg } + fontSize = 16f + ).padded(0f, 24f, 0f, 0f), + Text(susThing, fontSize = 18f).setFont { PolyUI.defaultFonts.semiBold }.setPalette { brand.fg } .padded(0f, 8f, 0f, 0f), + Block( Block( + // Selector + Block( + color = BLUE_600, + size = Vec2(0f, 2f) + ).ignoreLayout().radius(2f).afterParentInit(Int.MAX_VALUE) { + selectedSolution?.let { solution -> + this.y = parent.y + parent.height - 2f + + val index = crashScan?.solutions?.indexOf(solution) ?: 0 + val component = parent[1][index] + + this.width = component.width + this.x = component.x + } + }, + // Tabs Group( - children = crashScan?.solutions?.mapToArray { solution -> + children = (crashScan?.solutions?.mapToArray { solution -> val rightPad = when { solution == crashScan?.solutions?.last() -> 0f - else -> 32f + else -> 24f } Text(solution.name) @@ -115,27 +144,57 @@ class CrashGuiRewrite @JvmOverloads constructor( .padded(0f, 0f, rightPad, 0f) .onClick { selectedSolution = solution - block?.set( - 1, - Text( - solution.solutions.joinToString("\n"), - fontSize = 12f, - visibleSize = Vec2(550f, 121f), - ).padded(16f, 8f) - ) + + parent.parent[0].let { selector -> + Resize( + drawable = selector, + width = width, + add = false, + animation = Animations.EaseInQuad.create(0.15.seconds) + ).add() + + Move( + drawable = selector, + x = x, + add = false, + animation = Animations.EaseInQuad.create(0.15.seconds) + ).add() + } + + block?.get(1)?.let { group -> + group[0] = createSolutionText(solution) + } true } - } ?: arrayOf(), - ).padded(24f, 0f, 0f, 0f), + } ?: arrayOf()), + ).padded(12f, 0f, 0f, 0f), // Buttons Group( Button(leftImage = "/assets/crashpatch/copy.svg".image()).onClick { - selectedSolution?.solutions?.joinToString("\n")?.let(Clipboard.getInstance()::setString) + selectedSolution?.solutions?.joinToString("\n")?.let(Clipboard.getInstance()::setString).also { copyState -> + if (copyState == true) { + Notifications.enqueue(Notifications.Type.Success, CrashPatch.NAME, "Copied to clipboard!") + } + } + selectedSolution != null - }.setPalette { component.bg }, - Button(leftImage = "/assets/crashpatch/upload.svg".image()).padded(8f, 0f, 0f, 0f), + }.setPalette { createCustomButtonPalette(GRAY_600) }, + Button(leftImage = "/assets/crashpatch/upload.svg".image()).onClick { + selectedSolution?.let { solution -> + val link = InternetUtils.upload(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) + Clipboard.getInstance().setString(link) + + if (UDesktop.browse(URI.create(link))) { + Notifications.enqueue(Notifications.Type.Success, CrashPatch.NAME, "Link copied to clipboard and opened in browser") + } else { + Notifications.enqueue(Notifications.Type.Warning, CrashPatch.NAME, "Couldn't open link in browser, copied to clipboard instead.") + } + } + + selectedSolution != null + }.setPalette { createCustomButtonPalette(GRAY_600) }, ), alignment = Align( @@ -148,7 +207,11 @@ class CrashGuiRewrite @JvmOverloads constructor( ).radii(8f, 8f, 0f, 0f), // Selected solution goes here... - Group().padded(16f, 8f), + Group( + children = (selectedSolution?.let { arrayOf(createSolutionText(it)) } ?: arrayOf()), + alignment = Align(pad = Vec2.ZERO, mode = Align.Mode.Vertical), + size = Vec2(518f, 105f), + ).padded(16f, 8f), alignment = Align( main = Align.Main.Start, @@ -160,6 +223,34 @@ class CrashGuiRewrite @JvmOverloads constructor( color = GRAY_700 ).also { block = it }.padded(0f, 40f, 0f, 0f), + Text(DISCORD_PROMPT, fontSize = 16f).padded(0f, 25f, 0f, 0f), + Group( + Image("/assets/crashpatch/discord.svg".image(), size = Vec2(28f, 28f)), + Text(POLYFROST_DISCORD, fontSize = 16f).setPalette { brand.fg }.padded(4f, 0f, 0f, 0f), + ).onClick { + UDesktop.browse(URI.create(POLYFROST_DISCORD)) + true + }, + + // Buttons + Group( + Button(text = RETURN_TO_GAME, padding = Vec2(14f, 14f)).onClick { + if (type == GuiType.INIT) { + shouldCrash = true + } else { + UScreen.displayScreen(null) + } + }.setPalette { brand.fg }, + Button( + text = OPEN_CRASH_LOG, + rightImage = "/assets/crashpatch/open-external.svg".image(), + padding = Vec2(14f, 14f) + ).onClick { + file?.let { UDesktop.open(it) } + true + }.setPalette { createCustomButtonPalette(rgba(21, 21, 21)) }, + ).padded(0f, 32f, 0f, 0f), + size = Vec2(650f, 600f), alignment = Align(pad = Vec2.ZERO, mode = Align.Mode.Vertical) ), @@ -171,6 +262,18 @@ class CrashGuiRewrite @JvmOverloads constructor( return screen as GuiScreen } + private fun createSolutionText(solution: CrashScan.Solution): Text { + return Text( + solution.solutions.joinToString("\n"), + fontSize = 12f, + visibleSize = Vec2(518f, 105f), + ).setFont { PolyUI.monospaceFont }.padded(16f, 8f) + } + + private fun createCustomButtonPalette(normal: PolyColor): Colors.Palette { + return Colors.Palette(normal, GRAY_700, GRAY_700, PolyColor.BLACK) + } + enum class GuiType { INIT, NORMAL, DISCONNECT } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt index afd1e98..06ed832 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt @@ -31,5 +31,7 @@ internal val CAUSE_TEXT_DISCONNECTED = "Reason for disconnect:" internal val RETURN_TO_GAME = "Return to game" internal val OPEN_CRASH_LOG = "Crash log" +internal val DISCORD_PROMPT = "If the solution above doesn't help, join" + internal val SKYCLIENT_DISCORD = "https://inv.wtf/skyclient" internal val POLYFROST_DISCORD = "https://polyfrost.cc/discord" \ No newline at end of file From 6875e8154cfddd265e2b340bcf69bd174f1de143 Mon Sep 17 00:00:00 2001 From: nextdayy <79922345+nextdayy@users.noreply.github.com> Date: Sun, 24 Nov 2024 12:13:22 +0000 Subject: [PATCH 13/37] remove redundant assets and use translation files instead --- .../crashpatch/gui/CrashGuiRewrite.kt | 73 +++++--------- .../org/polyfrost/crashpatch/gui/constants.kt | 35 +------ .../assets/crashpatch/en_default.lang | 22 +++++ .../assets/crashpatch/fonts/AUTHORS.txt | 10 -- .../fonts/JetBrainsMono-Regular.ttf | Bin 273900 -> 0 bytes .../resources/assets/crashpatch/fonts/OFL.txt | 93 ------------------ 6 files changed, 48 insertions(+), 185 deletions(-) create mode 100644 src/main/resources/assets/crashpatch/en_default.lang delete mode 100755 src/main/resources/assets/crashpatch/fonts/AUTHORS.txt delete mode 100644 src/main/resources/assets/crashpatch/fonts/JetBrainsMono-Regular.ttf delete mode 100644 src/main/resources/assets/crashpatch/fonts/OFL.txt diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt index 7f2676f..8de1184 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt @@ -16,15 +16,12 @@ import org.polyfrost.polyui.animate.Animations import org.polyfrost.polyui.color.Colors import org.polyfrost.polyui.color.PolyColor import org.polyfrost.polyui.color.rgba -import org.polyfrost.polyui.component.Component import org.polyfrost.polyui.component.extensions.* import org.polyfrost.polyui.component.impl.* -import org.polyfrost.polyui.data.PolyImage import org.polyfrost.polyui.operations.Move import org.polyfrost.polyui.operations.Resize import org.polyfrost.polyui.unit.Align import org.polyfrost.polyui.unit.Vec2 -import org.polyfrost.polyui.unit.Vec4 import org.polyfrost.polyui.unit.seconds import org.polyfrost.polyui.utils.image import org.polyfrost.polyui.utils.mapToArray @@ -61,27 +58,15 @@ class CrashGuiRewrite @JvmOverloads constructor( } var shouldCrash = false - private val subtitle by lazy { - when (type) { - GuiType.INIT -> listOf( - SUBTITLE_INIT_1 + (if (crashScan != null) SUBTITLE_INIT_2 else "") + SUBTITLE_INIT_3, - "" - ) - - GuiType.NORMAL -> listOf(SUBTITLE_1, SUBTITLE_2) - GuiType.DISCONNECT -> listOf(SUBTITLE_DISCONNECTED, SUBTITLE_DISCONNECTED_2) - } - } - fun create(): GuiScreen { val builder = OCPolyUIBuilder.create() .blurs() .atResolution(1920f, 1080f) .backgroundColor(rgba(21, 21, 21)) .size(650f, 600f) - .renderer(UIManager.INSTANCE.renderer) + .renderer(UIManager.INSTANCE.renderer).translatorDelegate("assets/crashpatch") - val onClose: Consumer = Consumer { _: PolyUI -> + val onClose = Consumer { _: PolyUI -> leaveWorldCrash = false } @@ -98,16 +83,13 @@ class CrashGuiRewrite @JvmOverloads constructor( 0F ), + Text(type.title, fontSize = 24f).setFont { PolyUI.defaultFonts.medium }.padded(0f, 10f, 0f, 0f), + Text("${type.title}.desc.1", fontSize = 14f).setPalette { text.secondary } + .padded(0f, if (type != GuiType.INIT || crashScan != null) 16 + 14f else 16f, 0f, 0f), + if (type != GuiType.INIT || crashScan != null) Text("${type.title}.desc.2", fontSize = 14f).setPalette { text.secondary } + else null, Text( - if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, - fontSize = 24f, - ).setFont { PolyUI.defaultFonts.medium }.padded(0f, 10f, 0f, 0f), - Text(subtitle[0], fontSize = 14f).setPalette { text.secondary } - .padded(0f, 16f, 0f, 0f), - Text(subtitle[1], fontSize = 14f).setPalette { text.secondary } - .padded(0f, 0F, 0f, 0f), - Text( - if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, + if (type == GuiType.DISCONNECT) "crashpatch.disconnect.cause" else "crashpatch.crash.cause", fontSize = 16f ).padded(0f, 24f, 0f, 0f), Text(susThing, fontSize = 18f).setFont { PolyUI.defaultFonts.semiBold }.setPalette { brand.fg } @@ -116,10 +98,8 @@ class CrashGuiRewrite @JvmOverloads constructor( Block( Block( // Selector - Block( - color = BLUE_600, - size = Vec2(0f, 2f) - ).ignoreLayout().radius(2f).afterParentInit(Int.MAX_VALUE) { + Block(size = Vec2(0f, 2f)).ignoreLayout().radius(2f).afterParentInit(Int.MAX_VALUE) { + palette = polyUI.colors.brand.fg selectedSolution?.let { solution -> this.y = parent.y + parent.height - 2f @@ -223,18 +203,18 @@ class CrashGuiRewrite @JvmOverloads constructor( color = GRAY_700 ).also { block = it }.padded(0f, 40f, 0f, 0f), - Text(DISCORD_PROMPT, fontSize = 16f).padded(0f, 25f, 0f, 0f), + Text("crashpatch.discord.prompt", fontSize = 16f).padded(0f, 25f, 0f, 0f), Group( Image("/assets/crashpatch/discord.svg".image(), size = Vec2(28f, 28f)), - Text(POLYFROST_DISCORD, fontSize = 16f).setPalette { brand.fg }.padded(4f, 0f, 0f, 0f), + Text("crashpatch.link.discord.polyfrost", fontSize = 16f).setPalette { brand.fg }.padded(4f, 0f, 0f, 0f), ).onClick { - UDesktop.browse(URI.create(POLYFROST_DISCORD)) + UDesktop.browse(URI.create("crashpatch.link.discord.polyfrost")) true }, // Buttons Group( - Button(text = RETURN_TO_GAME, padding = Vec2(14f, 14f)).onClick { + Button(text = "crashpatch.continue", padding = Vec2(14f, 14f)).onClick { if (type == GuiType.INIT) { shouldCrash = true } else { @@ -242,7 +222,7 @@ class CrashGuiRewrite @JvmOverloads constructor( } }.setPalette { brand.fg }, Button( - text = OPEN_CRASH_LOG, + text = "crashpatch.log", rightImage = "/assets/crashpatch/open-external.svg".image(), padding = Vec2(14f, 14f) ).onClick { @@ -256,25 +236,20 @@ class CrashGuiRewrite @JvmOverloads constructor( ), ) - val screen = - UIManager.INSTANCE.createPolyUIScreen(polyUI, 1920f, 1080f, false, true, onClose) + val screen = UIManager.INSTANCE.createPolyUIScreen(polyUI, 1920f, 1080f, false, true, onClose) polyUI.window = UIManager.INSTANCE.createWindow() return screen as GuiScreen } - private fun createSolutionText(solution: CrashScan.Solution): Text { - return Text( - solution.solutions.joinToString("\n"), - fontSize = 12f, - visibleSize = Vec2(518f, 105f), - ).setFont { PolyUI.monospaceFont }.padded(16f, 8f) - } + private fun createSolutionText(solution: CrashScan.Solution) = Text( + solution.solutions.joinToString("\n"), + fontSize = 12f, + visibleSize = Vec2(518f, 105f), + ).setFont { PolyUI.monospaceFont }.padded(16f, 8f) - private fun createCustomButtonPalette(normal: PolyColor): Colors.Palette { - return Colors.Palette(normal, GRAY_700, GRAY_700, PolyColor.BLACK) - } + private fun createCustomButtonPalette(normal: PolyColor) = Colors.Palette(normal, GRAY_700, GRAY_700, PolyColor.TRANSPARENT) - enum class GuiType { - INIT, NORMAL, DISCONNECT + enum class GuiType(val title: String) { + INIT("crashpatch.init"), NORMAL("crashpatch.crash"), DISCONNECT("crashpatch.disconnect") } } \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt index 06ed832..0a47561 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt @@ -2,36 +2,5 @@ package org.polyfrost.crashpatch.gui import org.polyfrost.polyui.color.rgba -internal val GRAY_800 = rgba(21, 22, 23, 1f) // general background -internal val GRAY_700 = rgba(34, 35, 38, 1f) // log background -internal val GRAY_600 = rgba(42, 44, 48, 1f) // log header - -internal val WHITE_90 = rgba(255, 255, 255, 229 / 255f) // text -internal val WHITE_80 = rgba(255, 255, 255, 204 / 255f) // subtext -internal val WHITE_60 = rgba(255, 255, 255, 153 / 255f) // logs - -internal val BLUE_400 = rgba(77, 135, 229) // yeah -internal val BLUE_600 = rgba(20, 82, 204, 1F) // brand.hover - -internal val HYPERLINK_BLUE = rgba(48, 129, 242) - -internal val TITLE = "Uh-oh. Your game crashed!" -internal val DISCONNECTED_TITLE = "Uh-oh. You were disconnected from the server!" -internal val SUBTITLE_1 = "But, CrashPatch just saved the day! Feel free to ignore this, and" -internal val SUBTITLE_2 = "continue playing your game despite the crash." -internal val SUBTITLE_DISCONNECTED = "The full reason is below, but" -internal val SUBTITLE_DISCONNECTED_2 = "you can ignore this and continue playing." -internal val SUBTITLE_INIT_1 = "To fix this, " -internal val SUBTITLE_INIT_2 = "follow the tips listed below and / or " -internal val SUBTITLE_INIT_3 = "join the Discord server and make a support ticket." - -internal val CAUSE_TEXT = "This could have been caused by:" -internal val CAUSE_TEXT_DISCONNECTED = "Reason for disconnect:" - -internal val RETURN_TO_GAME = "Return to game" -internal val OPEN_CRASH_LOG = "Crash log" - -internal val DISCORD_PROMPT = "If the solution above doesn't help, join" - -internal val SKYCLIENT_DISCORD = "https://inv.wtf/skyclient" -internal val POLYFROST_DISCORD = "https://polyfrost.cc/discord" \ No newline at end of file +internal val GRAY_700 = rgba(34, 35, 38) // log background +internal val GRAY_600 = rgba(42, 44, 48) // log header \ No newline at end of file diff --git a/src/main/resources/assets/crashpatch/en_default.lang b/src/main/resources/assets/crashpatch/en_default.lang new file mode 100644 index 0000000..a132c59 --- /dev/null +++ b/src/main/resources/assets/crashpatch/en_default.lang @@ -0,0 +1,22 @@ +crashpatch.crash=Uh-oh. Your game crashed! +crashpatch.init=Uh-oh. Your game crashed on startup! +crashpatch.disconnect=Uh-oh. You were disconnected from the server! + +crashpatch.crash.desc.1=But, CrashPatch just saved the day! Feel free to ignore this, and +crashpatch.crash.desc.2=continue playing your game despite the crash. + +crashpatch.disconnect.desc.1=The full reason is below, but +crashpatch.disconnect.desc.2=you can ignore this and continue playing. + +crashpatch.init.desc.1=To fix this, join the Discord server and make a support ticket. +crashpatch.init.desc.2=Additionally, try following at the tips listed below. + +crashpatch.crash.cause=This could have been caused by: +crashpatch.disconnect.cause=Reason for disconnect: + +crashpatch.continue=Return to game +crashpatch.log=Crash log + +crashpatch.discord.prompt=If the solution above doesn't help, join +crashpatch.link.discord.skyclient=https://inv.wtf/skyclient +crashpatch.link.discord.polyfrost=https://polyfrost.org/discord \ No newline at end of file diff --git a/src/main/resources/assets/crashpatch/fonts/AUTHORS.txt b/src/main/resources/assets/crashpatch/fonts/AUTHORS.txt deleted file mode 100755 index 8814941..0000000 --- a/src/main/resources/assets/crashpatch/fonts/AUTHORS.txt +++ /dev/null @@ -1,10 +0,0 @@ -# This is the official list of project authors for copyright purposes. -# This file is distinct from the CONTRIBUTORS.txt file. -# See the latter for an explanation. -# -# Names should be added to this file as: -# Name or Organization - -JetBrains <> -Philipp Nurullin -Konstantin Bulenkov diff --git a/src/main/resources/assets/crashpatch/fonts/JetBrainsMono-Regular.ttf b/src/main/resources/assets/crashpatch/fonts/JetBrainsMono-Regular.ttf deleted file mode 100644 index dff66cc50702c75abd025dcf49f62a4dcc2d72de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 273900 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 z

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 Date: Wed, 11 Dec 2024 04:32:02 +0200 Subject: [PATCH 14/37] Major cleanup, crash scans TBD --- root.gradle.kts | 4 +- settings.gradle.kts | 3 +- .../crashpatch/mixin/MixinGuiConnecting.java | 64 ++- .../crashpatch/mixin/MixinGuiDupesFound.java | 6 +- .../crashpatch/mixin/MixinMinecraft.java | 16 +- .../MixinTileEntityRendererDispatcher.java | 2 + .../org/polyfrost/crashpatch/CrashPatch.kt | 105 ++--- .../polyfrost/crashpatch/CrashPatchCommand.kt | 34 ++ .../{config => }/CrashPatchConfig.kt | 22 +- .../crashpatch/crashes/CrashHelper.kt | 2 - .../crashes/DeobfuscatingRewritePolicy.kt | 9 +- .../org/polyfrost/crashpatch/gui/CrashGui.kt | 420 ------------------ .../gui/{CrashGuiRewrite.kt => CrashUI.kt} | 6 +- .../org/polyfrost/crashpatch/gui/constants.kt | 2 +- .../crashpatch/utils/GuiDisconnectedHook.kt | 17 +- .../{InternetUtils.kt => UploadUtils.kt} | 26 +- 16 files changed, 172 insertions(+), 566 deletions(-) create mode 100644 src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt rename src/main/kotlin/org/polyfrost/crashpatch/{config => }/CrashPatchConfig.kt (72%) delete mode 100644 src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGui.kt rename src/main/kotlin/org/polyfrost/crashpatch/gui/{CrashGuiRewrite.kt => CrashUI.kt} (97%) rename src/main/kotlin/org/polyfrost/crashpatch/utils/{InternetUtils.kt => UploadUtils.kt} (76%) diff --git a/root.gradle.kts b/root.gradle.kts index 234330e..e377dbe 100644 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -3,5 +3,7 @@ plugins { } preprocess { - "1.8.9-forge"(10809, "srg") {} + "1.8.9-forge"(10809, "srg") { + "1.8.9-fabric"(10809, "yarn") {} + } } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 37b8a30..da4280a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,7 +33,8 @@ rootProject.buildFileName = "root.gradle.kts" // Adds all of our build target versions to the classpath if we need to add version-specific code. listOf( - "1.8.9-forge" + "1.8.9-forge", + "1.8.9-fabric" ).forEach { version -> include(":$version") project(":$version").apply { diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java index 420c592..19b1291 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -9,6 +9,7 @@ import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.multiplayer.GuiConnecting; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -23,11 +24,12 @@ public class MixinGuiConnecting extends GuiScreen { @Inject(method = "drawScreen", at = @At("TAIL")) private void drawWarningText(int mouseX, int mouseY, float partialTicks, CallbackInfo ci) { if (((MinecraftHook) Minecraft.getMinecraft()).hasRecoveredFromCrash()) { - drawSplitCenteredString(getText(), width / 2, 5, Color.WHITE.getRGB()); + crashpatch$drawSplitCenteredString(crashpatch$getText(), width / 2, 5, Color.WHITE.getRGB()); } } - private String getText() { + @Unique + private String crashpatch$getText() { return ChatColor.RED + "If Minecraft is stuck on this screen, please force close the game" + (CrashPatch.INSTANCE.isSkyclient() ? " and go to https://discord.gg/eh7tNFezct for support" : "") + "."; } @@ -36,7 +38,7 @@ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOEx super.mouseClicked(mouseX, mouseY, mouseButton); if (((MinecraftHook) Minecraft.getMinecraft()).hasRecoveredFromCrash()) { if (mouseButton == 0) { - String[] list = wrapFormattedStringToWidth(getText(), width).split("\n"); + String[] list = crashpatch$wrapFormattedStringToWidth(crashpatch$getText(), width).split("\n"); int width = -1; for (String text : list) { width = Math.max(width, fontRendererObj.getStringWidth(text)); @@ -49,44 +51,40 @@ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOEx } } - public void drawSplitCenteredString(String text, int x, int y, int color) { - for (String line : wrapFormattedStringToWidth(text, width).split("\n")) { + @Unique + public void crashpatch$drawSplitCenteredString(String text, int x, int y, int color) { + for (String line : crashpatch$wrapFormattedStringToWidth(text, width).split("\n")) { drawCenteredString(fontRendererObj, line, x, y, color); y += fontRendererObj.FONT_HEIGHT + 2; } } - public String wrapFormattedStringToWidth(String str, int wrapWidth) - { - int i = this.sizeStringToWidth(str, wrapWidth); + @Unique + public String crashpatch$wrapFormattedStringToWidth(String str, int wrapWidth) { + int i = this.crashpatch$sizeStringToWidth(str, wrapWidth); - if (str.length() <= i) - { + if (str.length() <= i) { return str; - } - else - { + } else { String s = str.substring(0, i); char c0 = str.charAt(i); boolean flag = c0 == 32 || c0 == 10; String s1 = FontRenderer.getFormatFromString(s) + str.substring(i + (flag ? 1 : 0)); - return s + "\n" + this.wrapFormattedStringToWidth(s1, wrapWidth); + return s + "\n" + this.crashpatch$wrapFormattedStringToWidth(s1, wrapWidth); } } - private int sizeStringToWidth(String str, int wrapWidth) - { + @Unique + private int crashpatch$sizeStringToWidth(String str, int wrapWidth) { int i = str.length(); int j = 0; int k = 0; int l = -1; - for (boolean flag = false; k < i; ++k) - { + for (boolean flag = false; k < i; ++k) { char c0 = str.charAt(k); - switch (c0) - { + switch (c0) { case '\n': --k; break; @@ -95,42 +93,34 @@ private int sizeStringToWidth(String str, int wrapWidth) default: j += fontRendererObj.getCharWidth(c0); - if (flag) - { + if (flag) { ++j; } break; case '\u00a7': - if (k < i - 1) - { + if (k < i - 1) { ++k; char c1 = str.charAt(k); - if (c1 != 108 && c1 != 76) - { - if (c1 == 114 || c1 == 82 || isFormatColor(c1)) - { + if (c1 != 108 && c1 != 76) { + if (c1 == 114 || c1 == 82 || crashpatch$isFormatColor(c1)) { flag = false; } - } - else - { + } else { flag = true; } } } - if (c0 == 10) - { + if (c0 == 10) { ++k; l = k; break; } - if (j > wrapWidth) - { + if (j > wrapWidth) { break; } } @@ -138,8 +128,8 @@ private int sizeStringToWidth(String str, int wrapWidth) return k != i && l != -1 && l < k ? l : k; } - private static boolean isFormatColor(char colorChar) - { + @Unique + private static boolean crashpatch$isFormatColor(char colorChar) { return colorChar >= 48 && colorChar <= 57 || colorChar >= 97 && colorChar <= 102 || colorChar >= 65 && colorChar <= 70; } } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java index 9241d96..68de486 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java @@ -9,7 +9,7 @@ import net.minecraftforge.fml.common.DuplicateModsFoundException; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.ModContainer; -import org.polyfrost.crashpatch.CrashPatchKt; +import org.polyfrost.crashpatch.CrashPatchOldKt; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -40,7 +40,7 @@ private void onInit(CallbackInfo ci) { protected void actionPerformed(GuiButton button) { switch (button.id) { case 0: - UDesktop.open(new File(CrashPatchKt.getMcDir(), "mods")); + UDesktop.open(new File(CrashPatchOldKt.getMcDir(), "mods")); break; case 1: FMLCommonHandler.instance().exitJava(0, false); @@ -62,7 +62,7 @@ private void onDrawScreen(int mouseX, int mouseY, float partialTicks, CallbackIn offset += 10; - drawSplitString(EnumChatFormatting.BOLD + "To fix this, go into your mods folder by clicking the button below or going to " + CrashPatchKt.getMcDir().getAbsolutePath() + " and deleting the duplicate mods.", width / 2, offset, width, Color.BLUE.getRGB()); + drawSplitString(EnumChatFormatting.BOLD + "To fix this, go into your mods folder by clicking the button below or going to " + CrashPatchOldKt.getMcDir().getAbsolutePath() + " and deleting the duplicate mods.", width / 2, offset, width, Color.BLUE.getRGB()); for (GuiButton guiButton : this.buttonList) { guiButton.drawButton(this.mc, mouseX, mouseY); diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index 1b3fa44..f768fdf 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -35,7 +35,7 @@ import org.polyfrost.crashpatch.CrashPatch; import org.polyfrost.crashpatch.config.CrashPatchConfig; import org.polyfrost.crashpatch.crashes.StateManager; -import org.polyfrost.crashpatch.gui.CrashGuiRewrite; +import org.polyfrost.crashpatch.gui.CrashUI; import org.polyfrost.crashpatch.hooks.MinecraftHook; import org.polyfrost.crashpatch.utils.GuiDisconnectedHook; import org.spongepowered.asm.mixin.Final; @@ -230,7 +230,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { // Display the crash screen // crashpatch$runGUILoop(new GuiCrashScreen(report)); - displayGuiScreen(new CrashGuiRewrite(report).create()); + displayGuiScreen(new CrashUI(report).create()); } catch (Throwable t) { // The crash screen has crashed. Report it normally instead. logger.error("An uncaught exception occured while displaying the crash screen, making normal report instead", t); @@ -263,7 +263,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { if (crashpatch$clientCrashCount >= CrashPatchConfig.INSTANCE.getLeaveLimit() || crashpatch$serverCrashCount >= CrashPatchConfig.INSTANCE.getLeaveLimit()) { logger.error("Crash limit reached, exiting world"); - CrashGuiRewrite.Companion.setLeaveWorldCrash(true); + CrashUI.Companion.setLeaveWorldCrash(true); if (getNetHandler() != null) { getNetHandler().getNetworkManager().closeChannel(new ChatComponentText("[CrashPatch] Client crashed")); } @@ -333,7 +333,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { GlStateManager.enableTexture2D(); } catch (Throwable ignored) { } - crashpatch$runGUILoop(new CrashGuiRewrite(report, CrashGuiRewrite.GuiType.INIT)); + crashpatch$runGUILoop(new CrashUI(report, CrashUI.GuiType.INIT)); } catch (Throwable t) { if (!crashpatch$letDie) { logger.error("An uncaught exception occured while displaying the init error screen, making normal report instead", t); @@ -346,8 +346,8 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { /** * @author Runemoro */ - private void crashpatch$runGUILoop(CrashGuiRewrite crashGui) throws Throwable { - GuiScreen screen = crashGui.create(); + private void crashpatch$runGUILoop(CrashUI crashUI) throws Throwable { + GuiScreen screen = crashUI.create(); displayGuiScreen(screen); while (running && currentScreen != null) { if (Display.isCreated() && Display.isCloseRequested()) { @@ -381,9 +381,9 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { int mouseY = height - Mouse.getY() * height / displayHeight - 1; Gui.drawRect(0, 0, width, height, Color.WHITE.getRGB()); // DO NOT REMOVE THIS! FOR SOME REASON NANOVG DOESN'T RENDER WITHOUT IT currentScreen.drawScreen(mouseX, mouseY, 0); - if (crashGui.getShouldCrash()) { + if (crashUI.getShouldCrash()) { crashpatch$letDie = true; - throw Objects.requireNonNull(crashGui.getThrowable()); + throw Objects.requireNonNull(crashUI.getThrowable()); } framebufferMc.unbindFramebuffer(); diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java index acc1c29..a2f91a0 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java @@ -1,5 +1,6 @@ package org.polyfrost.crashpatch.mixin; +//#if FORGE import org.polyfrost.crashpatch.crashes.StateManager; import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; import net.minecraft.tileentity.TileEntity; @@ -50,3 +51,4 @@ private void setDrawingBatchFalse(int pass, CallbackInfo ci) { drawingBatch = false; } } +//#endif diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index ce380b2..53a77c8 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -1,35 +1,47 @@ package org.polyfrost.crashpatch -import org.polyfrost.universal.ChatColor -import org.polyfrost.universal.UMinecraft -import org.polyfrost.oneconfig.utils.v1.Multithreading -import org.polyfrost.oneconfig.api.commands.v1.CommandManager -import org.polyfrost.oneconfig.api.commands.v1.factories.annotated.Command -import org.polyfrost.crashpatch.config.CrashPatchConfig -import org.polyfrost.crashpatch.crashes.CrashHelper -import org.polyfrost.crashpatch.crashes.DeobfuscatingRewritePolicy -import net.minecraft.util.ChatComponentText +//#if FORGE import net.minecraftforge.fml.common.Mod import net.minecraftforge.fml.common.event.FMLInitializationEvent import net.minecraftforge.fml.common.event.FMLPreInitializationEvent import org.apache.logging.log4j.LogManager -import org.apache.logging.log4j.Logger -import org.polyfrost.utils.v1.dsl.openUI +//#else +//$$ import net.fabricmc.api.ClientModInitializer +//#endif + import java.io.File +import org.polyfrost.crashpatch.config.CrashPatchConfig +import org.polyfrost.crashpatch.crashes.CrashHelper +import org.polyfrost.crashpatch.crashes.DeobfuscatingRewritePolicy +import org.polyfrost.oneconfig.api.commands.v1.CommandManager +import org.polyfrost.oneconfig.utils.v1.Multithreading +//#if FORGE +@Mod(modid = CrashPatch.ID, version = CrashPatch.VERSION, name = CrashPatch.NAME, modLanguageAdapter = "org.polyfrost.oneconfig.utils.v1.forge.KotlinLanguageAdapter") +//#endif +object CrashPatch + //#if FABRIC + //$$ : ClientModInitializer + //#endif +{ -@Mod(modid = CrashPatch.MODID, version = CrashPatch.VERSION, name = CrashPatch.NAME, modLanguageAdapter = "org.polyfrost.oneconfig.utils.v1.forge.KotlinLanguageAdapter") -object CrashPatch { - const val MODID = "@MOD_ID@" + const val ID = "@MOD_ID@" const val NAME = "@MOD_NAME@" const val VERSION = "@MOD_VERSION@" - val isSkyclient by lazy(LazyThreadSafetyMode.PUBLICATION) { File(mcDir, "OneConfig/CrashPatch/SKYCLIENT").exists() || File( - mcDir, "W-OVERFLOW/CrashPatch/SKYCLIENT").exists() } + + private val logger = LogManager.getLogger() + + val mcDir by lazy(LazyThreadSafetyMode.PUBLICATION) { + File(System.getProperty("user.dir")) + } + + val isSkyclient by lazy(LazyThreadSafetyMode.PUBLICATION) { + File(mcDir, "OneConfig/CrashPatch/SKYCLIENT").exists() || File(mcDir, "W-OVERFLOW/CrashPatch/SKYCLIENT").exists() + } var requestedCrash = false - @Mod.EventHandler - fun onPreInit(e: FMLPreInitializationEvent) { + fun preInitialize() { DeobfuscatingRewritePolicy.install() Multithreading.submit { logger.info("Is SkyClient: $isSkyclient") @@ -39,47 +51,26 @@ object CrashPatch { } } - @Mod.EventHandler - fun onInit(e: FMLInitializationEvent) { - CommandManager.registerCommand(CrashPatchCommand()) - CrashPatchConfig - // uncomment to test init screen crashes -// throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") + fun initialize() { + CommandManager.registerCommand(CrashPatchCommand) + CrashPatchConfig // Initialize the config } - @Command("crashpatch") - class CrashPatchCommand { - @Command - fun main() { - CrashPatchConfig.openUI() - } - - @Command - fun reload() { - if (CrashHelper.loadJson()) { - CrashHelper.simpleCache.clear() - UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Successfully reloaded JSON file!")) - } else { - UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Failed to reload the JSON file!")) - } - } - - @Command - fun crash() { - requestedCrash = true - } + //#if FORGE + @Mod.EventHandler + fun onPreInit(e: FMLPreInitializationEvent) { + preInitialize() } -} -val logger: Logger = LogManager.getLogger(CrashPatch) -val gameDir: File by lazy(LazyThreadSafetyMode.PUBLICATION) { - val file = mcDir - try { - if (file.parentFile?.name?.let { it == ".minecraft" || it == "minecraft" } == true) file.parentFile else file - } catch (e: Exception) { - e.printStackTrace() - file + + @Mod.EventHandler + fun onInit(e: FMLInitializationEvent) { + initialize() } + //#else + //$$ override fun onInitializeClient() { + //$$ preInitialize() + //$$ initialize() + //$$ } + //#endif + } -val mcDir = File(System.getProperty("user.dir")) -val mc - get() = UMinecraft.getMinecraft() // todo replace with oneconfig in alpha20 \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt new file mode 100644 index 0000000..b8a68e6 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt @@ -0,0 +1,34 @@ +package org.polyfrost.crashpatch + +import net.minecraft.util.ChatComponentText +import org.polyfrost.crashpatch.config.CrashPatchConfig +import org.polyfrost.crashpatch.crashes.CrashHelper +import org.polyfrost.oneconfig.api.commands.v1.factories.annotated.Command +import org.polyfrost.universal.ChatColor +import org.polyfrost.universal.UMinecraft +import org.polyfrost.utils.v1.dsl.openUI + +@Command(CrashPatch.ID) +object CrashPatchCommand { + + @Command + fun main() { + CrashPatchConfig.openUI() + } + + @Command + fun reload() { + if (CrashHelper.loadJson()) { + CrashHelper.simpleCache.clear() + UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Successfully reloaded JSON file!")) + } else { + UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Failed to reload the JSON file!")) + } + } + + @Command + fun crash() { + CrashPatch.requestedCrash = true + } + +} diff --git a/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt similarity index 72% rename from src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt rename to src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt index 6df26d1..59490ce 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt @@ -1,11 +1,10 @@ -package org.polyfrost.crashpatch.config +package org.polyfrost.crashpatch import org.polyfrost.oneconfig.api.config.v1.Config import org.polyfrost.oneconfig.api.config.v1.annotations.* -import org.polyfrost.universal.UDesktop.browse +import org.polyfrost.universal.UDesktop import java.net.URI - object CrashPatchConfig : Config("crashpatch.json", "/assets/crashpatch/crashpatch_dark.svg", "CrashPatch", Category.QOL) { // Toggles @@ -67,20 +66,21 @@ object CrashPatchConfig : Config("crashpatch.json", "/assets/crashpatch/crashpat @Dropdown( title = "Log uploader", description = "The method used to upload the crash log.", - options = ["hst.sh", "mclo.gs (Aternos)"], subcategory = "Logs" ) - var crashLogUploadMethod = 0 + var crashLogUploadMethod = UploadMethod.HASTEBIN @Button( title = "Polyfrost support", text = "Discord" ) - fun supportDiscord() { browse(URI.create("https://polyfrost.cc/discord/")) } + fun supportDiscord() { + UDesktop.browse(URI.create("https://polyfrost.org/discord")) + } + + enum class UploadMethod(val text: String) { + HASTEBIN("hst.sh"), + MCLOGS("mclo.gs (Aternos)") + } - @Button( - title = "Crash game", - text = "Crash" - ) - fun crashGame() { throw Throwable("java.lang.NoClassDefFoundError: xyz/matthewtgm/requisite/keybinds/KeyBind at lumien.custommainmenu.configuration.ConfigurationLoader.load(ConfigurationLoader.java:142) club.sk1er.bossbarcustomizer.BossbarMod.loadConfig cc.woverflow.hytils.handlers.chat.modules.modifiers.DefaultChatRestyler Failed to login: null The Hypixel Alpha server is currently closed! net.kdt.pojavlaunch macromodmodules") } } \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt index b5924aa..82da599 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt @@ -1,8 +1,6 @@ package org.polyfrost.crashpatch.crashes import org.polyfrost.universal.wrappers.message.UTextComponent -import org.polyfrost.crashpatch.gameDir -import org.polyfrost.crashpatch.mcDir import com.google.gson.JsonObject import org.polyfrost.oneconfig.utils.v1.JsonUtils import java.io.File diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt index cb615f3..280556c 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt @@ -1,6 +1,5 @@ package org.polyfrost.crashpatch.crashes -import org.polyfrost.crashpatch.config.CrashPatchConfig import org.polyfrost.crashpatch.hooks.StacktraceDeobfuscator import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.LogEvent @@ -9,12 +8,14 @@ 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.LoggerConfig +import org.polyfrost.crashpatch.CrashPatchConfig class DeobfuscatingRewritePolicy : RewritePolicy { override fun rewrite(source: LogEvent): LogEvent { -// if (CrashPatchConfig.deobfuscateCrashLog) { -// source.thrown?.let { StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(it) } -// } + if (CrashPatchConfig.deobfuscateCrashLog) { + source.thrown?.let { StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(it) } + } + return source } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGui.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGui.kt deleted file mode 100644 index 5894de7..0000000 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGui.kt +++ /dev/null @@ -1,420 +0,0 @@ -package org.polyfrost.crashpatch.gui - -//class CrashGui @JvmOverloads constructor( -// private val scanText: String, -// private val file: File?, -// private val susThing: String, -// private val type: GuiType = GuiType.NORMAL, -// val throwable: Throwable? = null -//) : UScreen(false) { -// @JvmOverloads -// constructor(report: CrashReport, type: GuiType = GuiType.NORMAL) : this( -// report.completeReport, -// report.file, -// (report as CrashReportHook).suspectedCrashPatchMods, -// type, -// report.crashCause -// ) -// -// private val crashPatchLogo = Icon("/assets/crashpatch/crashpatch_dark.svg") -// -// private val crashScan: CrashScan? by lazy { -// return@lazy CrashHelper.scanReport(scanText, type == GuiType.DISCONNECT) -// .let { return@let if (it != null && it.solutions.isNotEmpty()) it else null } -// } -// var shouldCrash = false -// -// private val subtitle by lazy { -// when (type) { -// GuiType.INIT -> listOf(SUBTITLE_INIT_1 + (if (crashScan != null) SUBTITLE_INIT_2 else "") + SUBTITLE_INIT_3) -// GuiType.NORMAL -> listOf(SUBTITLE_1, SUBTITLE_2) -// GuiType.DISCONNECT -> listOf(SUBTITLE_DISCONNECTED, SUBTITLE_DISCONNECTED_2) -// } -// } -// -// private val buttonFontSizeField = BasicButton::class.java.getDeclaredField("fontSize").apply { isAccessible = true } -// -// private val returnToGameButton by lazy { -// val button = BasicButton( -// NanoVGHelper.INSTANCE.getTextWidth(vg, RETURN_TO_GAME, 14f, Fonts.MEDIUM).toInt() + 40, -// 40, -// RETURN_TO_GAME, -// 2, -// ColorPalette.PRIMARY -// ) -// button.setClickAction { -// if (type == GuiType.INIT) { -// shouldCrash = true -// } else { -// restorePreviousScreen() -// } -// } -// buttonFontSizeField.setFloat(button, 14f) -// button -// } -// -// private val openCrashLogButton by lazy { -// val button = BasicButton( -// NanoVGHelper.INSTANCE.getTextWidth(vg, OPEN_CRASH_LOG, 14f, Fonts.MEDIUM).toInt() + 40 + 5 + 35, -// 40, -// OPEN_CRASH_LOG, -// null, -// SVG("/assets/crashpatch/open-external.svg"), -// 2, -// ColorPalette.TERTIARY -// ) -// button.setClickAction { -// file?.let { -// UDesktop.open(it) -// } -// } -// buttonFontSizeField.setFloat(button, 14f) -// button -// } -// -// private val uploadLogButton by lazy { -// val button = BasicButton( -// 30, -// 30, -// SVG("/assets/crashpatch/upload.svg"), -// 2, -// ColorPalette(GRAY_600, GRAY_700, GRAY_800) -// ) -// button.setClickAction { -// selectedSolution?.let { solution -> -// val link = -// InternetUtils.upload(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) -// setClipboardString(link) -// if (UDesktop.browse(URI.create(link))) { -// Notifications.INSTANCE.send( -// "CrashPatch", "Link copied to clipboard and opened in browser", crashPatchLogo -// ) -// } else { -// Notifications.INSTANCE.send( -// "CrashPatch", "Couldn't open link in browser, copied to clipboard instead.", crashPatchLogo -// ) -// } -// } -// } -// button -// } -// -// private val copyLogButton by lazy { -// val button = BasicButton( -// 30, -// 30, -// SVG("/assets/crashpatch/copy.svg"), -// 2, -// ColorPalette(GRAY_600, GRAY_700, GRAY_800) -// ) -// button.setClickAction { -// selectedSolution?.let { solution -> -// setClipboardString(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) -// Notifications.INSTANCE.send("CrashPatch", "Copied to clipboard", crashPatchLogo) -// } -// } -// button -// } -// -// private var scrollAnimation: Animation? = null -// private val colorAnimation = ColorAnimation( -// ColorPalette( -// ColorUtils.getColor(55, 59, 69, 76), -// ColorUtils.getColor(55, 59, 69, 153), -// ColorUtils.getColor(55, 59, 69, 255) -// ), 200 -// ) -// private var hyperlinkAnimation: EaseInOutQuad? = EaseInOutQuad(200, 0f, 1f, false) -// private var scrollTarget = 0f -// private var scrollTime = 0L -// private var mouseWasDown = false -// private var dragging = false -// private var yStart = 0f -// private var scroll = 0f -// -// private var lastHeight = 0f -// -// private var selectedSolution: CrashScan.Solution? = null -// -// private var vg = -1L -// -// private val inputHandler = InputHandler() -// -// override fun onDrawScreen(matrixStack: UMatrixStack, mouseX: Int, mouseY: Int, partialTicks: Float) { -// super.onDrawScreen(matrixStack, mouseX, mouseY, partialTicks) -// if (mc.theWorld == null && leaveWorldCrash) { -// drawDefaultBackground() -// } -// NanoVGHelper.INSTANCE.setupAndDraw { draw(it, inputHandler) } -// } -// -// override fun onScreenClose() { -// super.onScreenClose() -// leaveWorldCrash = false -// } -// -// fun draw(vg: Long, inputHandler: InputHandler) { -// this.vg = vg -// nanoVG(vg) { -// FontHelper.INSTANCE.loadFont(vg, JETBRAINS_MONO) -// val scale = OneConfigGui.getScaleFactor() -// val x = ((windowWidth - 650 * scale) / 2f / scale).toInt() -// val y = ((windowHeight - 600 * scale) / 2f / scale).toInt() -// scale(scale, scale) -// inputHandler.scale(scale.toDouble(), scale.toDouble()) -// drawRoundedRect(x, y, 650, 600, 20, GRAY_800) -// drawSVG("/assets/crashpatch/WarningTriangle.svg", x + 305 + 10, y + 24 + 10, 20, 20, javaClass) -// drawText( -// if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, -// (windowWidth / 2f / scale) - (getTextWidth( -// if (type == GuiType.DISCONNECT) DISCONNECTED_TITLE else TITLE, -// 24, -// Fonts.MEDIUM -// ) / 2f), -// y + 56 + 22, -// WHITE_90, -// 24, -// Fonts.MEDIUM -// ) -// subtitle.forEachIndexed { index, s -> -// drawText( -// s, -// (windowWidth / 2f / scale) - (getTextWidth(s, 14, Fonts.REGULAR) / 2f), -// y + 56 + 87 + ((index - 1) * (14 * 1.75)), -// WHITE_80, -// 14, -// Fonts.REGULAR -// ) -// } -// -// drawText( -// if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, -// (windowWidth / 2f / scale) - (getTextWidth( -// if (type == GuiType.DISCONNECT) CAUSE_TEXT_DISCONNECTED else CAUSE_TEXT, -// 16, -// Fonts.REGULAR -// ) / 2f), -// y + 56 + 87 + 10 + (subtitle.size * (14 * 1.75)), -// WHITE_80, -// 16, -// Fonts.REGULAR -// ) -// drawText( -// susThing, (windowWidth / 2f / scale) - (getTextWidth( -// susThing, 18, Fonts.SEMIBOLD -// ) / 2f), y + 56 + 87 + 10 + (subtitle.size * (14 * 1.75)) + 30, BLUE_400, 18, Fonts.SEMIBOLD -// ) -// -// drawRoundedRect(x + 50, y + 273, 550, 158, 12, GRAY_700) -// ScissorHelper.INSTANCE.scissor(vg, x + 50f, y + 273f, 550f, 37f).let { -// drawRoundedRect(x + 50, y + 273, 550, 158, 12, GRAY_600) -// ScissorHelper.INSTANCE.resetScissor(vg, it) -// } -// -// crashScan?.solutions?.let { solutions -> -// var i = 0 -// var lastTextWidth = 0f -// solutions.forEach { solution -> -// i++ -// if (i == 1 && selectedSolution == null) { -// selectedSolution = solution -// } -// drawText( -// solution.name, -// x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth else 0f), -// y + 273 + 18.5, -// WHITE_90, -// 12, -// Fonts.MEDIUM -// ) -// val textWidth = getTextWidth(solution.name, 12, Fonts.MEDIUM) -// if (selectedSolution != solution) { -// if (inputHandler.isAreaClicked( -// x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth else 0f), -// y + 273f, -// textWidth, -// 37f, -// ) -// ) { -// selectedSolution = solution -// scroll = 0f -// scrollTarget = 0f -// scrollTime = 0 -// scrollAnimation = null -// } -// } -// val hovered = inputHandler.isAreaHovered( -// x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth else 0f), -// y + 273f, -// textWidth, -// 37f, -// ) -// lastTextWidth += textWidth -// if (selectedSolution == solution || hovered) { -// drawRoundedRect( -// x + 50 + 24 + (32 * (i - 1)) + (if (i > 1) lastTextWidth - textWidth else 0f), -// y + 273 + 35, -// textWidth, -// 2, -// 1, -// if (selectedSolution == solution) BLUE_600 else ColorUtils.setAlpha(BLUE_600, 128) -// ) -// } -// if (selectedSolution == solution) { -// ScissorHelper.INSTANCE.scissor(vg, x + 50f + 20f, y + 310f, 550f - 20 - 20, 121f) -// .let { scissor -> -// val scrollBarLength = 89 / lastHeight * 89 -// if (lastHeight > 89) { -// scroll = scrollAnimation?.get() ?: scrollTarget -// val dWheel = Platform.getMousePlatform().dWheel.toFloat() * 0.3f -// if (dWheel != 0f) { -// scrollTarget += dWheel -// if (scrollTarget > 0f) scrollTarget = -// 0f else if (scrollTarget < -lastHeight + 89) scrollTarget = -lastHeight + 89 -// scrollAnimation = EaseOutQuad(150, scroll, scrollTarget, false) -// scrollTime = System.currentTimeMillis() -// } else if (scrollAnimation != null && scrollAnimation!!.isFinished) scrollAnimation = -// null -// if (dragging && inputHandler.isClicked(true)) { -// dragging = false -// } -// } -// var height = 0F -// translate(0f, scroll) -// solution.solutions.forEach { -// height += 12 * 1.25f -// drawWrappedString( -// it, -// x + 50 + 20, -// y + 310f + height, -// 550 - 20 - 20, -// WHITE_60, -// 12, -// 1.25f, -// JETBRAINS_MONO -// ) -// height += NanoVGHelper.INSTANCE.getWrappedStringHeight( -// vg, it, 550F, 12F, 1.25f, JETBRAINS_MONO -// ) -// } -// height += 12 * 1.25f -// translate(0f, -scroll) -// lastHeight = height -// ScissorHelper.INSTANCE.resetScissor(vg, scissor) -// if (lastHeight > 89) { -// val scrollBarY = scroll / lastHeight * 81 -// val isMouseDown = Platform.getMousePlatform().isButtonDown(0) -// val scrollHover = inputHandler.isAreaHovered( -// (x + 50f + 20f + 530f - 14f), -// (y + 310f + 16 - scrollBarY), -// 12f, -// scrollBarLength.toInt().toFloat() -// ) -// val scrollTimePeriod = System.currentTimeMillis() - scrollTime < 1000 -// if (scrollHover && isMouseDown && !mouseWasDown) { -// yStart = inputHandler.mouseY() -// dragging = true -// } -// mouseWasDown = isMouseDown -// if (dragging) { -// scrollTarget = -(inputHandler.mouseY() - yStart) * lastHeight / 89f -// if (scrollTarget > 0f) scrollTarget = -// 0f else if (scrollTarget < -lastHeight + 89) scrollTarget = -// -lastHeight + 89f -// scrollAnimation = EaseOutQuad(150, scroll, scrollTarget, false) -// } -// NanoVGHelper.INSTANCE.drawRoundedRect( -// vg, -// (x + 50f + 20f + 530f - 14f), -// (y + 310f + 16 - scrollBarY), -// 4f, -// scrollBarLength, -// colorAnimation.getColor(scrollHover || scrollTimePeriod, dragging), -// 4f -// ) -// } -// } -// } -// } -// } -// uploadLogButton.draw(vg, x + 600 - 8 - 11 - 30f, y + 273f + 3.5f, inputHandler) -// copyLogButton.draw(vg, x + 600 - 8 - 11 - 15 - 8 - 11 - 30f, y + 273f + 3.5f, inputHandler) -// -// drawText( -// "If the solution above doesn't help, join", (windowWidth / 2f / scale) - (getTextWidth( -// "If the solution above doesn't help, join", 16, Fonts.REGULAR -// ) / 2f), y + 273 + 158 + 24 + 20, WHITE_80, 16, Fonts.REGULAR -// ) -// val discordMessageWidth = 20 + 15 + getTextWidth("https://inv.wtf/skyclient", 16, Fonts.REGULAR) -// drawSVG( -// "/assets/crashpatch/discord.svg", -// (windowWidth / 2f / scale) - (discordMessageWidth / 2), -// y + 273 + 158 + 24 + 20 + 15, -// 20, -// 20 -// ) -// drawURL( -// if (CrashPatch.isSkyclient) SKYCLIENT_DISCORD else POLYFROST_DISCORD, -// (windowWidth / 2f / scale) - (discordMessageWidth / 2) + 20 + 15, -// y + 273 + 158 + 24 + 20 + 15 + 11, -// 16, -// Fonts.REGULAR, -// inputHandler -// ) -// -// val buttonsWidth = -// returnToGameButton.width + if (type != GuiType.DISCONNECT) (getTextWidth( -// OPEN_CRASH_LOG, -// 14, -// Fonts.MEDIUM -// ) + 16 + 20) + 10 else 0f -// returnToGameButton.update((windowWidth / 2f / scale) - (buttonsWidth / 2), y + 600 - 16 - 36f, inputHandler) -// returnToGameButton.draw( -// vg, (windowWidth / 2f / scale) - (buttonsWidth / 2), y + 600 - 16 - 36f, inputHandler -// ) -// if (type != GuiType.DISCONNECT) { -// openCrashLogButton.update( -// (windowWidth / 2f / scale) - (buttonsWidth / 2) + returnToGameButton.width + 10, -// y + 600 - 16 - 36f, -// inputHandler -// ) -// openCrashLogButton.draw( -// vg, -// (windowWidth / 2f / scale) - (buttonsWidth / 2) + returnToGameButton.width + 10, -// y + 600 - 16 - 36f, -// inputHandler -// ) -// } -// } -// } -// -// private fun VG.drawURL(url: String, x: Number, y: Number, size: Int, font: Font, inputHandler: InputHandler) { -// drawText(url, x, y, HYPERLINK_BLUE, size, font) -// val length = getTextWidth(url, size, font) -// val hovered = inputHandler.isAreaHovered( -// (x.toFloat() - 2), (y.toFloat() - size.toFloat()), (length + 4), (size.toFloat() * 2 + 2) -// ) -// if (hovered || (hyperlinkAnimation != null && (hyperlinkAnimation?.isReversed == false || !hyperlinkAnimation!!.isFinished))) { -// if (!hovered && hyperlinkAnimation?.isReversed == false) { -// hyperlinkAnimation = EaseInOutQuad(100, 0f, hyperlinkAnimation!!.get(), true) -// } -// if (hyperlinkAnimation == null) { -// hyperlinkAnimation = EaseInOutQuad(100, 0f, 1f, false) -// } -// drawRect(x, y.toFloat() + size.toFloat() / 2, length, 2, ColorUtils.setAlpha(BLUE_600, (hyperlinkAnimation!!.get() * 255).toInt())) -// if (hovered && inputHandler.isClicked) { -// NetworkUtils.browseLink(url) -// } -// } else { -// hyperlinkAnimation = null -// } -// } -// -// enum class GuiType { -// INIT, NORMAL, DISCONNECT -// } -// -// companion object { -// internal var leaveWorldCrash = false -// } -//} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt similarity index 97% rename from src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt rename to src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt index 8de1184..cffc468 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGuiRewrite.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt @@ -7,7 +7,7 @@ import org.polyfrost.crashpatch.CrashPatch import org.polyfrost.crashpatch.crashes.CrashHelper import org.polyfrost.crashpatch.crashes.CrashScan import org.polyfrost.crashpatch.hooks.CrashReportHook -import org.polyfrost.crashpatch.utils.InternetUtils +import org.polyfrost.crashpatch.utils.UploadUtils import org.polyfrost.oneconfig.api.ui.v1.Notifications import org.polyfrost.oneconfig.api.ui.v1.OCPolyUIBuilder import org.polyfrost.oneconfig.api.ui.v1.UIManager @@ -31,7 +31,7 @@ import java.io.File import java.net.URI import java.util.function.Consumer -class CrashGuiRewrite @JvmOverloads constructor( +class CrashUI @JvmOverloads constructor( private val scanText: String, private val file: File?, private val susThing: String, @@ -163,7 +163,7 @@ class CrashGuiRewrite @JvmOverloads constructor( }.setPalette { createCustomButtonPalette(GRAY_600) }, Button(leftImage = "/assets/crashpatch/upload.svg".image()).onClick { selectedSolution?.let { solution -> - val link = InternetUtils.upload(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) + val link = UploadUtils.upload(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) Clipboard.getInstance().setString(link) if (UDesktop.browse(URI.create(link))) { diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt index 0a47561..541f792 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt @@ -3,4 +3,4 @@ package org.polyfrost.crashpatch.gui import org.polyfrost.polyui.color.rgba internal val GRAY_700 = rgba(34, 35, 38) // log background -internal val GRAY_600 = rgba(42, 44, 48) // log header \ No newline at end of file +internal val GRAY_600 = rgba(42, 44, 48) // log header diff --git a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt index 456d408..ade2a77 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt @@ -4,20 +4,23 @@ import org.polyfrost.crashpatch.crashes.CrashHelper.scanReport import org.polyfrost.crashpatch.mixin.AccessorGuiDisconnected import net.minecraft.client.gui.GuiDisconnected import net.minecraft.client.gui.GuiScreen +import org.polyfrost.crashpatch.CrashPatchConfig import org.spongepowered.asm.mixin.injection.callback.CallbackInfo -import org.polyfrost.crashpatch.config.CrashPatchConfig -import org.polyfrost.crashpatch.gui.CrashGuiRewrite -import org.polyfrost.crashpatch.mc +import org.polyfrost.crashpatch.gui.CrashUI +import org.polyfrost.utils.v1.dsl.mc object GuiDisconnectedHook { - fun onGUIDisplay(i: GuiScreen?, ci: CallbackInfo) { - if (i is GuiDisconnected && CrashPatchConfig.disconnectCrashPatch) { - val gui = i as AccessorGuiDisconnected + + @JvmStatic + fun onGUIDisplay(screen: GuiScreen?, ci: CallbackInfo) { + if (screen is GuiDisconnected && CrashPatchConfig.disconnectCrashPatch) { + val gui = screen as AccessorGuiDisconnected val scan = scanReport(gui.message.formattedText, true) if (scan != null && scan.solutions.size > 1) { ci.cancel() - mc.displayGuiScreen(CrashGuiRewrite(gui.message.formattedText, null, gui.reason, CrashGuiRewrite.GuiType.DISCONNECT).create()) + mc.displayGuiScreen(CrashUI(gui.message.formattedText, null, gui.reason, CrashUI.GuiType.DISCONNECT).create()) } } } + } \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/utils/InternetUtils.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/UploadUtils.kt similarity index 76% rename from src/main/kotlin/org/polyfrost/crashpatch/utils/InternetUtils.kt rename to src/main/kotlin/org/polyfrost/crashpatch/utils/UploadUtils.kt index 7d5cf58..2668516 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/utils/InternetUtils.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/UploadUtils.kt @@ -1,26 +1,31 @@ package org.polyfrost.crashpatch.utils import org.polyfrost.crashpatch.CrashPatch -import org.polyfrost.crashpatch.config.CrashPatchConfig import gs.mclo.api.APIException import gs.mclo.api.Log import gs.mclo.api.MclogsClient +import org.polyfrost.crashpatch.CrashPatchConfig import java.io.BufferedReader import java.io.DataOutputStream import java.io.InputStreamReader -import java.net.URL +import java.net.URI import java.nio.charset.StandardCharsets import javax.net.ssl.HttpsURLConnection +object UploadUtils { + + private val mclogsClient by lazy { + MclogsClient("CrashPatch", CrashPatch.VERSION, "1.8.9") + } -object InternetUtils { private val sessionIdRegex = Regex("((Session ID is|--accessToken|Your new API key is) (\\S+))") + fun upload(text: String): String { - val log = Log(text.replace(sessionIdRegex, "[SENSITIVE INFORMATION]")) + val sanitizedText = text.replace(sessionIdRegex, "[SENSITIVE INFORMATION]") + val log = Log(sanitizedText) return when (CrashPatchConfig.crashLogUploadMethod) { - 0 -> uploadToHastebin(log.content) - 1 -> uploadToMclogs(log) - else -> uploadToHastebin(log.content) + CrashPatchConfig.UploadMethod.HASTEBIN -> uploadToHastebin(log.content) + CrashPatchConfig.UploadMethod.MCLOGS -> uploadToMclogs(log) } } @@ -29,7 +34,7 @@ object InternetUtils { val postDataLength = postData.size val requestURL = "https://hst.sh/documents" - val url = URL(requestURL) + val url = URI.create(requestURL).toURL() val conn: HttpsURLConnection = url.openConnection() as HttpsURLConnection conn.doOutput = true conn.instanceFollowRedirects = false @@ -55,13 +60,12 @@ object InternetUtils { } private fun uploadToMclogs(log: Log): String { - val mcLogs = MclogsClient("CrashPatch", CrashPatch.VERSION, "1.8.9") return try { - val response = mcLogs.uploadLog(log) - response.url + mclogsClient.uploadLog(log).url } catch (e: APIException) { e.printStackTrace() "Failed to upload crash log to mclo.gs" } } + } \ No newline at end of file From 39fec52855fe81e2c39c4a1628542e6e30626df3 Mon Sep 17 00:00:00 2001 From: Deftu Date: Wed, 11 Dec 2024 04:52:05 +0200 Subject: [PATCH 15/37] Further cleanup, Mixins TBD --- .../org/polyfrost/crashpatch/CrashPatch.kt | 16 ++++- .../polyfrost/crashpatch/CrashPatchCommand.kt | 7 +-- .../{CrashHelper.kt => CrashScanStorage.kt} | 59 ++++++++++++------- .../org/polyfrost/crashpatch/gui/CrashUI.kt | 4 +- .../crashpatch/utils/GuiDisconnectedHook.kt | 2 +- 5 files changed, 57 insertions(+), 31 deletions(-) rename src/main/kotlin/org/polyfrost/crashpatch/crashes/{CrashHelper.kt => CrashScanStorage.kt} (76%) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index 53a77c8..364089d 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -10,8 +10,7 @@ import org.apache.logging.log4j.LogManager //#endif import java.io.File -import org.polyfrost.crashpatch.config.CrashPatchConfig -import org.polyfrost.crashpatch.crashes.CrashHelper +import org.polyfrost.crashpatch.crashes.CrashScanStorage import org.polyfrost.crashpatch.crashes.DeobfuscatingRewritePolicy import org.polyfrost.oneconfig.api.commands.v1.CommandManager import org.polyfrost.oneconfig.utils.v1.Multithreading @@ -35,6 +34,17 @@ object CrashPatch File(System.getProperty("user.dir")) } + val gameDir by lazy(LazyThreadSafetyMode.PUBLICATION) { + try { + if (mcDir.parentFile?.name?.let { name -> + name == ".minecraft" || name == "minecraft" + } == true) mcDir.parentFile else mcDir + } catch (e: Exception) { + e.printStackTrace() + mcDir + } + } + val isSkyclient by lazy(LazyThreadSafetyMode.PUBLICATION) { File(mcDir, "OneConfig/CrashPatch/SKYCLIENT").exists() || File(mcDir, "W-OVERFLOW/CrashPatch/SKYCLIENT").exists() } @@ -45,7 +55,7 @@ object CrashPatch DeobfuscatingRewritePolicy.install() Multithreading.submit { logger.info("Is SkyClient: $isSkyclient") - if (!CrashHelper.loadJson()) { + if (!CrashScanStorage.downloadJson()) { logger.error("CrashHelper failed to preload crash data JSON!") } } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt index b8a68e6..ccd0f0a 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt @@ -1,8 +1,7 @@ package org.polyfrost.crashpatch import net.minecraft.util.ChatComponentText -import org.polyfrost.crashpatch.config.CrashPatchConfig -import org.polyfrost.crashpatch.crashes.CrashHelper +import org.polyfrost.crashpatch.crashes.CrashScanStorage import org.polyfrost.oneconfig.api.commands.v1.factories.annotated.Command import org.polyfrost.universal.ChatColor import org.polyfrost.universal.UMinecraft @@ -18,8 +17,8 @@ object CrashPatchCommand { @Command fun reload() { - if (CrashHelper.loadJson()) { - CrashHelper.simpleCache.clear() + if (CrashScanStorage.downloadJson()) { + CrashScanStorage.simpleCache.clear() UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Successfully reloaded JSON file!")) } else { UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Failed to reload the JSON file!")) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt similarity index 76% rename from src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt rename to src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt index 82da599..0d139e7 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt @@ -2,51 +2,63 @@ package org.polyfrost.crashpatch.crashes import org.polyfrost.universal.wrappers.message.UTextComponent import com.google.gson.JsonObject +import org.apache.logging.log4j.LogManager +import org.polyfrost.crashpatch.CrashPatch import org.polyfrost.oneconfig.utils.v1.JsonUtils import java.io.File import kotlin.collections.set -object CrashHelper { +object CrashScanStorage { - private var skyclientJson: JsonObject? = null - val simpleCache = hashMapOf() + private val logger = LogManager.getLogger() + + private val cacheFile by lazy(LazyThreadSafetyMode.PUBLICATION) { + File(CrashPatch.mcDir, "OneConfig/CrashPatch/cache.json") + } + + private val String.mappedPlaceholders: String + get() = this + .replace("%pathindicator%", "") + .replace("%gameroot%", CrashPatch.gameDir.absolutePath.removeSuffix(File.separator)) + .replace("%profileroot%", File(CrashPatch.mcDir, "OneConfig").parentFile.absolutePath.removeSuffix(File.separator)) + + private var skyclientData: JsonObject? = null @JvmStatic - fun loadJson(): Boolean { + fun downloadJson(): Boolean { return try { - skyclientJson = - JsonUtils.parseFromUrl("https://raw.githubusercontent.com/SkyblockClient/CrashData/main/crashes.json")?.asJsonObject ?: return false + skyclientData = JsonUtils.parseFromUrl("https://raw.githubusercontent.com/SkyblockClient/CrashData/main/crashes.json") + ?.asJsonObject ?: return false + cacheFile.writeText(skyclientData.toString()) true } catch (e: Exception) { - e.printStackTrace() - false + logger.error("Failed to download crash data JSON!", e) + cacheFile.takeIf { it.exists() }?.let { + logger.info("Attempting to load cached crash data JSON...") + skyclientData = JsonUtils.parseOrNull(it.readText())?.asJsonObject + + skyclientData != null + } ?: false } } @JvmStatic fun scanReport(report: String, serverCrash: Boolean = false): CrashScan? { return try { - if (simpleCache.containsKey(report)) { - return simpleCache[report] - } val responses = getResponses(report, serverCrash) CrashScan(responses.also { it[if (serverCrash) "Disconnect reason" else "Crash log"] = report.split("\\R".toRegex()) }.toSortedMap { o1, o2 -> if (o1 == "Crash log" || o1 == "Disconnect reason") { return@toSortedMap 1 } + if (o2 == "Crash log" || o2 == "Disconnect reason") { return@toSortedMap -1 } + return@toSortedMap o1.compareTo(o2) }.map { map -> - CrashScan.Solution("${map.key} (${map.value.size})", map.value.map { - it.replace("%pathindicator%", "").replace( - "%gameroot%", gameDir.absolutePath.removeSuffix( - File.separator - ) - ).replace("%profileroot%", File(mcDir, "OneConfig").parentFile.absolutePath.removeSuffix(File.separator)) - }, true) - }.toMutableList()).also { simpleCache[report] = it } + CrashScan.Solution("${map.key} (${map.value.size})", map.value.map { entry -> entry.mappedPlaceholders }, true) + }.toMutableList()) } catch (e: Throwable) { e.printStackTrace() null @@ -54,7 +66,7 @@ object CrashHelper { } private fun getResponses(report: String, serverCrash: Boolean): MutableMap> { - val issues = skyclientJson ?: return linkedMapOf() + val issues = skyclientData ?: return linkedMapOf() val responses = linkedMapOf>() val triggersToIgnore = arrayListOf() @@ -74,6 +86,7 @@ object CrashHelper { } else { triggersToIgnore.add(index) } + responses[type["name"].asString] = arrayListOf() } @@ -83,11 +96,13 @@ object CrashHelper { for (solution in fixes) { val solutionJson = solution.asJsonObject if (solutionJson.has("bot_only")) continue + val triggerNumber = if (solutionJson.has("fixtype")) solutionJson["fixtype"].asInt else issues["default_fix_type"].asInt if (triggersToIgnore.contains(triggerNumber)) { continue } + val causes = solutionJson["causes"].asJsonArray var trigger = false for (cause in causes) { @@ -96,6 +111,7 @@ object CrashHelper { if (causeJson.has("unformatted") && causeJson["unformatted"].asBoolean) { theReport = UTextComponent.stripFormatting(theReport) } + when (causeJson["method"].asString) { "contains" -> { if (theReport.contains(causeJson["value"].asString)) { @@ -134,11 +150,12 @@ object CrashHelper { } } } + if (trigger) { responses[responseCategories[triggerNumber]]?.add(solutionJson["fix"].asString) } - } + return responses.filterNot { it.value.isEmpty() }.toMutableMap() } } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt index cffc468..7030da0 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt @@ -4,7 +4,7 @@ import dev.deftu.clipboard.Clipboard import net.minecraft.client.gui.GuiScreen import net.minecraft.crash.CrashReport import org.polyfrost.crashpatch.CrashPatch -import org.polyfrost.crashpatch.crashes.CrashHelper +import org.polyfrost.crashpatch.crashes.CrashScanStorage import org.polyfrost.crashpatch.crashes.CrashScan import org.polyfrost.crashpatch.hooks.CrashReportHook import org.polyfrost.crashpatch.utils.UploadUtils @@ -53,7 +53,7 @@ class CrashUI @JvmOverloads constructor( } private val crashScan: CrashScan? by lazy { - return@lazy CrashHelper.scanReport(scanText, type == GuiType.DISCONNECT) + return@lazy CrashScanStorage.scanReport(scanText, type == GuiType.DISCONNECT) .let { return@let if (it != null && it.solutions.isNotEmpty()) it else null } } var shouldCrash = false diff --git a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt index ade2a77..c89e5e9 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt @@ -1,6 +1,6 @@ package org.polyfrost.crashpatch.utils -import org.polyfrost.crashpatch.crashes.CrashHelper.scanReport +import org.polyfrost.crashpatch.crashes.CrashScanStorage.scanReport import org.polyfrost.crashpatch.mixin.AccessorGuiDisconnected import net.minecraft.client.gui.GuiDisconnected import net.minecraft.client.gui.GuiScreen From e79bae92e9300abd6dab33c2e2378bf784784875 Mon Sep 17 00:00:00 2001 From: Deftu Date: Wed, 11 Dec 2024 06:35:14 +0200 Subject: [PATCH 16/37] Finish porting to LF and cleaning up --- build.gradle.kts | 2 +- .../crashpatch/mixin/MixinCrashReport.java | 5 +- .../crashpatch/mixin/MixinGuiConnecting.java | 11 +- .../crashpatch/mixin/MixinGuiDupesFound.java | 20 +- .../crashpatch/mixin/MixinMinecraft.java | 89 ++++---- .../MixinTileEntityRendererDispatcher.java | 16 +- .../crashpatch/mixin/MixinWorldRenderer.java | 4 +- .../org/polyfrost/crashpatch/CrashPatch.kt | 4 +- .../polyfrost/crashpatch/CrashPatchCommand.kt | 3 +- .../crashpatch/crashes/ModIdentifier.kt | 89 -------- .../crashpatch/identifier/ModIdentifier.kt | 192 ++++++++++++++++++ .../crashpatch/identifier/ModMetadata.kt | 6 + .../crashpatch/utils/GuiDisconnectedHook.kt | 2 +- 13 files changed, 283 insertions(+), 160 deletions(-) delete mode 100644 src/main/kotlin/org/polyfrost/crashpatch/crashes/ModIdentifier.kt create mode 100644 src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt create mode 100644 src/main/kotlin/org/polyfrost/crashpatch/identifier/ModMetadata.kt diff --git a/build.gradle.kts b/build.gradle.kts index 713909d..2f29b56 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ plugins { toolkitLoomHelper { // Adds OneConfig to our project - useOneConfig(mcData, "commands", "config", "config-impl", "events", "internal", "ui") + useOneConfig(mcData, "commands", "config", "config-impl", "events", "internal", "ui", "utils") // Removes the server configs from IntelliJ IDEA, leaving only client runs. // If you're developing a server-side mod, you can remove this line. diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java index 9335159..3b88a91 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java @@ -6,11 +6,12 @@ package org.polyfrost.crashpatch.mixin; -import org.polyfrost.crashpatch.crashes.ModIdentifier; +import org.polyfrost.crashpatch.identifier.ModIdentifier; import org.polyfrost.crashpatch.hooks.CrashReportHook; import org.polyfrost.crashpatch.hooks.StacktraceDeobfuscator; import net.minecraft.crash.CrashReport; import net.minecraftforge.fml.common.ModContainer; +import org.polyfrost.crashpatch.identifier.ModMetadata; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -32,7 +33,7 @@ public String getSuspectedCrashPatchMods() { @Inject(method = "populateEnvironment", at = @At("TAIL")) private void afterPopulateEnvironment(CallbackInfo ci) { - ModContainer susMod = ModIdentifier.INSTANCE.identifyFromStacktrace(cause); + ModMetadata susMod = ModIdentifier.INSTANCE.identifyFromStacktrace(cause); crashpatch$suspectedMod = (susMod == null ? "Unknown" : susMod.getName()); } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java index 19b1291..2102bca 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -41,10 +41,11 @@ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOEx String[] list = crashpatch$wrapFormattedStringToWidth(crashpatch$getText(), width).split("\n"); int width = -1; for (String text : list) { - width = Math.max(width, fontRendererObj.getStringWidth(text)); + width = Math.max(width, this.fontRendererObj.getStringWidth(text)); } + int left = (this.width / 2) - width / 2; - if ((width == -1 || (left < mouseX && left + width > mouseX)) && (mouseY > 5 && mouseY < 15 + ((list.length - 1) * (fontRendererObj.FONT_HEIGHT + 2)))) { + if ((width == -1 || (left < mouseX && left + width > mouseX)) && (mouseY > 5 && mouseY < 15 + ((list.length - 1) * (this.fontRendererObj.FONT_HEIGHT + 2)))) { UDesktop.browse(URI.create("https://discord.gg/eh7tNFezct")); } } @@ -54,8 +55,8 @@ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOEx @Unique public void crashpatch$drawSplitCenteredString(String text, int x, int y, int color) { for (String line : crashpatch$wrapFormattedStringToWidth(text, width).split("\n")) { - drawCenteredString(fontRendererObj, line, x, y, color); - y += fontRendererObj.FONT_HEIGHT + 2; + drawCenteredString(this.fontRendererObj, line, x, y, color); + y += this.fontRendererObj.FONT_HEIGHT + 2; } } @@ -91,7 +92,7 @@ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOEx case ' ': l = k; default: - j += fontRendererObj.getCharWidth(c0); + j += this.fontRendererObj.getCharWidth(c0); if (flag) { ++j; diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java index 68de486..0b09055 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java @@ -1,5 +1,6 @@ package org.polyfrost.crashpatch.mixin; +import org.polyfrost.crashpatch.CrashPatch; import org.polyfrost.universal.UDesktop; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; @@ -9,9 +10,9 @@ import net.minecraftforge.fml.common.DuplicateModsFoundException; import net.minecraftforge.fml.common.FMLCommonHandler; import net.minecraftforge.fml.common.ModContainer; -import org.polyfrost.crashpatch.CrashPatchOldKt; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @@ -40,7 +41,7 @@ private void onInit(CallbackInfo ci) { protected void actionPerformed(GuiButton button) { switch (button.id) { case 0: - UDesktop.open(new File(CrashPatchOldKt.getMcDir(), "mods")); + UDesktop.open(new File(CrashPatch.getMcDir(), "mods")); break; case 1: FMLCommonHandler.instance().exitJava(0, false); @@ -53,16 +54,16 @@ private void onDrawScreen(int mouseX, int mouseY, float partialTicks, CallbackIn ci.cancel(); drawDefaultBackground(); int offset = 10; - offset += drawSplitString("There are duplicate mods in your mod folder!", width / 2, offset, width, Color.RED.getRGB()); + offset += crashpatch$drawSplitString("There are duplicate mods in your mod folder!", width / 2, offset, width, Color.RED.getRGB()); for (Map.Entry modContainerFileEntry : dupes.dupes.entries()) { offset += 10; - offset += drawSplitString(String.format("%s : %s", modContainerFileEntry.getKey().getModId(), modContainerFileEntry.getValue().getName()), width / 2, offset, width, Color.YELLOW.getRGB()); + offset += crashpatch$drawSplitString(String.format("%s : %s", modContainerFileEntry.getKey().getModId(), modContainerFileEntry.getValue().getName()), width / 2, offset, width, Color.YELLOW.getRGB()); } offset += 10; - drawSplitString(EnumChatFormatting.BOLD + "To fix this, go into your mods folder by clicking the button below or going to " + CrashPatchOldKt.getMcDir().getAbsolutePath() + " and deleting the duplicate mods.", width / 2, offset, width, Color.BLUE.getRGB()); + crashpatch$drawSplitString(EnumChatFormatting.BOLD + "To fix this, go into your mods folder by clicking the button below or going to " + CrashPatch.getMcDir().getAbsolutePath() + " and deleting the duplicate mods.", width / 2, offset, width, Color.BLUE.getRGB()); for (GuiButton guiButton : this.buttonList) { guiButton.drawButton(this.mc, mouseX, mouseY); @@ -72,8 +73,9 @@ private void onDrawScreen(int mouseX, int mouseY, float partialTicks, CallbackIn } } - private static int drawSplitString(String str, int x, int y, int wrapWidth, int textColor) { - str = trimStringNewline(str); + @Unique + private static int crashpatch$drawSplitString(String str, int x, int y, int wrapWidth, int textColor) { + str = crashpatch$trimStringNewline(str); int y2 = y; for (String s : Minecraft.getMinecraft().fontRendererObj.listFormattedStringToWidth(str, wrapWidth)) { Minecraft.getMinecraft().fontRendererObj.drawStringWithShadow(s, (float) (x - Minecraft.getMinecraft().fontRendererObj.getStringWidth(s) / 2), (float) y2, textColor); @@ -82,10 +84,12 @@ private static int drawSplitString(String str, int x, int y, int wrapWidth, int return y2 - y; } - private static String trimStringNewline(String text) { + @Unique + private static String crashpatch$trimStringNewline(String text) { while (text != null && text.endsWith("\n")) { text = text.substring(0, text.length() - 1); } return text; } + } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index f768fdf..84427e4 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -33,7 +33,7 @@ import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL14; import org.polyfrost.crashpatch.CrashPatch; -import org.polyfrost.crashpatch.config.CrashPatchConfig; +import org.polyfrost.crashpatch.CrashPatchConfig; import org.polyfrost.crashpatch.crashes.StateManager; import org.polyfrost.crashpatch.gui.CrashUI; import org.polyfrost.crashpatch.hooks.MinecraftHook; @@ -52,6 +52,7 @@ import java.util.Queue; import java.util.concurrent.FutureTask; +@SuppressWarnings("AccessStaticViaInstance") @Mixin(value = Minecraft.class, priority = -9000) public abstract class MixinMinecraft implements MinecraftHook { @@ -161,7 +162,7 @@ public void run(CallbackInfo ci) { } try { while (running) { - if (!hasCrashed || crashReporter == null) { + if (!this.hasCrashed || this.crashReporter == null) { try { if (CrashPatch.INSTANCE.getRequestedCrash()) { CrashPatch.INSTANCE.setRequestedCrash(false); @@ -174,7 +175,7 @@ public void run(CallbackInfo ci) { addGraphicsAndWorldToCrashReport(e.getCrashReport()); crashpatch$addInfoToCrash(e.getCrashReport()); crashpatch$resetGameState(); - logger.fatal("Reported exception thrown!", e); + this.logger.fatal("Reported exception thrown!", e); crashpatch$displayCrashScreen(e.getCrashReport()); } catch (Throwable e) { crashpatch$clientCrashCount++; @@ -182,16 +183,16 @@ public void run(CallbackInfo ci) { addGraphicsAndWorldToCrashReport(report); crashpatch$addInfoToCrash(report); crashpatch$resetGameState(); - logger.fatal("Unreported exception thrown!", e); + this.logger.fatal("Unreported exception thrown!", e); crashpatch$displayCrashScreen(report); } } else { crashpatch$serverCrashCount++; - crashpatch$addInfoToCrash(crashReporter); + crashpatch$addInfoToCrash(this.crashReporter); freeMemory(); - crashpatch$displayCrashScreen(crashReporter); - hasCrashed = false; - crashReporter = null; + crashpatch$displayCrashScreen(this.crashReporter); + this.hasCrashed = false; + this.crashReporter = null; } } } catch (MinecraftError ignored) { @@ -213,7 +214,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { crashpatch$letDie = true; } if ((crashpatch$clientCrashCount >= CrashPatchConfig.INSTANCE.getCrashLimit() || crashpatch$serverCrashCount >= CrashPatchConfig.INSTANCE.getCrashLimit())) { - logger.error("Crash limit reached, exiting game"); + this.logger.error("Crash limit reached, exiting game"); crashpatch$letDie = true; } displayCrashReport(report); @@ -221,19 +222,19 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { try { // Reset hasCrashed, debugCrashKeyPressTime, and crashIntegratedServerNextTick - hasCrashed = false; - debugCrashKeyPressTime = -1; + this.hasCrashed = false; + this.debugCrashKeyPressTime = -1; // Vanilla does this when switching to main menu but not our custom crash screen // nor the out of memory screen (see https://bugs.mojang.com/browse/MC-128953) - gameSettings.showDebugInfo = false; + this.gameSettings.showDebugInfo = false; // Display the crash screen // crashpatch$runGUILoop(new GuiCrashScreen(report)); displayGuiScreen(new CrashUI(report).create()); } catch (Throwable t) { // The crash screen has crashed. Report it normally instead. - logger.error("An uncaught exception occured while displaying the crash screen, making normal report instead", t); + this.logger.error("An uncaught exception occured while displaying the crash screen, making normal report instead", t); displayCrashReport(report); System.exit(report.getFile() != null ? -1 : -2); } @@ -252,9 +253,9 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { // Free up memory such that this works properly in case of an OutOfMemoryError int originalMemoryReserveSize = -1; try { // In case another mod actually deletes the memoryReserve field - if (memoryReserve != null) { - originalMemoryReserveSize = memoryReserve.length; - memoryReserve = new byte[0]; + if (this.memoryReserve != null) { + originalMemoryReserveSize = this.memoryReserve.length; + this.memoryReserve = new byte[0]; } } catch (Throwable ignored) { } @@ -262,31 +263,31 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { StateManager.INSTANCE.resetStates(); if (crashpatch$clientCrashCount >= CrashPatchConfig.INSTANCE.getLeaveLimit() || crashpatch$serverCrashCount >= CrashPatchConfig.INSTANCE.getLeaveLimit()) { - logger.error("Crash limit reached, exiting world"); + this.logger.error("Crash limit reached, exiting world"); CrashUI.Companion.setLeaveWorldCrash(true); if (getNetHandler() != null) { getNetHandler().getNetworkManager().closeChannel(new ChatComponentText("[CrashPatch] Client crashed")); } loadWorld(null); - if (entityRenderer.isShaderActive()) { - entityRenderer.stopUseShader(); + if (this.entityRenderer.isShaderActive()) { + this.entityRenderer.stopUseShader(); } - scheduledTasks.clear(); // TODO: Figure out why this isn't necessary for vanilla disconnect + this.scheduledTasks.clear(); // TODO: Figure out why this isn't necessary for vanilla disconnect } crashpatch$resetState(); if (originalMemoryReserveSize != -1) { try { - memoryReserve = new byte[originalMemoryReserveSize]; + this.memoryReserve = new byte[originalMemoryReserveSize]; } catch (Throwable ignored) { } } System.gc(); } catch (Throwable t) { - logger.error("Failed to reset state after a crash", t); + this.logger.error("Failed to reset state after a crash", t); try { StateManager.INSTANCE.resetStates(); crashpatch$resetState(); @@ -303,19 +304,19 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { } displayCrashReport(report); try { - mcResourceManager = new SimpleReloadableResourceManager(metadataSerializer_); - renderEngine = new TextureManager(mcResourceManager); - mcResourceManager.registerReloadListener(renderEngine); + this.mcResourceManager = new SimpleReloadableResourceManager(this.metadataSerializer_); + this.renderEngine = new TextureManager(this.mcResourceManager); + this.mcResourceManager.registerReloadListener(this.renderEngine); - mcLanguageManager = new LanguageManager(metadataSerializer_, gameSettings.language); - mcResourceManager.registerReloadListener(mcLanguageManager); + this.mcLanguageManager = new LanguageManager(this.metadataSerializer_, this.gameSettings.language); + this.mcResourceManager.registerReloadListener(this.mcLanguageManager); refreshResources(); // TODO: Why is this necessary? - fontRendererObj = new FontRenderer(gameSettings, new ResourceLocation("textures/font/ascii.png"), renderEngine, false); - mcResourceManager.registerReloadListener(fontRendererObj); + this.fontRendererObj = new FontRenderer(this.gameSettings, new ResourceLocation("textures/font/ascii.png"), this.renderEngine, false); + this.mcResourceManager.registerReloadListener(this.fontRendererObj); - mcSoundHandler = new SoundHandler(mcResourceManager, gameSettings); - mcResourceManager.registerReloadListener(mcSoundHandler); + this.mcSoundHandler = new SoundHandler(this.mcResourceManager, this.gameSettings); + this.mcResourceManager.registerReloadListener(this.mcSoundHandler); //try { // this is necessary for some GUI stuff. if it works, cool, if not, it's not a big deal // //EventManager.INSTANCE.register(Notifications.INSTANCE); @@ -325,7 +326,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { //} //todo do we need a polyui equivalent - running = true; + this.running = true; try { //noinspection deprecation SplashProgress.pause();// Disable the forge splash progress screen @@ -336,7 +337,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { crashpatch$runGUILoop(new CrashUI(report, CrashUI.GuiType.INIT)); } catch (Throwable t) { if (!crashpatch$letDie) { - logger.error("An uncaught exception occured while displaying the init error screen, making normal report instead", t); + this.logger.error("An uncaught exception occured while displaying the init error screen, making normal report instead", t); crashpatch$letDie = true; } displayCrashReport(report); @@ -349,21 +350,21 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { private void crashpatch$runGUILoop(CrashUI crashUI) throws Throwable { GuiScreen screen = crashUI.create(); displayGuiScreen(screen); - while (running && currentScreen != null) { + while (this.running && this.currentScreen != null) { if (Display.isCreated() && Display.isCloseRequested()) { System.exit(0); } //EventManager.INSTANCE.post(new RenderEvent.Start()); todo - leftClickCounter = 10000; - currentScreen.handleInput(); - currentScreen.updateScreen(); + this.leftClickCounter = 10000; + this.currentScreen.handleInput(); + this.currentScreen.updateScreen(); GlStateManager.pushMatrix(); GlStateManager.clear(16640); - framebufferMc.bindFramebuffer(true); + this.framebufferMc.bindFramebuffer(true); GlStateManager.enableTexture2D(); - GlStateManager.viewport(0, 0, displayWidth, displayHeight); + GlStateManager.viewport(0, 0, this.displayWidth, this.displayHeight); ScaledResolution scaledResolution = new ScaledResolution(((Minecraft) (Object) this)); GlStateManager.clear(256); @@ -377,20 +378,20 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { int width = scaledResolution.getScaledWidth(); int height = scaledResolution.getScaledHeight(); - int mouseX = Mouse.getX() * width / displayWidth; - int mouseY = height - Mouse.getY() * height / displayHeight - 1; + int mouseX = Mouse.getX() * width / this.displayWidth; + int mouseY = height - Mouse.getY() * height / this.displayHeight - 1; Gui.drawRect(0, 0, width, height, Color.WHITE.getRGB()); // DO NOT REMOVE THIS! FOR SOME REASON NANOVG DOESN'T RENDER WITHOUT IT - currentScreen.drawScreen(mouseX, mouseY, 0); + this.currentScreen.drawScreen(mouseX, mouseY, 0); if (crashUI.getShouldCrash()) { crashpatch$letDie = true; throw Objects.requireNonNull(crashUI.getThrowable()); } - framebufferMc.unbindFramebuffer(); + this.framebufferMc.unbindFramebuffer(); GlStateManager.popMatrix(); GlStateManager.pushMatrix(); - framebufferMc.framebufferRender(displayWidth, displayHeight); + this.framebufferMc.framebufferRender(this.displayWidth, this.displayHeight); GlStateManager.popMatrix(); //EventManager.INSTANCE.post(new RenderEvent(Stage.END, 0)); todo diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java index a2f91a0..df04c12 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java @@ -5,6 +5,7 @@ import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; import net.minecraft.tileentity.TileEntity; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; @@ -15,7 +16,9 @@ //TODO: this could be completely useless, check with smarter people @Mixin(TileEntityRendererDispatcher.class) public abstract class MixinTileEntityRendererDispatcher implements StateManager.IResettable { - private boolean drawingBatch = false; + + @Unique + private boolean crashpatch$drawingBatch = false; @Inject(method = "", at = @At(value = "RETURN")) public void onInit(CallbackInfo ci) { @@ -24,12 +27,12 @@ public void onInit(CallbackInfo ci) { @Override public void resetState() { - if (drawingBatch) drawingBatch = false; + if (crashpatch$drawingBatch) crashpatch$drawingBatch = false; } @Redirect(method = "renderTileEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/tileentity/TileEntity;hasFastRenderer()Z")) private boolean isNotFastRenderOrDrawing(TileEntity instance) { - if (!drawingBatch) { + if (!crashpatch$drawingBatch) { return false; } else { return instance.hasFastRenderer(); @@ -38,17 +41,18 @@ private boolean isNotFastRenderOrDrawing(TileEntity instance) { @Redirect(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDFI)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/tileentity/TileEntity;hasFastRenderer()Z")) private boolean isFastRenderOrDrawing(TileEntity instance) { - return drawingBatch && instance.hasFastRenderer(); + return crashpatch$drawingBatch && instance.hasFastRenderer(); } @Inject(method = "preDrawBatch", at = @At("TAIL"), remap = false) private void setDrawingBatchTrue(CallbackInfo ci) { - drawingBatch = true; + crashpatch$drawingBatch = true; } @Inject(method = "drawBatch", at = @At("TAIL"), remap = false) private void setDrawingBatchFalse(int pass, CallbackInfo ci) { - drawingBatch = false; + crashpatch$drawingBatch = false; } + } //#endif diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinWorldRenderer.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinWorldRenderer.java index f0f363d..654a3cb 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinWorldRenderer.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinWorldRenderer.java @@ -24,6 +24,8 @@ private void onInitEnd(int bufferSizeIn, CallbackInfo ci) { @Override public void resetState() { - if (isDrawing) finishDrawing(); + if (this.isDrawing) { + finishDrawing(); + } } } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index 364089d..3801e49 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -4,12 +4,12 @@ package org.polyfrost.crashpatch import net.minecraftforge.fml.common.Mod import net.minecraftforge.fml.common.event.FMLInitializationEvent import net.minecraftforge.fml.common.event.FMLPreInitializationEvent -import org.apache.logging.log4j.LogManager //#else //$$ import net.fabricmc.api.ClientModInitializer //#endif import java.io.File +import org.apache.logging.log4j.LogManager import org.polyfrost.crashpatch.crashes.CrashScanStorage import org.polyfrost.crashpatch.crashes.DeobfuscatingRewritePolicy import org.polyfrost.oneconfig.api.commands.v1.CommandManager @@ -30,10 +30,12 @@ object CrashPatch private val logger = LogManager.getLogger() + @JvmStatic val mcDir by lazy(LazyThreadSafetyMode.PUBLICATION) { File(System.getProperty("user.dir")) } + @JvmStatic val gameDir by lazy(LazyThreadSafetyMode.PUBLICATION) { try { if (mcDir.parentFile?.name?.let { name -> diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt index ccd0f0a..db6b9a1 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt @@ -3,9 +3,9 @@ package org.polyfrost.crashpatch import net.minecraft.util.ChatComponentText import org.polyfrost.crashpatch.crashes.CrashScanStorage import org.polyfrost.oneconfig.api.commands.v1.factories.annotated.Command +import org.polyfrost.oneconfig.utils.v1.dsl.openUI import org.polyfrost.universal.ChatColor import org.polyfrost.universal.UMinecraft -import org.polyfrost.utils.v1.dsl.openUI @Command(CrashPatch.ID) object CrashPatchCommand { @@ -18,7 +18,6 @@ object CrashPatchCommand { @Command fun reload() { if (CrashScanStorage.downloadJson()) { - CrashScanStorage.simpleCache.clear() UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Successfully reloaded JSON file!")) } else { UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Failed to reload the JSON file!")) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/ModIdentifier.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/ModIdentifier.kt deleted file mode 100644 index e37172b..0000000 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/ModIdentifier.kt +++ /dev/null @@ -1,89 +0,0 @@ -package org.polyfrost.crashpatch.crashes - -import org.polyfrost.crashpatch.logger -import net.minecraft.launchwrapper.Launch -import net.minecraft.launchwrapper.LaunchClassLoader -import net.minecraftforge.fml.common.Loader -import net.minecraftforge.fml.common.ModContainer -import java.io.File -import java.io.IOException -import java.net.URISyntaxException -import java.net.URL - -object ModIdentifier { - - fun identifyFromStacktrace(e: Throwable?): ModContainer? { - val modMap = makeModMap() - - // Get the set of classes - val classes = LinkedHashSet() - e?.stackTrace?.forEachIndexed { index, stackTraceElement -> - if (index < 4) { // everything after the first 3 lines are basically useless and only leads to false detections - classes.add(stackTraceElement.className) - } - } - val mods = LinkedHashSet() - for (className in classes) { - val classMods = identifyFromClass(className, modMap) - if (classMods.isNotEmpty()) { - mods.addAll(classMods) - } - } - return mods.firstOrNull() - } - - private fun identifyFromClass(className: String, modMap: Map>): Set { - // Skip identification for Mixin, one's mod copy of the library is shared with all other mods - if (className.startsWith("org.spongepowered.asm.mixin.")) return emptySet() - - // Get the URL of the class - val untrasformedName = untransformName(Launch.classLoader, className) - var url = Launch.classLoader.getResource(untrasformedName.replace('.', '/') + ".class") - logger.debug("$className = $untrasformedName = $url") - if (url == null) { - logger.warn("Failed to identify $className (untransformed name: $untrasformedName)") - return emptySet() - } - - // Get the mod containing that class - return try { - if (url.protocol == "jar") url = URL(url.file.substring(0, url.file.indexOf('!'))) - modMap[File(url.toURI()).canonicalFile] ?: emptySet() - } catch (e: URISyntaxException) { - throw RuntimeException(e) - } catch (e: IOException) { - throw RuntimeException(e) - } - } - - private fun makeModMap(): Map> { - val modMap = HashMap>() - for (mod in Loader.instance().modList) { - val currentMods = modMap.getOrDefault(mod.source, HashSet()) - currentMods.add(mod) - try { - modMap[mod.source.canonicalFile] = currentMods - } catch (e: IOException) { - throw RuntimeException(e) - } - } - try { - modMap.remove(Loader.instance().minecraftModContainer.source) // Ignore minecraft jar (minecraft) - modMap.remove(Loader.instance().indexedModList["FML"]!!.source) // Ignore forge jar (FML, forge) - } catch (ignored: NullPointerException) { - // Workaround for https://github.com/MinecraftForge/MinecraftForge/issues/4919 - } - return modMap - } - - private fun untransformName(launchClassLoader: LaunchClassLoader, className: String): String { - return try { - val untransformNameMethod = - LaunchClassLoader::class.java.getDeclaredMethod("untransformName", String::class.java) - untransformNameMethod.isAccessible = true - untransformNameMethod.invoke(launchClassLoader, className) as String - } catch (e: ReflectiveOperationException) { - throw RuntimeException(e) - } - } -} diff --git a/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt b/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt new file mode 100644 index 0000000..cee2f73 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt @@ -0,0 +1,192 @@ +package org.polyfrost.crashpatch.identifier + +//#if FORGE +import java.io.IOException +import java.net.URISyntaxException +import java.net.URL +import net.minecraft.launchwrapper.Launch +import net.minecraft.launchwrapper.LaunchClassLoader +import net.minecraftforge.fml.common.Loader +//#else +//$$ import net.fabricmc.loader.api.FabricLoader +//$$ import org.jetbrains.annotations.Nullable +//$$ import org.spongepowered.asm.mixin.extensibility.IMixinInfo +//$$ import org.spongepowered.asm.mixin.transformer.ClassInfo +//$$ import java.lang.reflect.Field +//$$ import java.nio.file.Path +//$$ import java.nio.file.Paths +//#endif + +import java.io.File +import org.apache.logging.log4j.LogManager + +typealias ModMap = Map> + +object ModIdentifier { + + private val logger = LogManager.getLogger() + + fun identifyFromStacktrace(e: Throwable?): ModMetadata? { + val modMap = makeModMap() + + // Get the set of classes + val classes = LinkedHashSet() + e?.stackTrace?.forEachIndexed { index, stackTraceElement -> + if (index < 4) { // everything after the first 3 lines are basically useless and only leads to false detections + classes.add(stackTraceElement.className) + } + } + + val mods = LinkedHashSet() + for (className in classes) { + val classMods = identifyFromClass(className, modMap) + if (classMods.isNotEmpty()) { + mods.addAll(classMods) + } + } + + return mods.firstOrNull() + } + + private fun identifyFromClass(className: String, modMap: ModMap): Set { + // Skip identification for Mixin, one's mod copy of the library is shared with all other mods + if (className.startsWith("org.spongepowered.asm.mixin.")) { + return emptySet() + } + + //#if FORGE + // Get the URL of the class + val untrasformedName = untransformName(Launch.classLoader, className) + var url = Launch.classLoader.getResource(untrasformedName.replace('.', '/') + ".class") + logger.debug("{} = {} = {}", className, untrasformedName, url) + if (url == null) { + logger.warn("Failed to identify $className (untransformed name: $untrasformedName)") + return emptySet() + } + + // Get the mod containing that class + return try { + if (url.protocol == "jar") url = URL(url.file.substring(0, url.file.indexOf('!'))) + modMap[File(url.toURI()).canonicalFile] ?: emptySet() + } catch (e: URISyntaxException) { + throw RuntimeException(e) + } catch (e: IOException) { + throw RuntimeException(e) + } + //#else + //$$ try { + //$$ val clz = Class.forName(className) + //$$ val codeSource = clz.protectionDomain.codeSource + //$$ if (codeSource == null) { + //$$ logger.debug("Failed to identify $className because of a null code source") + //$$ return emptySet() + //$$ } + //$$ + //$$ val url = codeSource.location + //$$ if (url == null) { + //$$ logger.debug("Failed to identify $className because of a null URL") + //$$ return emptySet() + //$$ } + //$$ + //$$ return getModsAt(Paths.get(url.toURI()), modMap) + //$$ } catch (e: Exception) { + //$$ logger.debug("Ignoring class $className for identification because of an error", e) + //$$ return emptySet() + //$$ } + //#endif + } + + //#if FABRIC + //$$ private fun getModsAt(path: Path, modMap: ModMap): MutableSet { + //$$ val mod: MutableSet? = modMap[path.toFile()] + //$$ if (mod != null) return mod + //$$ + //$$ else if (FabricLoader.getInstance().isDevelopmentEnvironment) { + //$$ // For some reason, in dev, the mod being tested has the 'resources' folder as the origin instead of the 'classes' folder. + //$$ + //$$ val resourcesPathString: String = + //$$ path.toString().replace("\\", "/") // Make it work with Architectury as well + //$$ .replace("common/build/classes/java/main", "fabric/build/resources/main") + //$$ .replace("common/build/classes/kotlin/main", "fabric/build/resources/main") + //$$ .replace("classes/java/main", "resources/main") + //$$ .replace("classes/kotlin/main", "resources/main") + //$$ val resourcesPath: Path = Paths.get(resourcesPathString) + //$$ return modMap.getOrElse(resourcesPath.toFile()) { emptySet() }.toMutableSet() + //$$ } else { + //$$ logger.debug("Mod at path '" + path.toAbsolutePath() + "' is at fault, but it could not be found in the map of mod paths: ") + //$$ return mutableSetOf() + //$$ } + //$$ } + //#endif + + private fun makeModMap(): ModMap { + val modMap = HashMap>() + + //#if FORGE + for (mod in Loader.instance().modList) { + val currentMods = modMap.getOrDefault(mod.source, HashSet()) + currentMods.add(ModMetadata(mod.modId, mod.name)) + + try { + modMap[mod.source.canonicalFile] = currentMods + } catch (e: IOException) { + throw RuntimeException(e) + } + } + + try { + modMap.remove(Loader.instance().minecraftModContainer.source) // Ignore minecraft jar (minecraft) + modMap.remove(Loader.instance().indexedModList["FML"]!!.source) // Ignore forge jar (FML, forge) + } catch (ignored: NullPointerException) { + // Workaround for https://github.com/MinecraftForge/MinecraftForge/issues/4919 + } + //#else + //$$ for (modContainer in FabricLoader.getInstance().allMods) { + //$$ val modMetadata = ModMetadata(modContainer.metadata.id, modContainer.metadata.name) + //$$ for (source in modContainer.origin.paths) { + //$$ modMap.computeIfAbsent(source.toFile()) { mutableSetOf() }.add(modMetadata) + //$$ } + //$$ } + //#endif + + return modMap + } + + //#if FORGE + private fun untransformName(launchClassLoader: LaunchClassLoader, className: String): String { + return try { + val untransformNameMethod = + LaunchClassLoader::class.java.getDeclaredMethod("untransformName", String::class.java) + untransformNameMethod.isAccessible = true + untransformNameMethod.invoke(launchClassLoader, className) as String + } catch (e: ReflectiveOperationException) { + throw RuntimeException(e) + } + } + //#endif + + //#if FABRIC + //$$ private object Reflection { + //$$ var classInfoMixin: Field? = null + //$$ + //$$ init { + //$$ try { + //$$ classInfoMixin = ClassInfo::class.java.getDeclaredField("mixin") + //$$ classInfoMixin!!.isAccessible = true + //$$ } catch (e: NoSuchFieldException) { + //$$ throw java.lang.RuntimeException(e) + //$$ } + //$$ } + //$$ + //$$ @Nullable + //$$ fun getMixinInfo(classInfo: ClassInfo?): IMixinInfo { + //$$ try { + //$$ return classInfoMixin!!.get(classInfo) as IMixinInfo + //$$ } catch (e: IllegalAccessException) { + //$$ throw java.lang.RuntimeException(e) + //$$ } + //$$ } + //$$ } + //#endif + +} diff --git a/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModMetadata.kt b/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModMetadata.kt new file mode 100644 index 0000000..6c694b6 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModMetadata.kt @@ -0,0 +1,6 @@ +package org.polyfrost.crashpatch.identifier + +data class ModMetadata( + val id: String, + val name: String, +) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt index c89e5e9..3a6246a 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt @@ -7,7 +7,7 @@ import net.minecraft.client.gui.GuiScreen import org.polyfrost.crashpatch.CrashPatchConfig import org.spongepowered.asm.mixin.injection.callback.CallbackInfo import org.polyfrost.crashpatch.gui.CrashUI -import org.polyfrost.utils.v1.dsl.mc +import org.polyfrost.oneconfig.utils.v1.dsl.mc object GuiDisconnectedHook { From 4babfbbca15f676d6b2575dc062eb6038150cbf0 Mon Sep 17 00:00:00 2001 From: Deftu Date: Wed, 11 Dec 2024 08:10:10 +0200 Subject: [PATCH 17/37] yeah no nevermind "finished" was a lie --- build.gradle.kts | 2 + settings.gradle.kts | 2 +- .../crashpatch/mixin/MixinCrashReport.java | 1 - .../crashpatch/mixin/MixinGuiConnecting.java | 6 +- .../crashpatch/mixin/MixinGuiDupesFound.java | 2 + .../crashpatch/mixin/MixinMinecraft.java | 11 +++- .../org/polyfrost/crashpatch/CrashPatch.kt | 2 +- .../polyfrost/crashpatch/CrashPatchCommand.kt | 2 +- .../crashpatch/mixins/MixinPlugin.kt | 60 +++++++++++++++++++ src/main/resources/fabric.mod.json | 31 ++++++++++ src/main/resources/mixins.crashpatch.json | 3 +- 11 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 src/main/kotlin/org/polyfrost/crashpatch/mixins/MixinPlugin.kt create mode 100644 src/main/resources/fabric.mod.json diff --git a/build.gradle.kts b/build.gradle.kts index 2f29b56..6ecde0a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,5 +52,7 @@ dependencies { // If we are building for legacy forge, includes the launch wrapper with `shade` as we configured earlier. if (mcData.isLegacyForge) { compileOnly("org.spongepowered:mixin:0.7.11-SNAPSHOT") + } else if (mcData.isFabric) { + modImplementation("net.fabricmc:fabric-language-kotlin:${mcData.dependencies.fabric.fabricLanguageKotlinVersion}") } } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index da4280a..6adc8a5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,7 +22,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.12.0") + id("dev.deftu.gradle.multiversion-root") version("2.12.2") } } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java index 3b88a91..ac37b08 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java @@ -10,7 +10,6 @@ import org.polyfrost.crashpatch.hooks.CrashReportHook; import org.polyfrost.crashpatch.hooks.StacktraceDeobfuscator; import net.minecraft.crash.CrashReport; -import net.minecraftforge.fml.common.ModContainer; import org.polyfrost.crashpatch.identifier.ModMetadata; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java index 2102bca..cf633c3 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -34,7 +34,11 @@ private void drawWarningText(int mouseX, int mouseY, float partialTicks, Callbac } @Override - protected void mouseClicked(int mouseX, int mouseY, int mouseButton) throws IOException { + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) + //#if FORGE + throws IOException + //#endif + { super.mouseClicked(mouseX, mouseY, mouseButton); if (((MinecraftHook) Minecraft.getMinecraft()).hasRecoveredFromCrash()) { if (mouseButton == 0) { diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java index 0b09055..11b7588 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java @@ -1,5 +1,6 @@ package org.polyfrost.crashpatch.mixin; +//#if FORGE import org.polyfrost.crashpatch.CrashPatch; import org.polyfrost.universal.UDesktop; import net.minecraft.client.Minecraft; @@ -93,3 +94,4 @@ private void onDrawScreen(int mouseX, int mouseY, float partialTicks, CallbackIn } } +//#endif diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index 84427e4..1a4d493 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -1,5 +1,10 @@ package org.polyfrost.crashpatch.mixin; +//#if FORGE +import net.minecraftforge.fml.client.SplashProgress; +import net.minecraftforge.fml.common.FMLCommonHandler; +//#endif + import net.minecraft.client.Minecraft; import net.minecraft.client.audio.SoundHandler; import net.minecraft.client.gui.FontRenderer; @@ -23,8 +28,6 @@ import net.minecraft.util.MinecraftError; import net.minecraft.util.ReportedException; import net.minecraft.util.ResourceLocation; -import net.minecraftforge.fml.client.SplashProgress; -import net.minecraftforge.fml.common.FMLCommonHandler; import org.apache.logging.log4j.Logger; import org.lwjgl.LWJGLException; import org.lwjgl.input.Mouse; @@ -328,8 +331,10 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { this.running = true; try { + //#if FORGE //noinspection deprecation SplashProgress.pause();// Disable the forge splash progress screen + //#endif GlStateManager.disableTexture2D(); GlStateManager.enableTexture2D(); } catch (Throwable ignored) { @@ -403,12 +408,14 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { } } + //#if FORGE @Redirect(method = "displayCrashReport", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/common/FMLCommonHandler;handleExit(I)V")) public void redirect(FMLCommonHandler instance, int code) { if (crashpatch$letDie) { instance.handleExit(code); } } + //#endif @Unique private void crashpatch$resetState() { diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index 3801e49..27c14e4 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -64,7 +64,7 @@ object CrashPatch } fun initialize() { - CommandManager.registerCommand(CrashPatchCommand) + CommandManager.registerCommand(CrashPatchCommand()) CrashPatchConfig // Initialize the config } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt index db6b9a1..8ba2631 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt @@ -8,7 +8,7 @@ import org.polyfrost.universal.ChatColor import org.polyfrost.universal.UMinecraft @Command(CrashPatch.ID) -object CrashPatchCommand { +class CrashPatchCommand { @Command fun main() { diff --git a/src/main/kotlin/org/polyfrost/crashpatch/mixins/MixinPlugin.kt b/src/main/kotlin/org/polyfrost/crashpatch/mixins/MixinPlugin.kt new file mode 100644 index 0000000..41c60be --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/mixins/MixinPlugin.kt @@ -0,0 +1,60 @@ +package org.polyfrost.crashpatch.mixins + +//#if MC >= 1.16.5 || FABRIC +//$$ import org.objectweb.asm.tree.ClassNode +//#endif + +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin +import org.spongepowered.asm.mixin.extensibility.IMixinInfo + +class MixinPlugin : IMixinConfigPlugin { + + override fun getMixins(): MutableList { + val result = mutableListOf() + + //#if FORGE + result.add("MixinGuiDupesFound") + result.add("MixinTileEntityRendererDispatcher") + //#endif + + return result + } + + override fun getRefMapperConfig(): String? = null + override fun shouldApplyMixin(targetClassName: String, mixinClassName: String): Boolean = true + + override fun onLoad(mixinPackage: String) { + // no-op + } + + override fun acceptTargets(myTargets: MutableSet, otherTargets: MutableSet) { + // no-op + } + + override fun preApply( + targetClassName: String, + //#if MC >= 1.16.5 || FABRIC + //$$ targetClass: ClassNode, + //#else + targetClass: org.spongepowered.asm.lib.tree.ClassNode, + //#endif + mixinClassName: String, + mixinInfo: IMixinInfo + ) { + // no-op + } + + override fun postApply( + targetClassName: String, + //#if MC >= 1.16.5 || FABRIC + //$$ targetClass: ClassNode, + //#else + targetClass: org.spongepowered.asm.lib.tree.ClassNode, + //#endif + mixinClassName: String, + mixinInfo: IMixinInfo + ) { + // no-op + } + +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..355dee0 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,31 @@ +{ + "schemaVersion": 1, + "id": "${mod_id}", + "version": "${mod_version}", + "name": "${mod_name}", + "authors": [ + "Polyfrost", + "SkyClient" + ], + "contact": { + "issues": "https://github.com/Polyfrost/${mod_name}/issues", + "sources": "https://github.com/Polyfrost/${mod_name}" + }, + "license": "GPL-3.0-or-later", + "environment": "*", + "entrypoints": { + "client": [ + { + "adapter": "kotlin", + "value": "org.polyfrost.crashpatch.CrashPatch" + } + ] + }, + "mixins": [ + "mixins.${mod_id}.json" + ], + "depends": { + "fabricloader": ">=0.15.11", + "fabric-language-kotlin": "*" + } +} \ No newline at end of file diff --git a/src/main/resources/mixins.crashpatch.json b/src/main/resources/mixins.crashpatch.json index eb1957a..02aa539 100644 --- a/src/main/resources/mixins.crashpatch.json +++ b/src/main/resources/mixins.crashpatch.json @@ -2,6 +2,7 @@ "compatibilityLevel": "JAVA_8", "minVersion": "0.7", "package": "org.polyfrost.crashpatch.mixin", + "plugin": "org.polyfrost.crashpatch.mixins.MixinPlugin", "refmap": "mixin.crashpatch.refmap.json", "injectors": { "maxShiftBy": 5 @@ -11,9 +12,7 @@ "MixinCrashReport", "MixinCrashReportCategory", "MixinGuiConnecting", - "MixinGuiDupesFound", "MixinMinecraft", - "MixinTileEntityRendererDispatcher", "MixinWorldRenderer" ], "verbose": true From 62e1bc9df0e5587485956f91e9784728756f8854 Mon Sep 17 00:00:00 2001 From: Deftu Date: Thu, 2 Jan 2025 20:36:34 +0200 Subject: [PATCH 18/37] Bring up to speed with the OneConfig example mod --- .github/workflows/gradle.yml | 49 ++++++++++++++-------------- build.gradle.kts | 63 ++++++++++++++++-------------------- root.gradle.kts | 11 ++++++- settings.gradle.kts | 17 +++++++--- 4 files changed, 73 insertions(+), 67 deletions(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 1de3da8..62008db 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,48 +1,47 @@ -name: build +# Build Workflow + +name: Build with Gradle on: - push: - branches: '*' - paths-ignore: - - 'README.md' - - 'LICENSE' - - '.gitignore' pull_request: - branches: '*' - paths-ignore: - - 'README.md' - - 'LICENSE' - - '.gitignore' workflow_dispatch: + push: concurrency: - # Maximum of one running workflow per pull request source branch - # or branch and run number combination (cancels old run if action is rerun) group: ${{ github.head_ref || format('{0}-{1}', github.ref, github.run_number) }} cancel-in-progress: true jobs: build: - name: "Build" - runs-on: "ubuntu-latest" + name: Build - steps: - - uses: actions/checkout@v2 + runs-on: ubuntu-latest - - uses: gradle/wrapper-validation-action@v1 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 10 - - uses: actions/setup-java@v2 + - name: Set up JDK 17 + uses: actions/setup-java@v4 with: - distribution: "temurin" - java-version: "17" + java-version: 21 + distribution: temurin - - uses: actions/cache@v2 + - uses: actions/cache@v4 with: path: | ~/.gradle/caches ~/.gradle/wrapper + **/loom-cache + **/prebundled-jars key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle- - - run: chmod +x ./gradlew - - run: ./gradlew --no-daemon build \ No newline at end of file + + - name: Chmod Gradle + run: chmod +x ./gradlew + + - name: Build + run: ./gradlew build --no-daemon \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 6ecde0a..65e98b8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,58 +1,49 @@ @file:Suppress("UnstableApiUsage", "PropertyName") import dev.deftu.gradle.utils.GameSide +import dev.deftu.gradle.utils.MinecraftVersion plugins { java kotlin("jvm") - id("dev.deftu.gradle.multiversion") - id("dev.deftu.gradle.tools") - id("dev.deftu.gradle.tools.resources") - id("dev.deftu.gradle.tools.bloom") - id("dev.deftu.gradle.tools.shadow") - id("dev.deftu.gradle.tools.minecraft.loom") + id("dev.deftu.gradle.multiversion") // Applies preprocessing for multiple versions of Minecraft and/or multiple mod loaders. + id("dev.deftu.gradle.tools") // Applies several configurations to things such as the Java version, project name/version, etc. + id("dev.deftu.gradle.tools.resources") // Applies resource processing so that we can replace tokens, such as our mod name/version, in our resources. + id("dev.deftu.gradle.tools.bloom") // Applies the Bloom plugin, which allows us to replace tokens in our source files, such as being able to use `@MOD_VERSION` in our source files. + id("dev.deftu.gradle.tools.shadow") // Applies the Shadow plugin, which allows us to shade our dependencies into our mod JAR. This is NOT recommended for Fabric mods, but we have an *additional* configuration for those! + id("dev.deftu.gradle.tools.minecraft.loom") // Applies the Loom plugin, which automagically configures Essential's Architectury Loom plugin for you. + id("dev.deftu.gradle.tools.minecraft.releases") // Applies the Minecraft auto-releasing plugin, which allows you to automatically release your mod to CurseForge and Modrinth. } toolkitLoomHelper { - // Adds OneConfig to our project - useOneConfig(mcData, "commands", "config", "config-impl", "events", "internal", "ui", "utils") + useOneConfig { + version = "1.0.0-alpha.49" + loaderVersion = "1.1.0-alpha.35" - // Removes the server configs from IntelliJ IDEA, leaving only client runs. - // If you're developing a server-side mod, you can remove this line. + usePolyMixin = true + polyMixinVersion = "0.8.4+build.2" + + applyLoaderTweaker = true + + for (module in arrayOf("commands", "config", "config-impl", "events", "internal", "ui", "utils")) { + +module + } + } + + // Turns off the server-side run configs, as we're building a client-sided mod. disableRunConfigs(GameSide.SERVER) - // Sets up our Mixin refmap naming + // Defines the name of the Mixin refmap, which is used to map the Mixin classes to the obfuscated Minecraft classes. if (!mcData.isNeoForge) { useMixinRefMap(modData.id) } - // Adds the tweak class if we are building legacy version of forge as per the documentation (https://docs.polyfrost.org) - if (mcData.isLegacyForge) { - useTweaker("org.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker", GameSide.CLIENT) - useForgeMixin(modData.id) // Configures the mixins if we are building for forge, useful for when we are dealing with cross-platform projects. + if (mcData.isForge) { + // Configures the Mixin tweaker if we are building for Forge. + useForgeMixin(modData.id) } } -// Configures the output directory for when building from the `src/resources` directory. -sourceSets { - main { - output.setResourcesDir(java.classesDirectory) - } -} - -// Adds the Polyfrost maven repository so that we can get the libraries necessary to develop the mod. -repositories { - maven("https://repo.polyfrost.org/releases") - maven("https://repo.polyfrost.org/snapshots") -} - -// Configures the libraries/dependencies for your mod. dependencies { implementation(shade("gs.mclo:api:3.0.1")!!) - // If we are building for legacy forge, includes the launch wrapper with `shade` as we configured earlier. - if (mcData.isLegacyForge) { - compileOnly("org.spongepowered:mixin:0.7.11-SNAPSHOT") - } else if (mcData.isFabric) { - modImplementation("net.fabricmc:fabric-language-kotlin:${mcData.dependencies.fabric.fabricLanguageKotlinVersion}") - } -} \ No newline at end of file +} diff --git a/root.gradle.kts b/root.gradle.kts index e377dbe..1873883 100644 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -3,7 +3,16 @@ plugins { } preprocess { + // Adding new versions/loaders can be done like so: + // For each version, we add a new wrapper around the last from highest to lowest. + // Each mod loader needs to link up to the previous version's mod loader so that the mappings can be processed from the previous version. + // "1.12.2-forge"(11202, "srg") { + // "1.8.9-forge"(10809, "srg") + // } + "1.8.9-forge"(10809, "srg") { - "1.8.9-fabric"(10809, "yarn") {} + "1.8.9-fabric"(10809, "yarn") } + + strictExtraMappings.set(true) } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 6adc8a5..fbd6c82 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,8 +1,10 @@ +@file:Suppress("PropertyName") + import groovy.lang.MissingPropertyException pluginManagement { repositories { - // Repositories + // Releases maven("https://maven.deftu.dev/releases") maven("https://maven.fabricmc.net") maven("https://maven.architectury.dev/") @@ -15,30 +17,35 @@ pluginManagement { maven("https://maven.deftu.dev/snapshots") mavenLocal() - // Default repositories + // Default gradlePluginPortal() mavenCentral() } plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.12.2") + id("dev.deftu.gradle.multiversion-root") version("2.18.1") } } val projectName: String = extra["mod.name"]?.toString() ?: throw MissingPropertyException("mod.name has not been set.") + +// Configures the root project Gradle name based on the value in `gradle.properties` rootProject.name = projectName rootProject.buildFileName = "root.gradle.kts" // Adds all of our build target versions to the classpath if we need to add version-specific code. +// Update this list if you want to remove/add a version and/or mod loader. +// The format is: version-modloader (f.ex: 1.8.9-forge, 1.17.1-fabric, etc) +// **REMEMBER TO ALSO UPDATE THE `root.gradle.kts` AND `build.gradle.kts` FILES WITH THE NEW VERSION(S). listOf( "1.8.9-forge", - "1.8.9-fabric" + "1.8.9-fabric", ).forEach { version -> include(":$version") project(":$version").apply { projectDir = file("versions/$version") buildFileName = "../../build.gradle.kts" } -} \ No newline at end of file +} From b851169976f7fa7a6793c19057f1a0081b3f16c5 Mon Sep 17 00:00:00 2001 From: ev chang Date: Sun, 2 Feb 2025 12:03:14 -0500 Subject: [PATCH 19/37] bump --- build.gradle.kts | 10 +- gradle.properties | 2 +- main.py | 93 ------------------- settings.gradle.kts | 2 +- .../{mixins => plugin}/MixinPlugin.kt | 22 ++--- src/main/resources/mixins.crashpatch.json | 2 +- 6 files changed, 14 insertions(+), 117 deletions(-) delete mode 100644 main.py rename src/main/kotlin/org/polyfrost/crashpatch/{mixins => plugin}/MixinPlugin.kt (70%) diff --git a/build.gradle.kts b/build.gradle.kts index 65e98b8..8bb5080 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ @file:Suppress("UnstableApiUsage", "PropertyName") import dev.deftu.gradle.utils.GameSide -import dev.deftu.gradle.utils.MinecraftVersion +import dev.deftu.gradle.utils.includeOrShade plugins { java @@ -10,15 +10,15 @@ plugins { id("dev.deftu.gradle.tools") // Applies several configurations to things such as the Java version, project name/version, etc. id("dev.deftu.gradle.tools.resources") // Applies resource processing so that we can replace tokens, such as our mod name/version, in our resources. id("dev.deftu.gradle.tools.bloom") // Applies the Bloom plugin, which allows us to replace tokens in our source files, such as being able to use `@MOD_VERSION` in our source files. - id("dev.deftu.gradle.tools.shadow") // Applies the Shadow plugin, which allows us to shade our dependencies into our mod JAR. This is NOT recommended for Fabric mods, but we have an *additional* configuration for those! id("dev.deftu.gradle.tools.minecraft.loom") // Applies the Loom plugin, which automagically configures Essential's Architectury Loom plugin for you. + id("dev.deftu.gradle.tools.shadow") // Applies the Shadow plugin, which allows us to shade our dependencies into our mod JAR. This is NOT recommended for Fabric mods, but we have an *additional* configuration for those! id("dev.deftu.gradle.tools.minecraft.releases") // Applies the Minecraft auto-releasing plugin, which allows you to automatically release your mod to CurseForge and Modrinth. } toolkitLoomHelper { useOneConfig { - version = "1.0.0-alpha.49" - loaderVersion = "1.1.0-alpha.35" + version = "1.0.0-alpha.62" + loaderVersion = "1.1.0-alpha.44" usePolyMixin = true polyMixinVersion = "0.8.4+build.2" @@ -45,5 +45,5 @@ toolkitLoomHelper { } dependencies { - implementation(shade("gs.mclo:api:3.0.1")!!) + implementation(includeOrShade("gs.mclo:api:3.0.1")!!) } diff --git a/gradle.properties b/gradle.properties index 80716ed..95bb22e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,4 +8,4 @@ org.gradle.jvmargs=-Xmx2G mod.group=org.polyfrost mod.id=crashpatch mod.name=CrashPatch -mod.version=2.0.1 \ No newline at end of file +mod.version=2.1.0-alpha.1 \ No newline at end of file diff --git a/main.py b/main.py deleted file mode 100644 index b851061..0000000 --- a/main.py +++ /dev/null @@ -1,93 +0,0 @@ -import os -import pathlib - -def add_import(imports): - with open(dir, "w") as new_file: - if original_text != content: - package_index = original_text.find('package') - line_end_index = original_text.find('\n', package_index) - - modified_text = content[:line_end_index] + '\nimport ' + imports - - if name.endswith(".java"): - modified_text += ";" - modified_text += "\n" + content[line_end_index:] - new_file.write(modified_text) - else: - new_file.write(content) - - -input("MAKE SURE YOU HAVE MADE A BACKUP OF YOUR PROJECT BEFORE RUNNING THIS SCRIPT (press enter to continue)\n") - -try: - os.chdir('src') -except FileNotFoundError: - input('Please put this script next to the src/ directory! (press enter to exit)') - exit() - -for path, _, files in os.walk("."): - for name in files: - dir = str(pathlib.PurePath(path, name)) - if not (name.endswith(".java") or name.endswith(".kt")): - continue - - with open(dir,"r+") as f: - new_f = f.readlines() - f.seek(0) - for line in new_f: - if "import cc.polyfrost.oneconfig.config.core.OneColor" in line: - f.write(line.replace("cc.polyfrost.oneconfig.config.core.OneColor", "org.polyfrost.polyui.color.PolyColor")) - elif "import cc.polyfrost.oneconfig.config.core.OneKeyBind" in line: - f.write(line.replace("cc.polyfrost.oneconfig.config.core.OneKeyBind", "org.polyfrost.polyui.input.Keybinder.Bind")) - if "import cc.polyfrost.oneconfig.config" in line: - f.write(line.replace("cc.polyfrost.oneconfig.config", "org.polyfrost.oneconfig.api.config.v1")) - elif "import cc.polyfrost.oneconfig.utils.commands.annotations" in line: - f.write(line.replace("cc.polyfrost.oneconfig.utils.commands.annotations", "org.polyfrost.oneconfig.api.commands.v1.factories.annotated")) - elif "import cc.polyfrost.oneconfig.utils.commands" in line: - f.write(line.replace("cc.polyfrost.oneconfig.utils.commands", "org.polyfrost.oneconfig.api.commands.v1")) - elif "import cc.polyfrost.oneconfig.utils.hypixel" in line: - f.write(line.replace("cc.polyfrost.oneconfig.utils.hypixel", "org.polyfrost.oneconfig.api.hypixel.v1")) - elif "import cc.polyfrost.oneconfig.utils.Notifications" in line: - f.write(line.replace("cc.polyfrost.oneconfig.utils.Notifications", "org.polyfrost.oneconfig.api.ui.v1.notifications.Notifications")) - elif "import cc.polyfrost.oneconfig.utils" in line: - f.write(line.replace("cc.polyfrost.oneconfig.utils", "org.polyfrost.oneconfig.utils.v1")) - elif "import cc.polyfrost.oneconfig.platform" in line: - f.write(line.replace("cc.polyfrost.oneconfig.platform", "org.polyfrost.oneconfig.api.platform.v1")) - elif "import cc.polyfrost.oneconfig.events.event" in line: - f.write(line.replace("cc.polyfrost.oneconfig.events", "org.polyfrost.oneconfig.api.event.v1.events")) - elif "import cc.polyfrost.oneconfig.events" in line: - f.write(line.replace("cc.polyfrost.oneconfig.events", "org.polyfrost.oneconfig.api.event.v1")) - elif "import cc.polyfrost.oneconfig.libs.universal" in line: - f.write(line.replace("cc.polyfrost.oneconfig.libs.universal", "org.polyfrost.universal")) - elif "import cc.polyfrost" in line: - f.write(line.replace("cc.polyfrost", "org.polyfrost")) - else: - f.write(line) - - f.truncate() - -input("Finished removing OneConfig V0 imports.\n\nPress enter to start replacing V0 methods.\n") - -for path, _, files in os.walk("."): - for name in files: - dir = str(pathlib.PurePath(path, name)) - if not (name.endswith(".java") or name.endswith(".kt")): - continue - - with open(dir, "r") as file: - original_text = file.read() - with open(dir, "r+") as file: - content = file.read().replace("TickDelay", "EventDelay.ticks").replace("tick(", "EventDelay.ticks(").replace("RenderTickDelay", "EventDelay.render").replace("renderTick(", "EventDelay.render(") - add_import("org.polyfrost.oneconfig.api.event.v1.EventDelay") - - with open(dir, "r") as file: - original_text = file.read() - with open(dir, "r+") as file: - content = file.read().replace("OneColor", "PolyColor") - - with open(dir, "r") as file: - original_text = file.read() - with open(dir, "r+") as file: - content = file.read().replace("OneKeyBind", "Keybinder.Bind") - -input("Done! (press enter to exit)") \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index fbd6c82..0add377 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,7 +24,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.18.1") + id("dev.deftu.gradle.multiversion-root") version("2.22.0") } } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/mixins/MixinPlugin.kt b/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt similarity index 70% rename from src/main/kotlin/org/polyfrost/crashpatch/mixins/MixinPlugin.kt rename to src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt index 41c60be..e2c6562 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/mixins/MixinPlugin.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt @@ -1,12 +1,10 @@ -package org.polyfrost.crashpatch.mixins - -//#if MC >= 1.16.5 || FABRIC -//$$ import org.objectweb.asm.tree.ClassNode -//#endif +package org.polyfrost.crashpatch.plugin import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin import org.spongepowered.asm.mixin.extensibility.IMixinInfo +import org.objectweb.asm.tree.ClassNode + class MixinPlugin : IMixinConfigPlugin { override fun getMixins(): MutableList { @@ -33,11 +31,7 @@ class MixinPlugin : IMixinConfigPlugin { override fun preApply( targetClassName: String, - //#if MC >= 1.16.5 || FABRIC - //$$ targetClass: ClassNode, - //#else - targetClass: org.spongepowered.asm.lib.tree.ClassNode, - //#endif + targetClass: ClassNode, mixinClassName: String, mixinInfo: IMixinInfo ) { @@ -46,15 +40,11 @@ class MixinPlugin : IMixinConfigPlugin { override fun postApply( targetClassName: String, - //#if MC >= 1.16.5 || FABRIC - //$$ targetClass: ClassNode, - //#else - targetClass: org.spongepowered.asm.lib.tree.ClassNode, - //#endif + targetClass: ClassNode, mixinClassName: String, mixinInfo: IMixinInfo ) { // no-op } -} +} \ No newline at end of file diff --git a/src/main/resources/mixins.crashpatch.json b/src/main/resources/mixins.crashpatch.json index 02aa539..4ce774f 100644 --- a/src/main/resources/mixins.crashpatch.json +++ b/src/main/resources/mixins.crashpatch.json @@ -2,7 +2,7 @@ "compatibilityLevel": "JAVA_8", "minVersion": "0.7", "package": "org.polyfrost.crashpatch.mixin", - "plugin": "org.polyfrost.crashpatch.mixins.MixinPlugin", + "plugin": "org.polyfrost.crashpatch.plugin.MixinPlugin", "refmap": "mixin.crashpatch.refmap.json", "injectors": { "maxShiftBy": 5 From f75e22fd8f1c38f37ea9a248ae532072c353e8ce Mon Sep 17 00:00:00 2001 From: ev chang Date: Sun, 2 Feb 2025 12:05:04 -0500 Subject: [PATCH 20/37] oops --- build.gradle.kts | 6 ++++++ src/main/resources/mcmod.info | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 8bb5080..371fe98 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,3 +47,9 @@ toolkitLoomHelper { dependencies { implementation(includeOrShade("gs.mclo:api:3.0.1")!!) } + +tasks { + jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } +} \ No newline at end of file diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index 337ce8e..03ed1ae 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -8,8 +8,7 @@ "url": "https://github.com/Polyfrost/CrashPatch", "updateUrl": "https://github.com/Polyfrost/CrashPatch/releases/latest", "authorList": [ - "Polyfrost", - "SkyClient" + "Polyfrost" ], "credits": "vfyjxf for the 1.7.10 port of VanillaFix (BetterCrashes), Runemoro for VanillaFix, natanfudge for NotEnoughCrashes.", "screenshots": [], From 067d8ee1b6f957d22a77b85296ff675be18ec185 Mon Sep 17 00:00:00 2001 From: ev chang Date: Sat, 15 Mar 2025 20:12:08 +0900 Subject: [PATCH 21/37] update to alpha.72 --- build.gradle.kts | 2 +- .../crashpatch/mixin/MixinGuiConnecting.java | 8 ++--- .../crashpatch/mixin/MixinGuiDupesFound.java | 4 +-- .../org/polyfrost/crashpatch/CrashPatch.kt | 26 ++++++++++++++- .../polyfrost/crashpatch/CrashPatchCommand.kt | 32 ------------------- .../polyfrost/crashpatch/CrashPatchConfig.kt | 4 +-- .../crashpatch/crashes/CrashScanStorage.kt | 4 +-- .../org/polyfrost/crashpatch/gui/CrashUI.kt | 12 +++---- 8 files changed, 42 insertions(+), 50 deletions(-) delete mode 100644 src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt diff --git a/build.gradle.kts b/build.gradle.kts index 371fe98..b9d52ca 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,7 @@ plugins { toolkitLoomHelper { useOneConfig { - version = "1.0.0-alpha.62" + version = "1.0.0-alpha.72" loaderVersion = "1.1.0-alpha.44" usePolyMixin = true diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java index cf633c3..79e270c 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -1,7 +1,7 @@ package org.polyfrost.crashpatch.mixin; -import org.polyfrost.universal.ChatColor; -import org.polyfrost.universal.UDesktop; +import dev.deftu.omnicore.client.OmniDesktop; +import dev.deftu.textile.minecraft.MinecraftTextFormat; import org.polyfrost.crashpatch.CrashPatch; import org.polyfrost.crashpatch.hooks.MinecraftHook; import net.minecraft.client.Minecraft; @@ -30,7 +30,7 @@ private void drawWarningText(int mouseX, int mouseY, float partialTicks, Callbac @Unique private String crashpatch$getText() { - return ChatColor.RED + "If Minecraft is stuck on this screen, please force close the game" + (CrashPatch.INSTANCE.isSkyclient() ? " and go to https://discord.gg/eh7tNFezct for support" : "") + "."; + return MinecraftTextFormat.RED + "If Minecraft is stuck on this screen, please force close the game" + (CrashPatch.INSTANCE.isSkyclient() ? " and go to https://discord.gg/eh7tNFezct for support" : "") + "."; } @Override @@ -50,7 +50,7 @@ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) int left = (this.width / 2) - width / 2; if ((width == -1 || (left < mouseX && left + width > mouseX)) && (mouseY > 5 && mouseY < 15 + ((list.length - 1) * (this.fontRendererObj.FONT_HEIGHT + 2)))) { - UDesktop.browse(URI.create("https://discord.gg/eh7tNFezct")); + OmniDesktop.browse(URI.create("https://discord.gg/eh7tNFezct")); } } } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java index 11b7588..58df291 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java @@ -1,8 +1,8 @@ package org.polyfrost.crashpatch.mixin; //#if FORGE +import dev.deftu.omnicore.client.OmniDesktop; import org.polyfrost.crashpatch.CrashPatch; -import org.polyfrost.universal.UDesktop; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiErrorScreen; @@ -42,7 +42,7 @@ private void onInit(CallbackInfo ci) { protected void actionPerformed(GuiButton button) { switch (button.id) { case 0: - UDesktop.open(new File(CrashPatch.getMcDir(), "mods")); + OmniDesktop.open(new File(CrashPatch.getMcDir(), "mods")); break; case 1: FMLCommonHandler.instance().exitJava(0, false); diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index 27c14e4..47c22d7 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -8,12 +8,16 @@ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent //$$ import net.fabricmc.api.ClientModInitializer //#endif +import dev.deftu.omnicore.client.OmniClientCommands +import dev.deftu.textile.minecraft.MinecraftTextFormat + import java.io.File import org.apache.logging.log4j.LogManager import org.polyfrost.crashpatch.crashes.CrashScanStorage import org.polyfrost.crashpatch.crashes.DeobfuscatingRewritePolicy import org.polyfrost.oneconfig.api.commands.v1.CommandManager import org.polyfrost.oneconfig.utils.v1.Multithreading +import org.polyfrost.oneconfig.utils.v1.dsl.openUI //#if FORGE @Mod(modid = CrashPatch.ID, version = CrashPatch.VERSION, name = CrashPatch.NAME, modLanguageAdapter = "org.polyfrost.oneconfig.utils.v1.forge.KotlinLanguageAdapter") @@ -64,7 +68,27 @@ object CrashPatch } fun initialize() { - CommandManager.registerCommand(CrashPatchCommand()) + OmniClientCommands.initialize() + + val command = CommandManager.literal(ID) + command.executes { + CrashPatchConfig.openUI() + 1 + } + command.then(CommandManager.literal("reload").executes { ctx -> + if (CrashScanStorage.downloadJson()) { + ctx.source.showMessage("${MinecraftTextFormat.GREEN}[CrashPatch] Successfully reloaded JSON file!") + } else { + ctx.source.showMessage("${MinecraftTextFormat.RED}[CrashPatch] Failed to reload the JSON file!") + } + 1 + }) + command.then(CommandManager.literal("crash").executes { ctx -> + requestedCrash = true + 1 + }) + CommandManager.register(command) + CrashPatchConfig // Initialize the config } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt deleted file mode 100644 index 8ba2631..0000000 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchCommand.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.polyfrost.crashpatch - -import net.minecraft.util.ChatComponentText -import org.polyfrost.crashpatch.crashes.CrashScanStorage -import org.polyfrost.oneconfig.api.commands.v1.factories.annotated.Command -import org.polyfrost.oneconfig.utils.v1.dsl.openUI -import org.polyfrost.universal.ChatColor -import org.polyfrost.universal.UMinecraft - -@Command(CrashPatch.ID) -class CrashPatchCommand { - - @Command - fun main() { - CrashPatchConfig.openUI() - } - - @Command - fun reload() { - if (CrashScanStorage.downloadJson()) { - UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Successfully reloaded JSON file!")) - } else { - UMinecraft.getMinecraft().thePlayer.addChatMessage(ChatComponentText("${ChatColor.RED}[CrashPatch] Failed to reload the JSON file!")) - } - } - - @Command - fun crash() { - CrashPatch.requestedCrash = true - } - -} diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt index 59490ce..9b48611 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt @@ -1,8 +1,8 @@ package org.polyfrost.crashpatch +import dev.deftu.omnicore.client.OmniDesktop import org.polyfrost.oneconfig.api.config.v1.Config import org.polyfrost.oneconfig.api.config.v1.annotations.* -import org.polyfrost.universal.UDesktop import java.net.URI object CrashPatchConfig : Config("crashpatch.json", "/assets/crashpatch/crashpatch_dark.svg", "CrashPatch", Category.QOL) { @@ -75,7 +75,7 @@ object CrashPatchConfig : Config("crashpatch.json", "/assets/crashpatch/crashpat text = "Discord" ) fun supportDiscord() { - UDesktop.browse(URI.create("https://polyfrost.org/discord")) + OmniDesktop.browse(URI.create("https://polyfrost.org/discord")) } enum class UploadMethod(val text: String) { diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt index 0d139e7..945ab9d 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt @@ -1,7 +1,7 @@ package org.polyfrost.crashpatch.crashes -import org.polyfrost.universal.wrappers.message.UTextComponent import com.google.gson.JsonObject +import dev.deftu.textile.minecraft.MinecraftTextFormat import org.apache.logging.log4j.LogManager import org.polyfrost.crashpatch.CrashPatch import org.polyfrost.oneconfig.utils.v1.JsonUtils @@ -109,7 +109,7 @@ object CrashScanStorage { val causeJson = cause.asJsonObject var theReport = report if (causeJson.has("unformatted") && causeJson["unformatted"].asBoolean) { - theReport = UTextComponent.stripFormatting(theReport) + theReport = MinecraftTextFormat.strip(theReport) } when (causeJson["method"].asString) { diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt index 7030da0..3bd17f5 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt @@ -1,6 +1,8 @@ package org.polyfrost.crashpatch.gui import dev.deftu.clipboard.Clipboard +import dev.deftu.omnicore.client.OmniDesktop +import dev.deftu.omnicore.client.OmniScreen import net.minecraft.client.gui.GuiScreen import net.minecraft.crash.CrashReport import org.polyfrost.crashpatch.CrashPatch @@ -25,8 +27,6 @@ import org.polyfrost.polyui.unit.Vec2 import org.polyfrost.polyui.unit.seconds import org.polyfrost.polyui.utils.image import org.polyfrost.polyui.utils.mapToArray -import org.polyfrost.universal.UDesktop -import org.polyfrost.universal.UScreen import java.io.File import java.net.URI import java.util.function.Consumer @@ -166,7 +166,7 @@ class CrashUI @JvmOverloads constructor( val link = UploadUtils.upload(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) Clipboard.getInstance().setString(link) - if (UDesktop.browse(URI.create(link))) { + if (OmniDesktop.browse(URI.create(link))) { Notifications.enqueue(Notifications.Type.Success, CrashPatch.NAME, "Link copied to clipboard and opened in browser") } else { Notifications.enqueue(Notifications.Type.Warning, CrashPatch.NAME, "Couldn't open link in browser, copied to clipboard instead.") @@ -208,7 +208,7 @@ class CrashUI @JvmOverloads constructor( Image("/assets/crashpatch/discord.svg".image(), size = Vec2(28f, 28f)), Text("crashpatch.link.discord.polyfrost", fontSize = 16f).setPalette { brand.fg }.padded(4f, 0f, 0f, 0f), ).onClick { - UDesktop.browse(URI.create("crashpatch.link.discord.polyfrost")) + OmniDesktop.browse(URI.create("crashpatch.link.discord.polyfrost")) true }, @@ -218,7 +218,7 @@ class CrashUI @JvmOverloads constructor( if (type == GuiType.INIT) { shouldCrash = true } else { - UScreen.displayScreen(null) + OmniScreen.closeScreen() } }.setPalette { brand.fg }, Button( @@ -226,7 +226,7 @@ class CrashUI @JvmOverloads constructor( rightImage = "/assets/crashpatch/open-external.svg".image(), padding = Vec2(14f, 14f) ).onClick { - file?.let { UDesktop.open(it) } + file?.let { OmniDesktop.open(it) } true }.setPalette { createCustomButtonPalette(rgba(21, 21, 21)) }, ).padded(0f, 32f, 0f, 0f), From ec25c9a4b22fe2121eff717f6e9d6300424e03cf Mon Sep 17 00:00:00 2001 From: ev chang Date: Sun, 16 Mar 2025 21:46:06 +0900 Subject: [PATCH 22/37] fix refmap --- src/main/resources/mixins.crashpatch.json | 34 +++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/resources/mixins.crashpatch.json b/src/main/resources/mixins.crashpatch.json index 4ce774f..59d1764 100644 --- a/src/main/resources/mixins.crashpatch.json +++ b/src/main/resources/mixins.crashpatch.json @@ -1,19 +1,19 @@ { - "compatibilityLevel": "JAVA_8", - "minVersion": "0.7", - "package": "org.polyfrost.crashpatch.mixin", - "plugin": "org.polyfrost.crashpatch.plugin.MixinPlugin", - "refmap": "mixin.crashpatch.refmap.json", - "injectors": { - "maxShiftBy": 5 - }, - "mixins": [ - "AccessorGuiDisconnected", - "MixinCrashReport", - "MixinCrashReportCategory", - "MixinGuiConnecting", - "MixinMinecraft", - "MixinWorldRenderer" - ], - "verbose": true + "compatibilityLevel": "JAVA_8", + "minVersion": "0.7", + "package": "org.polyfrost.crashpatch.mixin", + "plugin": "org.polyfrost.crashpatch.plugin.MixinPlugin", + "refmap": "mixins.crashpatch.refmap.json", + "injectors": { + "maxShiftBy": 5 + }, + "mixins": [ + "AccessorGuiDisconnected", + "MixinCrashReport", + "MixinCrashReportCategory", + "MixinGuiConnecting", + "MixinMinecraft", + "MixinWorldRenderer" + ], + "verbose": true } \ No newline at end of file From f0f5d6950ee98c4bec2c0153747a07a60ffb2a77 Mon Sep 17 00:00:00 2001 From: ev chang Date: Tue, 18 Mar 2025 23:19:34 +0900 Subject: [PATCH 23/37] update to new textile --- build.gradle.kts | 2 +- .../org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java | 4 ++-- src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt | 6 +++--- .../org/polyfrost/crashpatch/crashes/CrashScanStorage.kt | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b9d52ca..0714f54 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,7 @@ plugins { toolkitLoomHelper { useOneConfig { - version = "1.0.0-alpha.72" + version = "1.0.0-alpha.77" loaderVersion = "1.1.0-alpha.44" usePolyMixin = true diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java index 79e270c..51f06cd 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -1,7 +1,7 @@ package org.polyfrost.crashpatch.mixin; import dev.deftu.omnicore.client.OmniDesktop; -import dev.deftu.textile.minecraft.MinecraftTextFormat; +import dev.deftu.textile.minecraft.MCTextFormat; import org.polyfrost.crashpatch.CrashPatch; import org.polyfrost.crashpatch.hooks.MinecraftHook; import net.minecraft.client.Minecraft; @@ -30,7 +30,7 @@ private void drawWarningText(int mouseX, int mouseY, float partialTicks, Callbac @Unique private String crashpatch$getText() { - return MinecraftTextFormat.RED + "If Minecraft is stuck on this screen, please force close the game" + (CrashPatch.INSTANCE.isSkyclient() ? " and go to https://discord.gg/eh7tNFezct for support" : "") + "."; + return MCTextFormat.RED + "If Minecraft is stuck on this screen, please force close the game" + (CrashPatch.INSTANCE.isSkyclient() ? " and go to https://discord.gg/eh7tNFezct for support" : "") + "."; } @Override diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index 47c22d7..f6e37d1 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -9,7 +9,7 @@ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent //#endif import dev.deftu.omnicore.client.OmniClientCommands -import dev.deftu.textile.minecraft.MinecraftTextFormat +import dev.deftu.textile.minecraft.MCTextFormat import java.io.File import org.apache.logging.log4j.LogManager @@ -77,9 +77,9 @@ object CrashPatch } command.then(CommandManager.literal("reload").executes { ctx -> if (CrashScanStorage.downloadJson()) { - ctx.source.showMessage("${MinecraftTextFormat.GREEN}[CrashPatch] Successfully reloaded JSON file!") + ctx.source.displayMessage("${MCTextFormat.GREEN}[CrashPatch] Successfully reloaded JSON file!") } else { - ctx.source.showMessage("${MinecraftTextFormat.RED}[CrashPatch] Failed to reload the JSON file!") + ctx.source.displayMessage("${MCTextFormat.RED}[CrashPatch] Failed to reload the JSON file!") } 1 }) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt index 945ab9d..86515f7 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt @@ -1,7 +1,7 @@ package org.polyfrost.crashpatch.crashes import com.google.gson.JsonObject -import dev.deftu.textile.minecraft.MinecraftTextFormat +import dev.deftu.textile.minecraft.MCTextFormat import org.apache.logging.log4j.LogManager import org.polyfrost.crashpatch.CrashPatch import org.polyfrost.oneconfig.utils.v1.JsonUtils @@ -109,7 +109,7 @@ object CrashScanStorage { val causeJson = cause.asJsonObject var theReport = report if (causeJson.has("unformatted") && causeJson["unformatted"].asBoolean) { - theReport = MinecraftTextFormat.strip(theReport) + theReport = MCTextFormat.strip(theReport) } when (causeJson["method"].asString) { From 8a67fb89242dce0ec23bfef74eab0764dbee10fc Mon Sep 17 00:00:00 2001 From: ev chang Date: Tue, 18 Mar 2025 23:31:48 +0900 Subject: [PATCH 24/37] update DGT --- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1672508..b5fe3c7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip diff --git a/settings.gradle.kts b/settings.gradle.kts index 0add377..b813573 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,7 +24,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.22.0") + id("dev.deftu.gradle.multiversion-root") version("2.28.0") } } From c4bc9da589a679c9559a5898c07214034b3ae78a Mon Sep 17 00:00:00 2001 From: Deftu Date: Mon, 5 May 2025 20:42:42 +0200 Subject: [PATCH 25/37] Bump DGT and OneConfig --- build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0714f54..13ccbc0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,7 +17,7 @@ plugins { toolkitLoomHelper { useOneConfig { - version = "1.0.0-alpha.77" + version = "1.0.0-alpha.78" loaderVersion = "1.1.0-alpha.44" usePolyMixin = true diff --git a/settings.gradle.kts b/settings.gradle.kts index b813573..14908cd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,7 +24,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.28.0") + id("dev.deftu.gradle.multiversion-root") version("2.34.0") } } From a0d816dcdb6c6c59acd69acc05aab9c7cd779785 Mon Sep 17 00:00:00 2001 From: Deftu Date: Mon, 5 May 2025 20:43:05 +0200 Subject: [PATCH 26/37] Better command syntax --- .../org/polyfrost/crashpatch/CrashPatch.kt | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt index f6e37d1..dc9248d 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt @@ -9,6 +9,7 @@ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent //#endif import dev.deftu.omnicore.client.OmniClientCommands +import dev.deftu.textile.minecraft.MCSimpleTextHolder import dev.deftu.textile.minecraft.MCTextFormat import java.io.File @@ -70,24 +71,28 @@ object CrashPatch fun initialize() { OmniClientCommands.initialize() - val command = CommandManager.literal(ID) - command.executes { - CrashPatchConfig.openUI() - 1 - } - command.then(CommandManager.literal("reload").executes { ctx -> - if (CrashScanStorage.downloadJson()) { - ctx.source.displayMessage("${MCTextFormat.GREEN}[CrashPatch] Successfully reloaded JSON file!") - } else { - ctx.source.displayMessage("${MCTextFormat.RED}[CrashPatch] Failed to reload the JSON file!") + CommandManager.register(with(CommandManager.literal(ID)) { + executes { + CrashPatchConfig.openUI() + 1 } - 1 - }) - command.then(CommandManager.literal("crash").executes { ctx -> - requestedCrash = true - 1 + + then(CommandManager.literal("reload").executes { ctx -> + val text = if (CrashScanStorage.downloadJson()) { + MCSimpleTextHolder("[${NAME}] Successfully reloaded JSON file!").withFormatting(MCTextFormat.GREEN) + } else { + MCSimpleTextHolder("[${NAME}] Failed to reload JSON file!").withFormatting(MCTextFormat.RED) + } + + ctx.source.displayMessage(text) + 1 + }) + + then(CommandManager.literal("crash").executes { ctx -> + requestedCrash = true + 1 + }) }) - CommandManager.register(command) CrashPatchConfig // Initialize the config } From 7f1271919410a68f9f9e1c97c2b9c2a728883f33 Mon Sep 17 00:00:00 2001 From: Deftu Date: Wed, 4 Jun 2025 11:25:04 +0200 Subject: [PATCH 27/37] Bump OneConfig and OneConfig Loader --- build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 13ccbc0..40919fe 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,8 +17,8 @@ plugins { toolkitLoomHelper { useOneConfig { - version = "1.0.0-alpha.78" - loaderVersion = "1.1.0-alpha.44" + version = "1.0.0-alpha.106" + loaderVersion = "1.1.0-alpha.46" usePolyMixin = true polyMixinVersion = "0.8.4+build.2" From 9c52bb3e41a6b0eeb3b5876bd9f82853b5ae5176 Mon Sep 17 00:00:00 2001 From: ev chang Date: Thu, 26 Jun 2025 03:34:12 +0700 Subject: [PATCH 28/37] Update settings.gradle.kts --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index b813573..4193427 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,7 +24,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.28.0") + id("dev.deftu.gradle.multiversion-root") version("2.30.0") } } From 726633ca7b9b9a08a9a0c70ab1fd75c0b8a0cbd9 Mon Sep 17 00:00:00 2001 From: ev chang Date: Thu, 26 Jun 2025 03:34:40 +0700 Subject: [PATCH 29/37] workflows --- .github/workflows/{gradle.yml => build.yml} | 10 +++++++--- .github/workflows/mod_project_integration.yml | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) rename .github/workflows/{gradle.yml => build.yml} (84%) create mode 100644 .github/workflows/mod_project_integration.yml diff --git a/.github/workflows/gradle.yml b/.github/workflows/build.yml similarity index 84% rename from .github/workflows/gradle.yml rename to .github/workflows/build.yml index 62008db..d4a420f 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/build.yml @@ -23,11 +23,15 @@ jobs: with: fetch-depth: 10 - - name: Set up JDK 17 + - name: Set up JDK 8, 16, 17, 21 uses: actions/setup-java@v4 with: - java-version: 21 distribution: temurin + java-version: | + 8 + 16 + 17 + 21 - uses: actions/cache@v4 with: @@ -44,4 +48,4 @@ jobs: run: chmod +x ./gradlew - name: Build - run: ./gradlew build --no-daemon \ No newline at end of file + run: ./gradlew build --no-daemon diff --git a/.github/workflows/mod_project_integration.yml b/.github/workflows/mod_project_integration.yml new file mode 100644 index 0000000..ddd613b --- /dev/null +++ b/.github/workflows/mod_project_integration.yml @@ -0,0 +1,17 @@ +name: Mod-Project Integration + +on: + issues: + types: [opened] + pull_request: + types: [opened] + +jobs: + add-to-project: + name: Add issue/PR to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v1.0.2 + with: + project-url: https://github.com/orgs/Polyfrost/projects/9 + github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} \ No newline at end of file From 69e4fef12e0f24508d41cffa96128c53d70092b1 Mon Sep 17 00:00:00 2001 From: Wyvest Date: Fri, 15 Aug 2025 12:28:28 +0900 Subject: [PATCH 30/37] 1.12 port --- build.gradle.kts | 8 +- gradle/wrapper/gradle-wrapper.properties | 2 +- root.gradle.kts | 8 +- settings.gradle.kts | 4 +- .../hooks/StacktraceDeobfuscator.java | 9 +- .../crashpatch/mixin/MixinGuiDupesFound.java | 24 ++- .../crashpatch/mixin/MixinMinecraft.java | 101 +++------ .../MixinTileEntityRendererDispatcher.java | 12 ++ .../polyfrost/crashpatch/CrashPatchConfig.kt | 15 +- .../crashpatch/crashes/CrashScanStorage.kt | 2 +- .../org/polyfrost/crashpatch/gui/CrashUI.kt | 6 +- .../org/polyfrost/crashpatch/utils/GlUtil.kt | 64 ++++++ .../org/polyfrost/crashpatch/utils/GlUtil.kt | 204 ++++++++++++++++++ 13 files changed, 363 insertions(+), 96 deletions(-) create mode 100644 src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt create mode 100644 versions/1.12.2-fabric/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt diff --git a/build.gradle.kts b/build.gradle.kts index 40919fe..e530fee 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,15 +17,15 @@ plugins { toolkitLoomHelper { useOneConfig { - version = "1.0.0-alpha.106" - loaderVersion = "1.1.0-alpha.46" + version = "1.0.0-alpha.134" + loaderVersion = "1.1.0-alpha.48" usePolyMixin = true - polyMixinVersion = "0.8.4+build.2" + polyMixinVersion = "0.8.4+build.6" applyLoaderTweaker = true - for (module in arrayOf("commands", "config", "config-impl", "events", "internal", "ui", "utils")) { + for (module in arrayOf("commands", "config", "config-impl", "events", "internal", "hud", "ui", "utils")) { +module } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b5fe3c7..44aaae0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip diff --git a/root.gradle.kts b/root.gradle.kts index 1873883..b9bcae5 100644 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -10,8 +10,12 @@ preprocess { // "1.8.9-forge"(10809, "srg") // } - "1.8.9-forge"(10809, "srg") { - "1.8.9-fabric"(10809, "yarn") + "1.12.2-forge"(11202, "srg") { + "1.12.2-fabric"(11202, "yarn") { + "1.8.9-fabric"(10809, "yarn") { + "1.8.9-forge"(10809, "srg") + } + } } strictExtraMappings.set(true) diff --git a/settings.gradle.kts b/settings.gradle.kts index 14908cd..ab66608 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,7 +24,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.34.0") + id("dev.deftu.gradle.multiversion-root") version("2.49.0") } } @@ -42,6 +42,8 @@ rootProject.buildFileName = "root.gradle.kts" listOf( "1.8.9-forge", "1.8.9-fabric", + "1.12.2-forge", + "1.12.2-fabric" ).forEach { version -> include(":$version") project(":$version").apply { diff --git a/src/main/java/org/polyfrost/crashpatch/hooks/StacktraceDeobfuscator.java b/src/main/java/org/polyfrost/crashpatch/hooks/StacktraceDeobfuscator.java index 5aa19d9..d52f928 100644 --- a/src/main/java/org/polyfrost/crashpatch/hooks/StacktraceDeobfuscator.java +++ b/src/main/java/org/polyfrost/crashpatch/hooks/StacktraceDeobfuscator.java @@ -8,6 +8,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +//TODO rewrite for fabric public class StacktraceDeobfuscator { public static final StacktraceDeobfuscator INSTANCE = new StacktraceDeobfuscator(); @@ -21,7 +22,13 @@ private StacktraceDeobfuscator() { if (!mappings.exists()) { HttpURLConnection connection = null; try { - URL mappingsURL = new URL("https://maven.minecraftforge.net/de/oceanlabs/mcp/mcp_stable_nodoc/22-1.8.9/mcp_stable_nodoc-22-1.8.9.zip"); + URL mappingsURL = new URL( + //#if MC==1.8.9 + "https://maven.minecraftforge.net/de/oceanlabs/mcp/mcp_stable_nodoc/22-1.8.9/mcp_stable_nodoc-22-1.8.9.zip" + //#else + //$$ "http://export.mcpbot.bspk.rs/mcp_stable_nodoc/39-1.12/mcp_stable_nodoc-39-1.12.zip" + //#endif + ); connection = (HttpURLConnection) mappingsURL.openConnection(); connection.setDoInput(true); connection.connect(); diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java index 58df291..a32a8d6 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java @@ -1,12 +1,12 @@ package org.polyfrost.crashpatch.mixin; //#if FORGE +import dev.deftu.omnicore.client.OmniClient; import dev.deftu.omnicore.client.OmniDesktop; +import dev.deftu.textile.minecraft.MCTextFormat; import org.polyfrost.crashpatch.CrashPatch; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiErrorScreen; -import net.minecraft.util.EnumChatFormatting; import net.minecraftforge.fml.client.GuiDupesFound; import net.minecraftforge.fml.common.DuplicateModsFoundException; import net.minecraftforge.fml.common.FMLCommonHandler; @@ -32,8 +32,14 @@ public MixinGuiDupesFound() { super(null, null); } + //#if MC < 1.12 @Inject(method = "initGui", at = @At("RETURN")) private void onInit(CallbackInfo ci) { + //#else + //$$ @Override + //$$ public void initGui() { + //$$ super.initGui(); + //#endif this.buttonList.add(new GuiButton(0, width / 2 - 100, height - 50, "Open Folder")); this.buttonList.add(new GuiButton(1, width / 2 - 100, height - 30, "Quit Game")); } @@ -64,10 +70,14 @@ private void onDrawScreen(int mouseX, int mouseY, float partialTicks, CallbackIn offset += 10; - crashpatch$drawSplitString(EnumChatFormatting.BOLD + "To fix this, go into your mods folder by clicking the button below or going to " + CrashPatch.getMcDir().getAbsolutePath() + " and deleting the duplicate mods.", width / 2, offset, width, Color.BLUE.getRGB()); + crashpatch$drawSplitString(MCTextFormat.BOLD + "To fix this, go into your mods folder by clicking the button below or going to " + CrashPatch.getMcDir().getAbsolutePath() + " and deleting the duplicate mods.", width / 2, offset, width, Color.BLUE.getRGB()); for (GuiButton guiButton : this.buttonList) { - guiButton.drawButton(this.mc, mouseX, mouseY); + guiButton.drawButton(this.mc, mouseX, mouseY + //#if MC >= 1.12 + //$$ , partialTicks + //#endif + ); } for (net.minecraft.client.gui.GuiLabel guiLabel : this.labelList) { guiLabel.drawLabel(this.mc, mouseX, mouseY); @@ -78,9 +88,9 @@ private void onDrawScreen(int mouseX, int mouseY, float partialTicks, CallbackIn private static int crashpatch$drawSplitString(String str, int x, int y, int wrapWidth, int textColor) { str = crashpatch$trimStringNewline(str); int y2 = y; - for (String s : Minecraft.getMinecraft().fontRendererObj.listFormattedStringToWidth(str, wrapWidth)) { - Minecraft.getMinecraft().fontRendererObj.drawStringWithShadow(s, (float) (x - Minecraft.getMinecraft().fontRendererObj.getStringWidth(s) / 2), (float) y2, textColor); - y2 += Minecraft.getMinecraft().fontRendererObj.FONT_HEIGHT; + for (String s : OmniClient.getFontRenderer().listFormattedStringToWidth(str, wrapWidth)) { + OmniClient.getFontRenderer().drawStringWithShadow(s, (float) (x - OmniClient.getFontRenderer().getStringWidth(s) / 2), (float) y2, textColor); + y2 += OmniClient.getFontRenderer().FONT_HEIGHT; } return y2 - y; } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index 1a4d493..bf437b8 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -15,7 +15,6 @@ import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.client.renderer.EntityRenderer; import net.minecraft.client.renderer.GlStateManager; -import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.resources.IReloadableResourceManager; import net.minecraft.client.resources.LanguageManager; @@ -32,19 +31,14 @@ import org.lwjgl.LWJGLException; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.Display; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL12; -import org.lwjgl.opengl.GL14; import org.polyfrost.crashpatch.CrashPatch; import org.polyfrost.crashpatch.CrashPatchConfig; import org.polyfrost.crashpatch.crashes.StateManager; import org.polyfrost.crashpatch.gui.CrashUI; import org.polyfrost.crashpatch.hooks.MinecraftHook; +import org.polyfrost.crashpatch.utils.GlUtil; import org.polyfrost.crashpatch.utils.GuiDisconnectedHook; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; @@ -111,9 +105,6 @@ public abstract class MixinMinecraft implements MinecraftHook { @Shadow protected abstract void runGameLoop(); - @Shadow - public abstract void freeMemory(); - @Shadow public abstract void shutdownMinecraftApplet(); @@ -248,10 +239,14 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { crashReport.getCategory().addCrashSectionCallable("Integrated Server Crashes Since Restart", () -> String.valueOf(crashpatch$serverCrashCount)); } + public void crashpatch$resetGameState() { + crashpatch$resetGameState(false); + } + /** * @author Runemoro */ - public void crashpatch$resetGameState() { + public void crashpatch$resetGameState(boolean freeingMemory) { try { // Free up memory such that this works properly in case of an OutOfMemoryError int originalMemoryReserveSize = -1; @@ -265,9 +260,18 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { StateManager.INSTANCE.resetStates(); - if (crashpatch$clientCrashCount >= CrashPatchConfig.INSTANCE.getLeaveLimit() || crashpatch$serverCrashCount >= CrashPatchConfig.INSTANCE.getLeaveLimit()) { + boolean shouldCrash = crashpatch$clientCrashCount >= CrashPatchConfig.INSTANCE.getLeaveLimit() || crashpatch$serverCrashCount >= CrashPatchConfig.INSTANCE.getLeaveLimit(); + + if (shouldCrash && !freeingMemory) { this.logger.error("Crash limit reached, exiting world"); CrashUI.Companion.setLeaveWorldCrash(true); + } + + if (shouldCrash || freeingMemory + //#if MC > 1.12 + //$$ || true + //#endif + ) { if (getNetHandler() != null) { getNetHandler().getNetworkManager().closeChannel(new ChatComponentText("[CrashPatch] Client crashed")); } @@ -280,7 +284,7 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { this.scheduledTasks.clear(); // TODO: Figure out why this isn't necessary for vanilla disconnect } - crashpatch$resetState(); + GlUtil.INSTANCE.resetState(); if (originalMemoryReserveSize != -1) { try { @@ -293,11 +297,25 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { this.logger.error("Failed to reset state after a crash", t); try { StateManager.INSTANCE.resetStates(); - crashpatch$resetState(); + GlUtil.INSTANCE.resetState(); } catch (Throwable ignored) {} } } + /** + * @reason Disconnect from the current world and free memory, using a memory reserve + * to make sure that an OutOfMemory doesn't happen while doing this. + *

+ * Bugs Fixed: + * - https://bugs.mojang.com/browse/MC-128953 + * - Memory reserve not recreated after out-of memory + * @author Runemoro + */ + @Overwrite + public void freeMemory() { + crashpatch$resetGameState(true); + } + /** * @author Runemoro */ @@ -417,59 +435,6 @@ public void redirect(FMLCommonHandler instance, int code) { } //#endif - @Unique - private void crashpatch$resetState() { - GlStateManager.bindTexture(0); - GlStateManager.disableTexture2D(); - - // Reset depth - GlStateManager.disableDepth(); - GlStateManager.depthFunc(513); - GlStateManager.depthMask(true); - - // Reset blend mode - GlStateManager.disableBlend(); - GlStateManager.blendFunc(1, 0); - GlStateManager.tryBlendFuncSeparate(1, 0, 1, 0); - GL14.glBlendEquation(GL14.GL_FUNC_ADD); - - // Reset polygon offset - GlStateManager.doPolygonOffset(0.0F, 0.0F); - GlStateManager.disablePolygonOffset(); - - // Reset color logic - GlStateManager.disableColorLogic(); - GlStateManager.colorLogicOp(5379); - - // Disable lightmap - GlStateManager.setActiveTexture(OpenGlHelper.lightmapTexUnit); - GlStateManager.disableTexture2D(); - - GlStateManager.setActiveTexture(OpenGlHelper.defaultTexUnit); - - // Reset texture parameters - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_LINEAR); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 1000); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LOD, 1000); - GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MIN_LOD, -1000); - GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 0.0F); - - GlStateManager.colorMask(true, true, true, true); - GlStateManager.clearDepth(1.0D); - GL11.glLineWidth(1.0F); - GL11.glNormal3f(0.0F, 0.0F, 1.0F); - GL11.glPolygonMode(GL11.GL_FRONT, GL11.GL_FILL); - GL11.glPolygonMode(GL11.GL_BACK, GL11.GL_FILL); - GlStateManager.enableTexture2D(); - GlStateManager.clearDepth(1.0D); - GlStateManager.enableDepth(); - GlStateManager.depthFunc(515); - GlStateManager.enableCull(); - GL11.glDisable(GL11.GL_SCISSOR_TEST); - } } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java index df04c12..9896758 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java @@ -17,8 +17,13 @@ @Mixin(TileEntityRendererDispatcher.class) public abstract class MixinTileEntityRendererDispatcher implements StateManager.IResettable { + //#if MC < 1.12 @Unique private boolean crashpatch$drawingBatch = false; + //#else + //$$ @org.spongepowered.asm.mixin.Shadow + //$$ private boolean drawingBatch; + //#endif @Inject(method = "", at = @At(value = "RETURN")) public void onInit(CallbackInfo ci) { @@ -27,9 +32,15 @@ public void onInit(CallbackInfo ci) { @Override public void resetState() { + //#if MC < 1.12 if (crashpatch$drawingBatch) crashpatch$drawingBatch = false; + //#else + //$$ if (drawingBatch) drawingBatch = false; + //#endif } + //#if MC < 1.12 + @Redirect(method = "renderTileEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/tileentity/TileEntity;hasFastRenderer()Z")) private boolean isNotFastRenderOrDrawing(TileEntity instance) { if (!crashpatch$drawingBatch) { @@ -54,5 +65,6 @@ private void setDrawingBatchFalse(int pass, CallbackInfo ci) { crashpatch$drawingBatch = false; } + //#endif } //#endif diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt index 9b48611..c7def60 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt @@ -29,14 +29,13 @@ object CrashPatchConfig : Config("crashpatch.json", "/assets/crashpatch/crashpat ) var disconnectCrashPatch = true - // Limits - //@Info( // todo - // text = "It's recommended to leave the world after a few crashes, and outright quit the game if there are more; this is to avoid severe instability", - // type = InfoType.WARNING, - // size = 2, - // subcategory = "Limits" - //) - //var ignored: Boolean = false + @Info( + title = "polyui.warning", + description = "It's recommended to leave the world after a few crashes, and outright quit the game if there are more; this is to avoid severe instability", + icon = "polyui/warning.svg", + subcategory = "Limits" + ) + var ignored: Boolean = false @Slider( title = "World Leave Limit", diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt index 86515f7..40f2f48 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt @@ -27,7 +27,7 @@ object CrashScanStorage { @JvmStatic fun downloadJson(): Boolean { return try { - skyclientData = JsonUtils.parseFromUrl("https://raw.githubusercontent.com/SkyblockClient/CrashData/main/crashes.json") + skyclientData = JsonUtils.parseFromUrl("https://raw.githubusercontent.com/Polyfrost/CrashData/main/crashes.json") ?.asJsonObject ?: return false cacheFile.writeText(skyclientData.toString()) true diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt index 3bd17f5..fb6b225 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt @@ -179,7 +179,7 @@ class CrashUI @JvmOverloads constructor( alignment = Align( pad = Vec2.ZERO, - main = Align.Main.SpaceBetween, + main = Align.Content.SpaceBetween, mode = Align.Mode.Horizontal ), size = Vec2(550f, 37f), @@ -194,8 +194,8 @@ class CrashUI @JvmOverloads constructor( ).padded(16f, 8f), alignment = Align( - main = Align.Main.Start, - cross = Align.Cross.Start, + main = Align.Content.Start, + cross = Align.Content.Start, mode = Align.Mode.Vertical, pad = Vec2.ZERO ), diff --git a/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt new file mode 100644 index 0000000..1e72836 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt @@ -0,0 +1,64 @@ +package org.polyfrost.crashpatch.utils + +import net.minecraft.client.renderer.GlStateManager +import net.minecraft.client.renderer.OpenGlHelper +import org.lwjgl.opengl.GL11 +import org.lwjgl.opengl.GL12 +import org.lwjgl.opengl.GL14 + +// Replaced in 1.12.2 source set +object GlUtil { + + fun resetState() { + GlStateManager.bindTexture(0) + GlStateManager.disableTexture2D() + + // Reset depth + GlStateManager.disableDepth() + GlStateManager.depthFunc(513) + GlStateManager.depthMask(true) + + // Reset blend mode + GlStateManager.disableBlend() + GlStateManager.blendFunc(1, 0) + GlStateManager.tryBlendFuncSeparate(1, 0, 1, 0) + GL14.glBlendEquation(GL14.GL_FUNC_ADD) + + // Reset polygon offset + GlStateManager.doPolygonOffset(0.0f, 0.0f) + GlStateManager.disablePolygonOffset() + + // Reset color logic + GlStateManager.disableColorLogic() + GlStateManager.colorLogicOp(5379) + + // Disable lightmap + GlStateManager.setActiveTexture(OpenGlHelper.lightmapTexUnit) + GlStateManager.disableTexture2D() + + GlStateManager.setActiveTexture(OpenGlHelper.defaultTexUnit) + + // Reset texture parameters + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR) + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_LINEAR) + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT) + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT) + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 1000) + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LOD, 1000) + GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MIN_LOD, -1000) + GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 0.0f) + + GlStateManager.colorMask(true, true, true, true) + GlStateManager.clearDepth(1.0) + GL11.glLineWidth(1.0f) + GL11.glNormal3f(0.0f, 0.0f, 1.0f) + GL11.glPolygonMode(GL11.GL_FRONT, GL11.GL_FILL) + GL11.glPolygonMode(GL11.GL_BACK, GL11.GL_FILL) + GlStateManager.enableTexture2D() + GlStateManager.clearDepth(1.0) + GlStateManager.enableDepth() + GlStateManager.depthFunc(515) + GlStateManager.enableCull() + GL11.glDisable(GL11.GL_SCISSOR_TEST) + } +} \ No newline at end of file diff --git a/versions/1.12.2-fabric/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt b/versions/1.12.2-fabric/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt new file mode 100644 index 0000000..4e7f927 --- /dev/null +++ b/versions/1.12.2-fabric/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt @@ -0,0 +1,204 @@ +package org.polyfrost.crashpatch.utils + +import com.mojang.blaze3d.platform.GLX +import com.mojang.blaze3d.platform.GlStateManager +import net.minecraft.client.render.DiffuseLighting +import org.lwjgl.opengl.* + +// I hate Legacy Yarn mappings.. +// This should be better once we switch to Ornithe/Feather mappings, but +// if you want to see the original MCP code, go to: +// https://github.com/DimensionalDevelopment/VanillaFix/blob/master/src/main/java/org/dimdev/utils/GlUtil.java +object GlUtil { + + fun resetState() { + + // Clear matrix stack + GlStateManager.matrixMode(GL11.GL_MODELVIEW) + GlStateManager.loadIdentity() + GlStateManager.matrixMode(GL11.GL_PROJECTION) + GlStateManager.loadIdentity() + GlStateManager.matrixMode(GL11.GL_TEXTURE) + GlStateManager.loadIdentity() + GlStateManager.matrixMode(GL11.GL_COLOR) + GlStateManager.loadIdentity() + + + // Clear attribute stacks TODO: Broken, a stack underflow breaks LWJGL + // try { + // do GL11.glPopAttrib(); while (GlStateManager.glGetError() == 0); + // } catch (Throwable ignored) {} + // + // try { + // do GL11.glPopClientAttrib(); while (GlStateManager.glGetError() == 0); + // } catch (Throwable ignored) {} + + // Reset texture + GlStateManager.bindTexture(0) + GlStateManager.disableTexture() + + + // Reset GL lighting + GlStateManager.disableLighting() + GlStateManager.method_12282(GL11.GL_LIGHT_MODEL_AMBIENT, DiffuseLighting.method_845(0.2f, 0.2f, 0.2f, 1.0f)) + for (i in 0..7) { + GlStateManager.disableLight(i) + GlStateManager.method_12281( + GL11.GL_LIGHT0 + i, + GL11.GL_AMBIENT, + DiffuseLighting.method_845(0.0f, 0.0f, 0.0f, 1.0f) + ) + GlStateManager.method_12281( + GL11.GL_LIGHT0 + i, + GL11.GL_POSITION, + DiffuseLighting.method_845(0.0f, 0.0f, 1.0f, 0.0f) + ) + + if (i == 0) { + GlStateManager.method_12281( + GL11.GL_LIGHT0 + i, + GL11.GL_DIFFUSE, + DiffuseLighting.method_845(1.0f, 1.0f, 1.0f, 1.0f) + ) + GlStateManager.method_12281( + GL11.GL_LIGHT0 + i, + GL11.GL_SPECULAR, + DiffuseLighting.method_845(1.0f, 1.0f, 1.0f, 1.0f) + ) + } else { + GlStateManager.method_12281( + GL11.GL_LIGHT0 + i, + GL11.GL_DIFFUSE, + DiffuseLighting.method_845(0.0f, 0.0f, 0.0f, 1.0f) + ) + GlStateManager.method_12281( + GL11.GL_LIGHT0 + i, + GL11.GL_SPECULAR, + DiffuseLighting.method_845(0.0f, 0.0f, 0.0f, 1.0f) + ) + } + } + GlStateManager.disableColorMaterial() + GlStateManager.colorMaterial(1032, 5634) + + + // Reset depth + GlStateManager.disableDepthTest() + GlStateManager.depthFunc(513) + GlStateManager.depthMask(true) + + + // Reset blend mode + GlStateManager.disableBlend() + GlStateManager.method_12287(GlStateManager.class_2870.ONE, GlStateManager.class_2866.ZERO) + GlStateManager.method_12288( + GlStateManager.class_2870.ONE, + GlStateManager.class_2866.ZERO, + GlStateManager.class_2870.ONE, + GlStateManager.class_2866.ZERO + ) + GlStateManager.method_12305(GL14.GL_FUNC_ADD) + + + // Reset fog + GlStateManager.disableFog() + GlStateManager.method_12285(GlStateManager.class_2867.LINEAR) + GlStateManager.fogDensity(1.0f) + GlStateManager.fogStart(0.0f) + GlStateManager.fogEnd(1.0f) + GlStateManager.method_12298(GL11.GL_FOG_COLOR, DiffuseLighting.method_845(0.0f, 0.0f, 0.0f, 0.0f)) + if (GLContext.getCapabilities().GL_NV_fog_distance) GlStateManager.method_12300(GL11.GL_FOG_MODE, 34140) + + + // Reset polygon offset + GlStateManager.polygonOffset(0.0f, 0.0f) + GlStateManager.disablePolyOffset() + + + // Reset color logic + GlStateManager.disableColorLogic() + GlStateManager.logicOp(5379) + + + // Reset texgen TODO: is this correct? + GlStateManager.disableTexCoord(GlStateManager.TexCoord.S) + GlStateManager.disableTexCoord(GlStateManager.TexCoord.T) + GlStateManager.disableTexCoord(GlStateManager.TexCoord.R) + GlStateManager.disableTexCoord(GlStateManager.TexCoord.Q) + GlStateManager.genTex(GlStateManager.TexCoord.S, 9216) + GlStateManager.genTex(GlStateManager.TexCoord.T, 9216) + GlStateManager.genTex(GlStateManager.TexCoord.R, 9216) + GlStateManager.genTex(GlStateManager.TexCoord.Q, 9216) + GlStateManager.genTex(GlStateManager.TexCoord.S, 9474, DiffuseLighting.method_845(1.0f, 0.0f, 0.0f, 0.0f)) + GlStateManager.genTex(GlStateManager.TexCoord.T, 9474, DiffuseLighting.method_845(0.0f, 1.0f, 0.0f, 0.0f)) + GlStateManager.genTex(GlStateManager.TexCoord.R, 9474, DiffuseLighting.method_845(0.0f, 0.0f, 1.0f, 0.0f)) + GlStateManager.genTex(GlStateManager.TexCoord.Q, 9474, DiffuseLighting.method_845(0.0f, 0.0f, 0.0f, 1.0f)) + GlStateManager.genTex(GlStateManager.TexCoord.S, 9217, DiffuseLighting.method_845(1.0f, 0.0f, 0.0f, 0.0f)) + GlStateManager.genTex(GlStateManager.TexCoord.T, 9217, DiffuseLighting.method_845(0.0f, 1.0f, 0.0f, 0.0f)) + GlStateManager.genTex(GlStateManager.TexCoord.R, 9217, DiffuseLighting.method_845(0.0f, 0.0f, 1.0f, 0.0f)) + GlStateManager.genTex(GlStateManager.TexCoord.Q, 9217, DiffuseLighting.method_845(0.0f, 0.0f, 0.0f, 1.0f)) + + + // Disable lightmap + GlStateManager.activeTexture(GLX.lightmapTextureUnit) + GlStateManager.disableTexture() + + GlStateManager.activeTexture(GLX.textureUnit) + + + // Reset texture parameters + GlStateManager.method_12294(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR) + GlStateManager.method_12294(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_LINEAR) + GlStateManager.method_12294(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT) + GlStateManager.method_12294(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT) + GlStateManager.method_12294(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 1000) + GlStateManager.method_12294(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LOD, 1000) + GlStateManager.method_12294(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MIN_LOD, -1000) + GlStateManager.method_12293(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 0.0f) + + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE) + GlStateManager.method_12297( + GL11.GL_TEXTURE_ENV, + GL11.GL_TEXTURE_ENV_COLOR, + DiffuseLighting.method_845(0.0f, 0.0f, 0.0f, 0.0f) + ) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL15.GL_SRC0_RGB, GL11.GL_TEXTURE) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL15.GL_SRC1_RGB, GL13.GL_PREVIOUS) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL15.GL_SRC2_RGB, GL13.GL_CONSTANT) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL15.GL_SRC0_ALPHA, GL11.GL_TEXTURE) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL15.GL_SRC1_ALPHA, GL13.GL_PREVIOUS) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL15.GL_SRC2_ALPHA, GL13.GL_CONSTANT) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_RGB, GL11.GL_SRC_COLOR) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND2_RGB, GL11.GL_SRC_ALPHA) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_ALPHA, GL11.GL_SRC_ALPHA) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND1_ALPHA, GL11.GL_SRC_ALPHA) + GlStateManager.method_12274(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND2_ALPHA, GL11.GL_SRC_ALPHA) + GlStateManager.method_12273(GL11.GL_TEXTURE_ENV, GL13.GL_RGB_SCALE, 1.0f) + GlStateManager.method_12273(GL11.GL_TEXTURE_ENV, GL11.GL_ALPHA_SCALE, 1.0f) + + GlStateManager.disableNormalize() + GlStateManager.shadeModel(7425) + GlStateManager.disableRescaleNormal() + GlStateManager.colorMask(true, true, true, true) + GlStateManager.clearDepth(1.0) + GlStateManager.method_12304(1.0f) + GlStateManager.method_12272(0.0f, 0.0f, 1.0f) + GlStateManager.method_12306(GL11.GL_FRONT, GL11.GL_FILL) + GlStateManager.method_12306(GL11.GL_BACK, GL11.GL_FILL) + + GlStateManager.enableTexture() + GlStateManager.shadeModel(7425) + GlStateManager.clearDepth(1.0) + GlStateManager.enableDepthTest() + GlStateManager.depthFunc(515) + GlStateManager.enableAlphaTest() + GlStateManager.alphaFunc(516, 0.1f) + GlStateManager.method_12284(GlStateManager.class_2865.BACK) + GlStateManager.matrixMode(5889) + GlStateManager.loadIdentity() + GlStateManager.matrixMode(5888) + } +} \ No newline at end of file From 887a40d9de316d48dc1a0590af120d34860c45e9 Mon Sep 17 00:00:00 2001 From: Wyvest Date: Sat, 16 Aug 2025 16:43:56 +0900 Subject: [PATCH 31/37] Make compile on 1.16 Still need to make it override NEC --- build.gradle.kts | 40 +++++++++ root.gradle.kts | 12 ++- settings.gradle.kts | 4 +- .../hooks/StacktraceDeobfuscator.java | 3 + .../mixin/AccessorGuiDisconnected.java | 11 +-- .../crashpatch/mixin/MixinCrashReport.java | 7 +- .../mixin/MixinCrashReportCategory.java | 5 +- .../crashpatch/mixin/MixinGuiConnecting.java | 86 ++----------------- .../crashpatch/mixin/MixinGuiDupesFound.java | 8 +- .../crashpatch/mixin/MixinMinecraft.java | 11 ++- .../mixin/MixinMinecraft_PreInitialize.java | 17 ++++ .../MixinTileEntityRendererDispatcher.java | 2 + .../crashpatch/mixin/MixinWorldRenderer.java | 3 +- .../crashpatch/CrashPatchConstants.kt | 10 +++ .../crashpatch/CrashPatchEntrypoint.kt | 74 ++++++++++++++++ .../CrashPatchClient.kt} | 58 +++---------- .../crashpatch/crashes/CrashScanStorage.kt | 8 +- .../crashes/DeobfuscatingRewritePolicy.kt | 2 + .../crashpatch/crashes/StateManager.kt | 4 +- .../org/polyfrost/crashpatch/gui/CrashUI.kt | 9 +- .../crashpatch/identifier/ModIdentifier.kt | 8 +- .../crashpatch/plugin/MixinPlugin.kt | 2 +- .../org/polyfrost/crashpatch/utils/GlUtil.kt | 2 +- .../crashpatch/utils/GuiDisconnectedHook.kt | 10 ++- .../polyfrost/crashpatch/utils/UploadUtils.kt | 6 +- src/main/resources/fabric.mod.json | 6 +- src/main/resources/mixins.crashpatch.json | 5 +- .../org/polyfrost/crashpatch/utils/GlUtil.kt | 5 +- versions/1.16.5-1.8.9.txt | 1 + .../crashpatch/mixin/MixinGuiConnecting.java | 74 ++++++++++++++++ .../mixin/MixinMinecraft_PreInitialize.java | 18 ++++ .../crashpatch/identifier/ModIdentifier.kt | 16 ++++ 32 files changed, 348 insertions(+), 179 deletions(-) create mode 100644 src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java create mode 100644 src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConstants.kt create mode 100644 src/main/kotlin/org/polyfrost/crashpatch/CrashPatchEntrypoint.kt rename src/main/kotlin/org/polyfrost/crashpatch/{CrashPatch.kt => client/CrashPatchClient.kt} (59%) create mode 100644 versions/1.16.5-1.8.9.txt create mode 100644 versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java create mode 100644 versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java create mode 100644 versions/1.16.5-forge/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt diff --git a/build.gradle.kts b/build.gradle.kts index e530fee..65a3857 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,8 @@ import dev.deftu.gradle.utils.GameSide import dev.deftu.gradle.utils.includeOrShade +import dev.deftu.gradle.utils.version.MinecraftVersion +import dev.deftu.gradle.utils.version.MinecraftVersions plugins { java @@ -44,8 +46,46 @@ toolkitLoomHelper { } } +repositories { + maven("https://api.modrinth.com/maven") { + content { includeGroup("maven.modrinth") } + } +} + dependencies { implementation(includeOrShade("gs.mclo:api:3.0.1")!!) + if (mcData.version >= MinecraftVersions.VERSION_1_16) { + data class CompatDependency( + val forge: String, + val fabric: String, + val neoforge: String + ) + + fun DependencyHandlerScope.modImplementationCompat(notation: CompatDependency?) { + notation?.let { + when { + mcData.isNeoForge -> modImplementation(it.neoforge) + mcData.isForge -> modImplementation(it.forge) + mcData.isFabric -> modImplementation(it.fabric) + else -> error("Unsupported loader type: ${mcData.loader}") + } + } + } + + fun nec(mcVersion: String, modVersion: String) = + mcVersion to CompatDependency( + fabric = "maven.modrinth:notenoughcrashes:$modVersion+$mcVersion-fabric", + forge = "maven.modrinth:notenoughcrashes:$modVersion+$mcVersion-forge", + neoforge = "maven.modrinth:notenoughcrashes:$modVersion+$mcVersion-neoforge" + ) + + val nec = mapOf( + nec("1.16.5", "4.1.4"), + nec("1.17.1", "4.1.4") + ) + + modImplementationCompat(nec[mcData.version.toString()]) + } } tasks { diff --git a/root.gradle.kts b/root.gradle.kts index b9bcae5..7717232 100644 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -10,10 +10,14 @@ preprocess { // "1.8.9-forge"(10809, "srg") // } - "1.12.2-forge"(11202, "srg") { - "1.12.2-fabric"(11202, "yarn") { - "1.8.9-fabric"(10809, "yarn") { - "1.8.9-forge"(10809, "srg") + "1.16.5-fabric"(11605, "yarn") { + "1.16.5-forge"(11605, "srg") { + "1.12.2-forge"(11202, "srg", rootProject.file("versions/1.16.5-1.8.9.txt")) { + "1.12.2-fabric"(11202, "yarn") { + "1.8.9-fabric"(10809, "yarn") { + "1.8.9-forge"(10809, "srg") + } + } } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index ab66608..237374e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -43,7 +43,9 @@ listOf( "1.8.9-forge", "1.8.9-fabric", "1.12.2-forge", - "1.12.2-fabric" + "1.12.2-fabric", + "1.16.5-forge", + "1.16.5-fabric", ).forEach { version -> include(":$version") project(":$version").apply { diff --git a/src/main/java/org/polyfrost/crashpatch/hooks/StacktraceDeobfuscator.java b/src/main/java/org/polyfrost/crashpatch/hooks/StacktraceDeobfuscator.java index d52f928..83046d5 100644 --- a/src/main/java/org/polyfrost/crashpatch/hooks/StacktraceDeobfuscator.java +++ b/src/main/java/org/polyfrost/crashpatch/hooks/StacktraceDeobfuscator.java @@ -1,5 +1,7 @@ package org.polyfrost.crashpatch.hooks; +//#if MC<1.13 + import java.io.File; import java.io.FileOutputStream; import java.net.HttpURLConnection; @@ -110,3 +112,4 @@ public String deobfuscateMethodName(String srgName) { return mcpName != null ? mcpName : srgName; } } +//#endif \ No newline at end of file diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/AccessorGuiDisconnected.java b/src/main/java/org/polyfrost/crashpatch/mixin/AccessorGuiDisconnected.java index 7926b89..cb93561 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/AccessorGuiDisconnected.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/AccessorGuiDisconnected.java @@ -1,15 +1,16 @@ package org.polyfrost.crashpatch.mixin; import net.minecraft.client.gui.GuiDisconnected; -import net.minecraft.util.IChatComponent; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; @Mixin(GuiDisconnected.class) public interface AccessorGuiDisconnected { - @Accessor("message") - IChatComponent getMessage(); - @Accessor("reason") - String getReason(); + //#if MC<=1.12.2 + String + //#else + //$$ net.minecraft.network.chat.Component + //#endif + getReason(); } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java index ac37b08..06e4a6e 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java @@ -8,7 +8,6 @@ import org.polyfrost.crashpatch.identifier.ModIdentifier; import org.polyfrost.crashpatch.hooks.CrashReportHook; -import org.polyfrost.crashpatch.hooks.StacktraceDeobfuscator; import net.minecraft.crash.CrashReport; import org.polyfrost.crashpatch.identifier.ModMetadata; import org.spongepowered.asm.mixin.Final; @@ -32,12 +31,14 @@ public String getSuspectedCrashPatchMods() { @Inject(method = "populateEnvironment", at = @At("TAIL")) private void afterPopulateEnvironment(CallbackInfo ci) { - ModMetadata susMod = ModIdentifier.INSTANCE.identifyFromStacktrace(cause); + ModMetadata susMod = ModIdentifier.INSTANCE.identifyFromStacktrace((CrashReport) (Object) this, this.cause); crashpatch$suspectedMod = (susMod == null ? "Unknown" : susMod.getName()); } + //#if MC<1.13 @Inject(method = "populateEnvironment", at = @At("HEAD")) private void beforePopulateEnvironment(CallbackInfo ci) { - StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(cause); + org.polyfrost.crashpatch.hooks.StacktraceDeobfuscator.INSTANCE.deobfuscateThrowable(this.cause); } + //#endif } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReportCategory.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReportCategory.java index 4cc06f2..a89dc2b 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReportCategory.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReportCategory.java @@ -1,6 +1,5 @@ package org.polyfrost.crashpatch.mixin; -import org.polyfrost.crashpatch.hooks.StacktraceDeobfuscator; import net.minecraft.crash.CrashReportCategory; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -10,8 +9,10 @@ @Mixin(CrashReportCategory.class) public class MixinCrashReportCategory { + //#if MC<1.13 @Inject(method = "getPrunedStackTrace", at = @At(value = "INVOKE", target = "Ljava/lang/Thread;getStackTrace()[Ljava/lang/StackTraceElement;", shift = At.Shift.BY, by = 2, ordinal = 0), locals = LocalCapture.CAPTURE_FAILHARD) private void afterGetStacktrace(int size, CallbackInfoReturnable cir, StackTraceElement[] stackTrace) { - StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(stackTrace); + org.polyfrost.crashpatch.hooks.StacktraceDeobfuscator.INSTANCE.deobfuscateStacktrace(stackTrace); } + //#endif } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java index 51f06cd..1ed0a54 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -2,10 +2,9 @@ import dev.deftu.omnicore.client.OmniDesktop; import dev.deftu.textile.minecraft.MCTextFormat; -import org.polyfrost.crashpatch.CrashPatch; +import org.polyfrost.crashpatch.client.CrashPatchClient; import org.polyfrost.crashpatch.hooks.MinecraftHook; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.multiplayer.GuiConnecting; import org.spongepowered.asm.mixin.Mixin; @@ -17,6 +16,7 @@ import java.awt.*; import java.io.IOException; import java.net.URI; +import java.util.List; @Mixin(GuiConnecting.class) public class MixinGuiConnecting extends GuiScreen { @@ -30,7 +30,7 @@ private void drawWarningText(int mouseX, int mouseY, float partialTicks, Callbac @Unique private String crashpatch$getText() { - return MCTextFormat.RED + "If Minecraft is stuck on this screen, please force close the game" + (CrashPatch.INSTANCE.isSkyclient() ? " and go to https://discord.gg/eh7tNFezct for support" : "") + "."; + return MCTextFormat.RED + "If Minecraft is stuck on this screen, please force close the game" + (CrashPatchClient.INSTANCE.isSkyclient() ? " and go to https://discord.gg/eh7tNFezct for support" : "") + "."; } @Override @@ -42,14 +42,14 @@ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) super.mouseClicked(mouseX, mouseY, mouseButton); if (((MinecraftHook) Minecraft.getMinecraft()).hasRecoveredFromCrash()) { if (mouseButton == 0) { - String[] list = crashpatch$wrapFormattedStringToWidth(crashpatch$getText(), width).split("\n"); + List list = this.fontRendererObj.listFormattedStringToWidth(crashpatch$getText(), width); int width = -1; for (String text : list) { width = Math.max(width, this.fontRendererObj.getStringWidth(text)); } int left = (this.width / 2) - width / 2; - if ((width == -1 || (left < mouseX && left + width > mouseX)) && (mouseY > 5 && mouseY < 15 + ((list.length - 1) * (this.fontRendererObj.FONT_HEIGHT + 2)))) { + if ((width == -1 || (left < mouseX && left + width > mouseX)) && (mouseY > 5 && mouseY < 15 + ((list.size() - 1) * (this.fontRendererObj.FONT_HEIGHT + 2)))) { OmniDesktop.browse(URI.create("https://discord.gg/eh7tNFezct")); } } @@ -58,83 +58,9 @@ protected void mouseClicked(int mouseX, int mouseY, int mouseButton) @Unique public void crashpatch$drawSplitCenteredString(String text, int x, int y, int color) { - for (String line : crashpatch$wrapFormattedStringToWidth(text, width).split("\n")) { + for (String line : this.fontRendererObj.listFormattedStringToWidth(text, width)) { drawCenteredString(this.fontRendererObj, line, x, y, color); y += this.fontRendererObj.FONT_HEIGHT + 2; } } - - @Unique - public String crashpatch$wrapFormattedStringToWidth(String str, int wrapWidth) { - int i = this.crashpatch$sizeStringToWidth(str, wrapWidth); - - if (str.length() <= i) { - return str; - } else { - String s = str.substring(0, i); - char c0 = str.charAt(i); - boolean flag = c0 == 32 || c0 == 10; - String s1 = FontRenderer.getFormatFromString(s) + str.substring(i + (flag ? 1 : 0)); - return s + "\n" + this.crashpatch$wrapFormattedStringToWidth(s1, wrapWidth); - } - } - - @Unique - private int crashpatch$sizeStringToWidth(String str, int wrapWidth) { - int i = str.length(); - int j = 0; - int k = 0; - int l = -1; - - for (boolean flag = false; k < i; ++k) { - char c0 = str.charAt(k); - - switch (c0) { - case '\n': - --k; - break; - case ' ': - l = k; - default: - j += this.fontRendererObj.getCharWidth(c0); - - if (flag) { - ++j; - } - - break; - case '\u00a7': - - if (k < i - 1) { - ++k; - char c1 = str.charAt(k); - - if (c1 != 108 && c1 != 76) { - if (c1 == 114 || c1 == 82 || crashpatch$isFormatColor(c1)) { - flag = false; - } - } else { - flag = true; - } - } - } - - if (c0 == 10) { - ++k; - l = k; - break; - } - - if (j > wrapWidth) { - break; - } - } - - return k != i && l != -1 && l < k ? l : k; - } - - @Unique - private static boolean crashpatch$isFormatColor(char colorChar) { - return colorChar >= 48 && colorChar <= 57 || colorChar >= 97 && colorChar <= 102 || colorChar >= 65 && colorChar <= 70; - } } diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java index a32a8d6..74bbda9 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java @@ -1,10 +1,10 @@ package org.polyfrost.crashpatch.mixin; -//#if FORGE +//#if FORGE && MC<1.13 import dev.deftu.omnicore.client.OmniClient; import dev.deftu.omnicore.client.OmniDesktop; import dev.deftu.textile.minecraft.MCTextFormat; -import org.polyfrost.crashpatch.CrashPatch; +import org.polyfrost.crashpatch.client.CrashPatchClient; import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiErrorScreen; import net.minecraftforge.fml.client.GuiDupesFound; @@ -48,7 +48,7 @@ private void onInit(CallbackInfo ci) { protected void actionPerformed(GuiButton button) { switch (button.id) { case 0: - OmniDesktop.open(new File(CrashPatch.getMcDir(), "mods")); + OmniDesktop.open(new File(CrashPatchClient.getMcDir(), "mods")); break; case 1: FMLCommonHandler.instance().exitJava(0, false); @@ -70,7 +70,7 @@ private void onDrawScreen(int mouseX, int mouseY, float partialTicks, CallbackIn offset += 10; - crashpatch$drawSplitString(MCTextFormat.BOLD + "To fix this, go into your mods folder by clicking the button below or going to " + CrashPatch.getMcDir().getAbsolutePath() + " and deleting the duplicate mods.", width / 2, offset, width, Color.BLUE.getRGB()); + crashpatch$drawSplitString(MCTextFormat.BOLD + "To fix this, go into your mods folder by clicking the button below or going to " + CrashPatchClient.getMcDir().getAbsolutePath() + " and deleting the duplicate mods.", width / 2, offset, width, Color.BLUE.getRGB()); for (GuiButton guiButton : this.buttonList) { guiButton.drawButton(this.mc, mouseX, mouseY diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index bf437b8..98d1e1b 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -1,5 +1,5 @@ package org.polyfrost.crashpatch.mixin; - +//#if MC<1.13 //#if FORGE import net.minecraftforge.fml.client.SplashProgress; import net.minecraftforge.fml.common.FMLCommonHandler; @@ -31,7 +31,7 @@ import org.lwjgl.LWJGLException; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.Display; -import org.polyfrost.crashpatch.CrashPatch; +import org.polyfrost.crashpatch.client.CrashPatchClient; import org.polyfrost.crashpatch.CrashPatchConfig; import org.polyfrost.crashpatch.crashes.StateManager; import org.polyfrost.crashpatch.gui.CrashUI; @@ -158,8 +158,8 @@ public void run(CallbackInfo ci) { while (running) { if (!this.hasCrashed || this.crashReporter == null) { try { - if (CrashPatch.INSTANCE.getRequestedCrash()) { - CrashPatch.INSTANCE.setRequestedCrash(false); + if (CrashPatchClient.INSTANCE.getRequestedCrash()) { + CrashPatchClient.INSTANCE.setRequestedCrash(false); throw new RuntimeException("Crash requested by CrashPatch"); } @@ -438,5 +438,4 @@ public void redirect(FMLCommonHandler instance, int code) { } - - +//#endif \ No newline at end of file diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java new file mode 100644 index 0000000..a7e4e64 --- /dev/null +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java @@ -0,0 +1,17 @@ +package org.polyfrost.crashpatch.mixin; + +import net.minecraft.client.Minecraft; +import org.polyfrost.crashpatch.client.CrashPatchClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MixinMinecraft_PreInitialize { + + @Inject(method = "startGame", at = @At("HEAD")) + private void preInitialize(CallbackInfo ci) { + CrashPatchClient.INSTANCE.preInitialize(); + } +} diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java index 9896758..f9625ef 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java @@ -1,5 +1,6 @@ package org.polyfrost.crashpatch.mixin; +//#if MC < 1.13 //#if FORGE import org.polyfrost.crashpatch.crashes.StateManager; import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; @@ -68,3 +69,4 @@ private void setDrawingBatchFalse(int pass, CallbackInfo ci) { //#endif } //#endif +//#endif \ No newline at end of file diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinWorldRenderer.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinWorldRenderer.java index 654a3cb..700aa82 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinWorldRenderer.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinWorldRenderer.java @@ -1,5 +1,5 @@ package org.polyfrost.crashpatch.mixin; - +//#if MC < 1.13 import org.polyfrost.crashpatch.crashes.StateManager; import net.minecraft.client.renderer.WorldRenderer; import org.spongepowered.asm.mixin.Mixin; @@ -29,3 +29,4 @@ public void resetState() { } } } +//#endif \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConstants.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConstants.kt new file mode 100644 index 0000000..91d307e --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConstants.kt @@ -0,0 +1,10 @@ +package org.polyfrost.crashpatch + +object CrashPatchConstants { + + // Sets the variables from `gradle.properties`. Depends on the `bloom` DGT plugin. + const val ID = "@MOD_ID@" + const val NAME = "@MOD_NAME@" + const val VERSION = "@MOD_VERSION@" + +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchEntrypoint.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchEntrypoint.kt new file mode 100644 index 0000000..51ddb76 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchEntrypoint.kt @@ -0,0 +1,74 @@ +package org.polyfrost.crashpatch + +//#if FABRIC +//$$ import net.fabricmc.api.ClientModInitializer +//#elseif FORGE +//#if MC >= 1.16.5 +//$$ import net.minecraftforge.eventbus.api.IEventBus +//$$ import net.minecraftforge.fml.common.Mod +//$$ import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent +//$$ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext +//#else +import net.minecraftforge.fml.common.Mod +import net.minecraftforge.fml.common.event.FMLInitializationEvent +//#endif +//#elseif NEOFORGE +//$$ import net.neoforged.bus.api.IEventBus +//$$ import net.neoforged.fml.common.Mod +//$$ import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent +//#endif + +import org.polyfrost.crashpatch.client.CrashPatchClient + +//#if FORGE-LIKE +//$$ import org.polyfrost.crashpatch.CrashPatchConstants +//#if MC >= 1.16.5 +//$$ @Mod(CrashPatchConstants.ID) +//#else +@Mod(modid = CrashPatchConstants.ID, version = CrashPatchConstants.VERSION) +//#endif +//#endif +class CrashPatchEntrypoint +//#if FABRIC +//$$ : ClientModInitializer +//#endif +{ + + //#if FORGE && MC >= 1.16.5 + //$$ init { + //$$ setupForgeEvents(FMLJavaModLoadingContext.get().modEventBus) + //$$ } + //#elseif NEOFORGE + //$$ constructor(modEventBus: IEventBus) { + //$$ setupForgeEvents(modEventBus) + //$$ } + //#endif + + //#if FABRIC + //$$ override + //#elseif FORGE && MC <= 1.12.2 + @Mod.EventHandler + //#endif + fun onInitializeClient( + //#if FORGE-LIKE + //#if MC >= 1.16.5 + //$$ event: FMLClientSetupEvent + //#else + event: FMLInitializationEvent + //#endif + //#endif + ) { + //#if MC <= 1.12.2 && FORGE-LIKE + if (!event.side.isClient) return + //#endif + + CrashPatchClient.initialize() + } + + //#if FORGE-LIKE && MC >= 1.16.5 + //$$ fun setupForgeEvents(modEventBus: IEventBus) { + //$$ modEventBus.addListener(this::onInitializeClient) + //$$ } + //#endif + +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt b/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt similarity index 59% rename from src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt rename to src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt index dc9248d..bba5a3e 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt @@ -1,37 +1,18 @@ -package org.polyfrost.crashpatch - -//#if FORGE -import net.minecraftforge.fml.common.Mod -import net.minecraftforge.fml.common.event.FMLInitializationEvent -import net.minecraftforge.fml.common.event.FMLPreInitializationEvent -//#else -//$$ import net.fabricmc.api.ClientModInitializer -//#endif +package org.polyfrost.crashpatch.client import dev.deftu.omnicore.client.OmniClientCommands import dev.deftu.textile.minecraft.MCSimpleTextHolder import dev.deftu.textile.minecraft.MCTextFormat - -import java.io.File import org.apache.logging.log4j.LogManager +import org.polyfrost.crashpatch.CrashPatchConfig +import org.polyfrost.crashpatch.CrashPatchConstants import org.polyfrost.crashpatch.crashes.CrashScanStorage -import org.polyfrost.crashpatch.crashes.DeobfuscatingRewritePolicy import org.polyfrost.oneconfig.api.commands.v1.CommandManager import org.polyfrost.oneconfig.utils.v1.Multithreading import org.polyfrost.oneconfig.utils.v1.dsl.openUI +import java.io.File -//#if FORGE -@Mod(modid = CrashPatch.ID, version = CrashPatch.VERSION, name = CrashPatch.NAME, modLanguageAdapter = "org.polyfrost.oneconfig.utils.v1.forge.KotlinLanguageAdapter") -//#endif -object CrashPatch - //#if FABRIC - //$$ : ClientModInitializer - //#endif -{ - - const val ID = "@MOD_ID@" - const val NAME = "@MOD_NAME@" - const val VERSION = "@MOD_VERSION@" +object CrashPatchClient { private val logger = LogManager.getLogger() @@ -59,7 +40,9 @@ object CrashPatch var requestedCrash = false fun preInitialize() { - DeobfuscatingRewritePolicy.install() + //#if MC<1.13 + org.polyfrost.crashpatch.crashes.DeobfuscatingRewritePolicy.install() + //#endif Multithreading.submit { logger.info("Is SkyClient: $isSkyclient") if (!CrashScanStorage.downloadJson()) { @@ -71,7 +54,7 @@ object CrashPatch fun initialize() { OmniClientCommands.initialize() - CommandManager.register(with(CommandManager.literal(ID)) { + CommandManager.register(with(CommandManager.literal(CrashPatchConstants.ID)) { executes { CrashPatchConfig.openUI() 1 @@ -79,9 +62,9 @@ object CrashPatch then(CommandManager.literal("reload").executes { ctx -> val text = if (CrashScanStorage.downloadJson()) { - MCSimpleTextHolder("[${NAME}] Successfully reloaded JSON file!").withFormatting(MCTextFormat.GREEN) + MCSimpleTextHolder("[${CrashPatchConstants.NAME}] Successfully reloaded JSON file!").withFormatting(MCTextFormat.Companion.GREEN) } else { - MCSimpleTextHolder("[${NAME}] Failed to reload JSON file!").withFormatting(MCTextFormat.RED) + MCSimpleTextHolder("[${CrashPatchConstants.NAME}] Failed to reload JSON file!").withFormatting(MCTextFormat.Companion.RED) } ctx.source.displayMessage(text) @@ -97,21 +80,4 @@ object CrashPatch CrashPatchConfig // Initialize the config } - //#if FORGE - @Mod.EventHandler - fun onPreInit(e: FMLPreInitializationEvent) { - preInitialize() - } - - @Mod.EventHandler - fun onInit(e: FMLInitializationEvent) { - initialize() - } - //#else - //$$ override fun onInitializeClient() { - //$$ preInitialize() - //$$ initialize() - //$$ } - //#endif - -} +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt index 40f2f48..c204e61 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt @@ -3,7 +3,7 @@ package org.polyfrost.crashpatch.crashes import com.google.gson.JsonObject import dev.deftu.textile.minecraft.MCTextFormat import org.apache.logging.log4j.LogManager -import org.polyfrost.crashpatch.CrashPatch +import org.polyfrost.crashpatch.client.CrashPatchClient import org.polyfrost.oneconfig.utils.v1.JsonUtils import java.io.File import kotlin.collections.set @@ -13,14 +13,14 @@ object CrashScanStorage { private val logger = LogManager.getLogger() private val cacheFile by lazy(LazyThreadSafetyMode.PUBLICATION) { - File(CrashPatch.mcDir, "OneConfig/CrashPatch/cache.json") + File(CrashPatchClient.mcDir, "OneConfig/CrashPatch/cache.json") } private val String.mappedPlaceholders: String get() = this .replace("%pathindicator%", "") - .replace("%gameroot%", CrashPatch.gameDir.absolutePath.removeSuffix(File.separator)) - .replace("%profileroot%", File(CrashPatch.mcDir, "OneConfig").parentFile.absolutePath.removeSuffix(File.separator)) + .replace("%gameroot%", CrashPatchClient.gameDir.absolutePath.removeSuffix(File.separator)) + .replace("%profileroot%", File(CrashPatchClient.mcDir, "OneConfig").parentFile.absolutePath.removeSuffix(File.separator)) private var skyclientData: JsonObject? = null diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt index 280556c..2955d64 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt @@ -1,5 +1,6 @@ package org.polyfrost.crashpatch.crashes +//#if MC<1.13 import org.polyfrost.crashpatch.hooks.StacktraceDeobfuscator import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.LogEvent @@ -46,3 +47,4 @@ class DeobfuscatingRewritePolicy : RewritePolicy { } } } +//#endif \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/crashes/StateManager.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/StateManager.kt index 07c22db..a07ef84 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/StateManager.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/StateManager.kt @@ -1,5 +1,6 @@ package org.polyfrost.crashpatch.crashes +//#if MC<1.13 import java.lang.ref.WeakReference /** @@ -25,4 +26,5 @@ object StateManager { interface IResettable { fun resetState() } -} \ No newline at end of file +} +//#endif \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt index fb6b225..384f313 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt @@ -5,7 +5,8 @@ import dev.deftu.omnicore.client.OmniDesktop import dev.deftu.omnicore.client.OmniScreen import net.minecraft.client.gui.GuiScreen import net.minecraft.crash.CrashReport -import org.polyfrost.crashpatch.CrashPatch +import org.polyfrost.crashpatch.CrashPatchConstants +import org.polyfrost.crashpatch.client.CrashPatchClient import org.polyfrost.crashpatch.crashes.CrashScanStorage import org.polyfrost.crashpatch.crashes.CrashScan import org.polyfrost.crashpatch.hooks.CrashReportHook @@ -155,7 +156,7 @@ class CrashUI @JvmOverloads constructor( Button(leftImage = "/assets/crashpatch/copy.svg".image()).onClick { selectedSolution?.solutions?.joinToString("\n")?.let(Clipboard.getInstance()::setString).also { copyState -> if (copyState == true) { - Notifications.enqueue(Notifications.Type.Success, CrashPatch.NAME, "Copied to clipboard!") + Notifications.enqueue(Notifications.Type.Success, CrashPatchConstants.NAME, "Copied to clipboard!") } } @@ -167,9 +168,9 @@ class CrashUI @JvmOverloads constructor( Clipboard.getInstance().setString(link) if (OmniDesktop.browse(URI.create(link))) { - Notifications.enqueue(Notifications.Type.Success, CrashPatch.NAME, "Link copied to clipboard and opened in browser") + Notifications.enqueue(Notifications.Type.Success, CrashPatchConstants.NAME, "Link copied to clipboard and opened in browser") } else { - Notifications.enqueue(Notifications.Type.Warning, CrashPatch.NAME, "Couldn't open link in browser, copied to clipboard instead.") + Notifications.enqueue(Notifications.Type.Warning, CrashPatchConstants.NAME, "Couldn't open link in browser, copied to clipboard instead.") } } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt b/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt index cee2f73..83be181 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt @@ -1,5 +1,5 @@ package org.polyfrost.crashpatch.identifier - +//#if MC<1.13 //#if FORGE import java.io.IOException import java.net.URISyntaxException @@ -17,16 +17,17 @@ import net.minecraftforge.fml.common.Loader //$$ import java.nio.file.Paths //#endif +import net.minecraft.crash.CrashReport import java.io.File import org.apache.logging.log4j.LogManager -typealias ModMap = Map> +private typealias ModMap = Map> object ModIdentifier { private val logger = LogManager.getLogger() - fun identifyFromStacktrace(e: Throwable?): ModMetadata? { + fun identifyFromStacktrace(crashReport: CrashReport, e: Throwable?): ModMetadata? { val modMap = makeModMap() // Get the set of classes @@ -190,3 +191,4 @@ object ModIdentifier { //#endif } +//#endif \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt b/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt index e2c6562..b37a171 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt @@ -10,7 +10,7 @@ class MixinPlugin : IMixinConfigPlugin { override fun getMixins(): MutableList { val result = mutableListOf() - //#if FORGE + //#if FORGE && MC<1.13 result.add("MixinGuiDupesFound") result.add("MixinTileEntityRendererDispatcher") //#endif diff --git a/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt index 1e72836..ba67fc0 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt @@ -6,7 +6,7 @@ import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL12 import org.lwjgl.opengl.GL14 -// Replaced in 1.12.2 source set +// Replaced in 1.12.2-fabric source set, removed in 1.16 and later object GlUtil { fun resetState() { diff --git a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt index 3a6246a..c7650b4 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt @@ -14,11 +14,15 @@ object GuiDisconnectedHook { @JvmStatic fun onGUIDisplay(screen: GuiScreen?, ci: CallbackInfo) { if (screen is GuiDisconnected && CrashPatchConfig.disconnectCrashPatch) { - val gui = screen as AccessorGuiDisconnected - val scan = scanReport(gui.message.formattedText, true) + val reason = (screen as AccessorGuiDisconnected).reason + //#if MC>=1.16 + //$$ .string + //#endif + + val scan = scanReport(reason, true) if (scan != null && scan.solutions.size > 1) { ci.cancel() - mc.displayGuiScreen(CrashUI(gui.message.formattedText, null, gui.reason, CrashUI.GuiType.DISCONNECT).create()) + mc.displayGuiScreen(CrashUI(reason, null, reason, CrashUI.GuiType.DISCONNECT).create()) } } } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/utils/UploadUtils.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/UploadUtils.kt index 2668516..8dd60a2 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/utils/UploadUtils.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/UploadUtils.kt @@ -1,10 +1,10 @@ package org.polyfrost.crashpatch.utils -import org.polyfrost.crashpatch.CrashPatch import gs.mclo.api.APIException import gs.mclo.api.Log import gs.mclo.api.MclogsClient import org.polyfrost.crashpatch.CrashPatchConfig +import org.polyfrost.crashpatch.CrashPatchConstants import java.io.BufferedReader import java.io.DataOutputStream import java.io.InputStreamReader @@ -15,7 +15,7 @@ import javax.net.ssl.HttpsURLConnection object UploadUtils { private val mclogsClient by lazy { - MclogsClient("CrashPatch", CrashPatch.VERSION, "1.8.9") + MclogsClient("CrashPatch", CrashPatchConstants.VERSION, "1.8.9") } private val sessionIdRegex = Regex("((Session ID is|--accessToken|Your new API key is) (\\S+))") @@ -39,7 +39,7 @@ object UploadUtils { conn.doOutput = true conn.instanceFollowRedirects = false conn.requestMethod = "POST" - conn.setRequestProperty("User-Agent", "CrashPatch/${CrashPatch.VERSION}") + conn.setRequestProperty("User-Agent", "CrashPatch/${CrashPatchConstants.VERSION}") conn.setRequestProperty("Content-Length", postDataLength.toString()) conn.useCaches = false diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 355dee0..809e20c 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -4,8 +4,7 @@ "version": "${mod_version}", "name": "${mod_name}", "authors": [ - "Polyfrost", - "SkyClient" + "Polyfrost" ], "contact": { "issues": "https://github.com/Polyfrost/${mod_name}/issues", @@ -16,8 +15,7 @@ "entrypoints": { "client": [ { - "adapter": "kotlin", - "value": "org.polyfrost.crashpatch.CrashPatch" + "value": "org.polyfrost.crashpatch.CrashPatchEntrypoint" } ] }, diff --git a/src/main/resources/mixins.crashpatch.json b/src/main/resources/mixins.crashpatch.json index 59d1764..fd5d1b1 100644 --- a/src/main/resources/mixins.crashpatch.json +++ b/src/main/resources/mixins.crashpatch.json @@ -15,5 +15,8 @@ "MixinMinecraft", "MixinWorldRenderer" ], - "verbose": true + "verbose": true, + "client": [ + "MixinMinecraft_PreInitialize" + ] } \ No newline at end of file diff --git a/versions/1.12.2-fabric/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt b/versions/1.12.2-fabric/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt index 4e7f927..b83068f 100644 --- a/versions/1.12.2-fabric/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt +++ b/versions/1.12.2-fabric/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt @@ -1,5 +1,5 @@ package org.polyfrost.crashpatch.utils - +//#if MC<1.13 import com.mojang.blaze3d.platform.GLX import com.mojang.blaze3d.platform.GlStateManager import net.minecraft.client.render.DiffuseLighting @@ -201,4 +201,5 @@ object GlUtil { GlStateManager.loadIdentity() GlStateManager.matrixMode(5888) } -} \ No newline at end of file +} +//#endif \ No newline at end of file diff --git a/versions/1.16.5-1.8.9.txt b/versions/1.16.5-1.8.9.txt new file mode 100644 index 0000000..134a484 --- /dev/null +++ b/versions/1.16.5-1.8.9.txt @@ -0,0 +1 @@ +net.minecraft.client.gui.screens.Screen net.minecraft.client.gui.GuiScreen \ No newline at end of file diff --git a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java new file mode 100644 index 0000000..db805ad --- /dev/null +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -0,0 +1,74 @@ +package org.polyfrost.crashpatch.mixin; + +import com.mojang.blaze3d.vertex.PoseStack; +import dev.deftu.omnicore.client.OmniDesktop; +import dev.deftu.textile.minecraft.MCTextFormat; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.FormattedText; +import net.minecraft.util.FormattedCharSequence; +import org.polyfrost.crashpatch.client.CrashPatchClient; +import org.polyfrost.crashpatch.hooks.MinecraftHook; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.ConnectScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.awt.*; +import java.net.URI; +import java.util.List; + +@Mixin(ConnectScreen.class) +public class MixinGuiConnecting extends Screen { + + protected MixinGuiConnecting(Component arg) { + super(arg); + } + + @Inject(method = "render", at = @At("TAIL")) + private void drawWarningText(PoseStack arg, int i, int j, float f, CallbackInfo ci) { + if (((MinecraftHook) Minecraft.getInstance()).hasRecoveredFromCrash()) { + crashpatch$drawSplitCenteredString(arg, crashpatch$getText(), width / 2, 5, Color.WHITE.getRGB()); + } + } + + @Unique + private String crashpatch$getText() { + return MCTextFormat.RED + "If Minecraft is stuck on this screen, please force close the game" + (CrashPatchClient.INSTANCE.isSkyclient() ? " and go to https://discord.gg/eh7tNFezct for support" : "") + "."; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) { + boolean clicked = super.mouseClicked(mouseX, mouseY, mouseButton); + if (!clicked) { + return true; + } + if (((MinecraftHook) Minecraft.getInstance()).hasRecoveredFromCrash()) { + if (mouseButton == 0) { + List list = this.font.split(FormattedText.of(crashpatch$getText()), width); + int width = -1; + for (FormattedCharSequence text : list) { + width = Math.max(width, this.font.width(text)); + } + + int left = (this.width / 2) - width / 2; + if ((width == -1 || (left < mouseX && left + width > mouseX)) && (mouseY > 5 && mouseY < 15 + ((list.size() - 1) * (this.font.lineHeight + 2)))) { + OmniDesktop.browse(URI.create("https://discord.gg/eh7tNFezct")); + return true; + } + } + } + return false; + } + + @Unique + public void crashpatch$drawSplitCenteredString(PoseStack stack, String text, int x, int y, int color) { + for (FormattedCharSequence line : this.font.split(FormattedText.of(text), width)) { + this.font.drawShadow(stack, line, (float) (x - this.font.width(line) / 2), (float) y, color); + y += this.font.lineHeight + 2; + } + } +} diff --git a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java new file mode 100644 index 0000000..10bfb14 --- /dev/null +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java @@ -0,0 +1,18 @@ +package org.polyfrost.crashpatch.mixin; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.main.GameConfig; +import org.polyfrost.crashpatch.client.CrashPatchClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyVariable; + +@Mixin(Minecraft.class) +public class MixinMinecraft_PreInitialize { + + @ModifyVariable(method = "", at = @At("STORE"), argsOnly = true, ordinal = 0) + private static GameConfig preInitialize(GameConfig v) { + CrashPatchClient.INSTANCE.preInitialize(); + return v; + } +} diff --git a/versions/1.16.5-forge/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt b/versions/1.16.5-forge/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt new file mode 100644 index 0000000..7db856f --- /dev/null +++ b/versions/1.16.5-forge/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt @@ -0,0 +1,16 @@ +package org.polyfrost.crashpatch.identifier + +import net.minecraft.CrashReport +import fudge.notenoughcrashes.stacktrace.ModIdentifier as NECModIdentifier + +object ModIdentifier { + + fun identifyFromStacktrace(crashReport: CrashReport, e: Throwable?): ModMetadata? { + return NECModIdentifier.getSuspectedModsOf(crashReport)?.map { mod -> + ModMetadata( + mod.id(), + mod.name() + ) + }?.firstOrNull() + } +} \ No newline at end of file From 502d2f9c77cf0626ddf262faf36fd49dc15596b6 Mon Sep 17 00:00:00 2001 From: Wyvest Date: Sat, 16 Aug 2025 17:47:43 +0900 Subject: [PATCH 32/37] Replace NEC's GUI with CrashPatch's one --- .../crashpatch/mixin/MixinMinecraft.java | 6 ---- ...nMinecraft_OverrideDisconnectedScreen.java | 18 ++++++++++++ .../org/polyfrost/crashpatch/gui/CrashUI.kt | 4 +++ .../crashpatch/plugin/MixinPlugin.kt | 9 +++++- src/main/resources/mixins.crashpatch.json | 11 +++---- ...xinEntryPointCatcher_UseCrashPatchGui.java | 22 ++++++++++++++ .../MixinInGameCatcher_UseCrashPatchGui.java | 29 +++++++++++++++++++ .../org/polyfrost/crashpatch/utils/GlUtil.kt | 13 +++++++++ 8 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_OverrideDisconnectedScreen.java create mode 100644 versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinEntryPointCatcher_UseCrashPatchGui.java create mode 100644 versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinInGameCatcher_UseCrashPatchGui.java create mode 100644 versions/1.16.5-forge/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index 98d1e1b..75aab8c 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -37,7 +37,6 @@ import org.polyfrost.crashpatch.gui.CrashUI; import org.polyfrost.crashpatch.hooks.MinecraftHook; import org.polyfrost.crashpatch.utils.GlUtil; -import org.polyfrost.crashpatch.utils.GuiDisconnectedHook; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -195,11 +194,6 @@ public void run(CallbackInfo ci) { } } - @Inject(method = "displayGuiScreen", at = @At("HEAD"), cancellable = true) - private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { - GuiDisconnectedHook.INSTANCE.onGUIDisplay(i, ci); - } - /** * @author Runemoro */ diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_OverrideDisconnectedScreen.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_OverrideDisconnectedScreen.java new file mode 100644 index 0000000..63164a1 --- /dev/null +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_OverrideDisconnectedScreen.java @@ -0,0 +1,18 @@ +package org.polyfrost.crashpatch.mixin; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import org.polyfrost.crashpatch.utils.GuiDisconnectedHook; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MixinMinecraft_OverrideDisconnectedScreen { + + @Inject(method = "displayGuiScreen", at = @At("HEAD"), cancellable = true) + private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { + GuiDisconnectedHook.onGUIDisplay(i, ci); + } +} diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt index 384f313..07292a4 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt @@ -217,7 +217,11 @@ class CrashUI @JvmOverloads constructor( Group( Button(text = "crashpatch.continue", padding = Vec2(14f, 14f)).onClick { if (type == GuiType.INIT) { + //#if MC < 1.13 shouldCrash = true + //#else + //$$ throw throwable!! + //#endif } else { OmniScreen.closeScreen() } diff --git a/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt b/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt index b37a171..6610eef 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt @@ -10,10 +10,17 @@ class MixinPlugin : IMixinConfigPlugin { override fun getMixins(): MutableList { val result = mutableListOf() - //#if FORGE && MC<1.13 + //#if MC<1.13 + //#if FORGE result.add("MixinGuiDupesFound") result.add("MixinTileEntityRendererDispatcher") //#endif + result.add("MixinMinecraft") + result.add("MixinWorldRenderer") + //#else + //$$ result.add("MixinEntryPointCatcher_UseCrashPatchGui") + //$$ result.add("MixinInGameCatcher_UseCrashPatchGui") + //#endif return result } diff --git a/src/main/resources/mixins.crashpatch.json b/src/main/resources/mixins.crashpatch.json index fd5d1b1..1e373b5 100644 --- a/src/main/resources/mixins.crashpatch.json +++ b/src/main/resources/mixins.crashpatch.json @@ -1,6 +1,6 @@ { "compatibilityLevel": "JAVA_8", - "minVersion": "0.7", + "minVersion": "0.8", "package": "org.polyfrost.crashpatch.mixin", "plugin": "org.polyfrost.crashpatch.plugin.MixinPlugin", "refmap": "mixins.crashpatch.refmap.json", @@ -12,11 +12,8 @@ "MixinCrashReport", "MixinCrashReportCategory", "MixinGuiConnecting", - "MixinMinecraft", - "MixinWorldRenderer" - ], - "verbose": true, - "client": [ + "MixinMinecraft_OverrideDisconnectedScreen", "MixinMinecraft_PreInitialize" - ] + ], + "verbose": true } \ No newline at end of file diff --git a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinEntryPointCatcher_UseCrashPatchGui.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinEntryPointCatcher_UseCrashPatchGui.java new file mode 100644 index 0000000..92cf62a --- /dev/null +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinEntryPointCatcher_UseCrashPatchGui.java @@ -0,0 +1,22 @@ +package org.polyfrost.crashpatch.mixin; + +import fudge.notenoughcrashes.mixinhandlers.EntryPointCatcher; +import net.minecraft.CrashReport; +import net.minecraft.client.gui.screens.Screen; +import org.polyfrost.crashpatch.gui.CrashUI; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(value = EntryPointCatcher.class) +public class MixinEntryPointCatcher_UseCrashPatchGui { + + @Shadow private static CrashReport crashReport; + + @ModifyArg(method = "displayInitErrorScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setScreen(Lnet/minecraft/client/gui/screens/Screen;)V"), index = 0) + private static Screen useCrashPatchGui(Screen screen) { + // Use the CrashPatch GUI instead of the default one + return new CrashUI(crashReport, CrashUI.GuiType.INIT).create(); + } +} diff --git a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinInGameCatcher_UseCrashPatchGui.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinInGameCatcher_UseCrashPatchGui.java new file mode 100644 index 0000000..1c0b920 --- /dev/null +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinInGameCatcher_UseCrashPatchGui.java @@ -0,0 +1,29 @@ +package org.polyfrost.crashpatch.mixin; + +import fudge.notenoughcrashes.mixinhandlers.InGameCatcher; +import net.minecraft.CrashReport; +import net.minecraft.client.gui.screens.Screen; +import org.polyfrost.crashpatch.gui.CrashUI; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.ModifyArg; + +@Mixin(value = InGameCatcher.class) +public class MixinInGameCatcher_UseCrashPatchGui { + + @Unique private static CrashReport crashpatch$crashReport; + + @ModifyArg(method = "displayCrashScreen", at = @At(value = "INVOKE", target = "Lfudge/notenoughcrashes/stacktrace/CrashUtils;outputReport(Lnet/minecraft/CrashReport;Z)V"), index = 0) + private static CrashReport captureCrashReport(CrashReport report) { + // Capture the crash report to be used in the CrashPatch GUI + crashpatch$crashReport = report; + return report; + } + + @ModifyArg(method = "displayCrashScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setScreen(Lnet/minecraft/client/gui/screens/Screen;)V"), index = 0) + private static Screen useCrashPatchGui(Screen par1) { + // Use the CrashPatch GUI instead of the default one + return new CrashUI(crashpatch$crashReport, CrashUI.GuiType.NORMAL).create(); + } +} diff --git a/versions/1.16.5-forge/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt b/versions/1.16.5-forge/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt new file mode 100644 index 0000000..e212423 --- /dev/null +++ b/versions/1.16.5-forge/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt @@ -0,0 +1,13 @@ +package org.polyfrost.crashpatch.utils + +import fudge.notenoughcrashes.utils.GlUtil as NECGlUtil + +/** + * Isn't used on 1.16. + */ +@Suppress("unused") +object GlUtil { + fun resetState() { + NECGlUtil.resetState() + } +} \ No newline at end of file From 3bea34b314781212f5f4f76e997d8caecea858a7 Mon Sep 17 00:00:00 2001 From: Wyvest Date: Sat, 16 Aug 2025 23:19:09 +0900 Subject: [PATCH 33/37] make all versions up to 1.21.5 compile --- build.gradle.kts | 11 +++- root.gradle.kts | 52 ++++++++++++++++--- settings.gradle.kts | 32 ++++++++++++ .../crashpatch/mixin/MixinCrashReport.java | 8 ++- .../org/polyfrost/crashpatch/gui/CrashUI.kt | 13 ++++- ...xinEntryPointCatcher_UseCrashPatchGui.java | 2 +- .../crashpatch/mixin/MixinGuiConnecting.java | 23 ++++++-- .../MixinInGameCatcher_UseCrashPatchGui.java | 4 +- .../mixin/AccessorGuiDisconnected.java | 12 +++++ .../crashpatch/utils/GuiDisconnectedHook.kt | 27 ++++++++++ 10 files changed, 168 insertions(+), 16 deletions(-) create mode 100644 versions/1.21.1-neoforge/src/main/java/org/polyfrost/crashpatch/mixin/AccessorGuiDisconnected.java create mode 100644 versions/1.21.1-neoforge/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt diff --git a/build.gradle.kts b/build.gradle.kts index 65a3857..a2f524e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -81,7 +81,16 @@ dependencies { val nec = mapOf( nec("1.16.5", "4.1.4"), - nec("1.17.1", "4.1.4") + nec("1.17.1", "4.1.4"), + nec("1.18.2", "4.2.0"), + nec("1.19.2", "5.0.0"), + nec("1.19.4", "4.4.1"), + nec("1.20.1", "4.4.9"), + nec("1.20.4", "4.4.7"), + nec("1.20.6", "4.4.7"), + nec("1.21.1", "4.4.9"), + nec("1.21.4", "4.4.8"), + nec("1.21.5", "4.4.9") ) modImplementationCompat(nec[mcData.version.toString()]) diff --git a/root.gradle.kts b/root.gradle.kts index 7717232..c3988a4 100644 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -10,12 +10,52 @@ preprocess { // "1.8.9-forge"(10809, "srg") // } - "1.16.5-fabric"(11605, "yarn") { - "1.16.5-forge"(11605, "srg") { - "1.12.2-forge"(11202, "srg", rootProject.file("versions/1.16.5-1.8.9.txt")) { - "1.12.2-fabric"(11202, "yarn") { - "1.8.9-fabric"(10809, "yarn") { - "1.8.9-forge"(10809, "srg") + "1.21.5-fabric"(1_21_05, "yarn") { + "1.21.5-neoforge"(1_21_05, "srg") { + "1.21.4-neoforge"(1_21_04, "srg") { + "1.21.4-fabric"(1_21_04, "yarn") { + "1.21.1-fabric"(1_21_01, "yarn") { + "1.21.1-neoforge"(1_21_01, "srg") { + "1.20.6-neoforge"(1_20_06, "srg") { + "1.20.6-fabric"(1_20_06, "yarn") { + "1.20.4-fabric"(1_20_04, "yarn") { + "1.20.4-forge"(1_20_04, "srg") { + "1.20.1-forge"(1_20_01, "srg") { + "1.20.1-fabric"(1_20_01, "yarn") { + "1.19.4-fabric"(1_19_04, "yarn") { + "1.19.4-forge"(1_19_04, "srg") { + "1.19.2-forge"(1_19_02, "srg") { + "1.19.2-fabric"(1_19_02, "yarn") { + "1.18.2-fabric"(1_18_02, "yarn") { + "1.18.2-forge"(1_18_02, "srg") { + "1.17.1-forge"(1_17_01, "srg") { + "1.17.1-fabric"(1_17_01, "yarn") { + "1.16.5-fabric"(1_16_05, "yarn"){ + "1.16.5-forge"(1_16_05, "srg") { + "1.12.2-forge"(1_12_02, "srg", rootProject.file("versions/1.16.5-1.8.9.txt")) { + "1.12.2-fabric"(1_12_02, "yarn") { + "1.8.9-fabric"(1_08_09, "yarn"){ + "1.8.9-forge"(1_08_09, "srg") + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 237374e..129335a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -42,10 +42,42 @@ rootProject.buildFileName = "root.gradle.kts" listOf( "1.8.9-forge", "1.8.9-fabric", + "1.12.2-forge", "1.12.2-fabric", + "1.16.5-forge", "1.16.5-fabric", + + "1.17.1-forge", + "1.17.1-fabric", + + "1.18.2-forge", + "1.18.2-fabric", + + "1.19.2-forge", + "1.19.2-fabric", + + "1.19.4-forge", + "1.19.4-fabric", + + "1.20.1-forge", + "1.20.1-fabric", + + "1.20.4-forge", + "1.20.4-fabric", + + "1.20.6-neoforge", + "1.20.6-fabric", + + "1.21.1-neoforge", + "1.21.1-fabric", + + "1.21.4-neoforge", + "1.21.4-fabric", + + "1.21.5-neoforge", + "1.21.5-fabric" ).forEach { version -> include(":$version") project(":$version").apply { diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java index 06e4a6e..76f7fb6 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java @@ -29,7 +29,13 @@ public String getSuspectedCrashPatchMods() { return crashpatch$suspectedMod; } - @Inject(method = "populateEnvironment", at = @At("TAIL")) + @Inject(method = + //#if MC<1.17 + "populateEnvironment" + //#else + //$$ "" + //#endif + , at = @At("TAIL")) private void afterPopulateEnvironment(CallbackInfo ci) { ModMetadata susMod = ModIdentifier.INSTANCE.identifyFromStacktrace((CrashReport) (Object) this, this.cause); crashpatch$suspectedMod = (susMod == null ? "Unknown" : susMod.getName()); diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt index 07292a4..f3b8eb9 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt @@ -42,8 +42,17 @@ class CrashUI @JvmOverloads constructor( @JvmOverloads constructor(report: CrashReport, type: GuiType = GuiType.NORMAL) : this( - report.completeReport, - report.file, + report + //#if MC < 1.21 + .completeReport, + //#else + //$$ .getFriendlyReport(net.minecraft.ReportType.CRASH), + //#endif + report.file + //#if MC >= 1.21 + //$$ ?.toFile() + //#endif + , (report as CrashReportHook).suspectedCrashPatchMods, type, report.crashCause diff --git a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinEntryPointCatcher_UseCrashPatchGui.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinEntryPointCatcher_UseCrashPatchGui.java index 92cf62a..137ab62 100644 --- a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinEntryPointCatcher_UseCrashPatchGui.java +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinEntryPointCatcher_UseCrashPatchGui.java @@ -12,7 +12,7 @@ @Mixin(value = EntryPointCatcher.class) public class MixinEntryPointCatcher_UseCrashPatchGui { - @Shadow private static CrashReport crashReport; + @Shadow(remap = false) private static CrashReport crashReport; @ModifyArg(method = "displayInitErrorScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setScreen(Lnet/minecraft/client/gui/screens/Screen;)V"), index = 0) private static Screen useCrashPatchGui(Screen screen) { diff --git a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java index db805ad..047885d 100644 --- a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -29,7 +29,13 @@ protected MixinGuiConnecting(Component arg) { } @Inject(method = "render", at = @At("TAIL")) - private void drawWarningText(PoseStack arg, int i, int j, float f, CallbackInfo ci) { + private void drawWarningText( + //#if MC<1.20 + PoseStack arg, + //#else + //$$ net.minecraft.client.gui.DrawContext arg, + //#endif + int i, int j, float f, CallbackInfo ci) { if (((MinecraftHook) Minecraft.getInstance()).hasRecoveredFromCrash()) { crashpatch$drawSplitCenteredString(arg, crashpatch$getText(), width / 2, 5, Color.WHITE.getRGB()); } @@ -65,9 +71,20 @@ public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) { } @Unique - public void crashpatch$drawSplitCenteredString(PoseStack stack, String text, int x, int y, int color) { + public void crashpatch$drawSplitCenteredString( + //#if MC<1.20 + PoseStack stack, + //#else + //$$ net.minecraft.client.gui.DrawContext ctx, + //#endif + String text, int x, int y, int color) { for (FormattedCharSequence line : this.font.split(FormattedText.of(text), width)) { - this.font.drawShadow(stack, line, (float) (x - this.font.width(line) / 2), (float) y, color); + //#if MC<1.20 + this.font.drawShadow(stack, + //#else + //$$ ctx.drawTextWithShadow(this.textRenderer, + //#endif + line, (int) (x - ((float) this.font.width(line) / 2)), y, color); y += this.font.lineHeight + 2; } } diff --git a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinInGameCatcher_UseCrashPatchGui.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinInGameCatcher_UseCrashPatchGui.java index 1c0b920..7f1216e 100644 --- a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinInGameCatcher_UseCrashPatchGui.java +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinInGameCatcher_UseCrashPatchGui.java @@ -14,14 +14,14 @@ public class MixinInGameCatcher_UseCrashPatchGui { @Unique private static CrashReport crashpatch$crashReport; - @ModifyArg(method = "displayCrashScreen", at = @At(value = "INVOKE", target = "Lfudge/notenoughcrashes/stacktrace/CrashUtils;outputReport(Lnet/minecraft/CrashReport;Z)V"), index = 0) + @ModifyArg(method = "displayCrashScreen", at = @At(value = "INVOKE", target = "Lfudge/notenoughcrashes/stacktrace/CrashUtils;outputReport(Lnet/minecraft/CrashReport;Z)V"), index = 0, remap = false) private static CrashReport captureCrashReport(CrashReport report) { // Capture the crash report to be used in the CrashPatch GUI crashpatch$crashReport = report; return report; } - @ModifyArg(method = "displayCrashScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setScreen(Lnet/minecraft/client/gui/screens/Screen;)V"), index = 0) + @ModifyArg(method = "displayCrashScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;setScreen(Lnet/minecraft/client/gui/screens/Screen;)V", remap = true), index = 0, remap = false) private static Screen useCrashPatchGui(Screen par1) { // Use the CrashPatch GUI instead of the default one return new CrashUI(crashpatch$crashReport, CrashUI.GuiType.NORMAL).create(); diff --git a/versions/1.21.1-neoforge/src/main/java/org/polyfrost/crashpatch/mixin/AccessorGuiDisconnected.java b/versions/1.21.1-neoforge/src/main/java/org/polyfrost/crashpatch/mixin/AccessorGuiDisconnected.java new file mode 100644 index 0000000..c3fff34 --- /dev/null +++ b/versions/1.21.1-neoforge/src/main/java/org/polyfrost/crashpatch/mixin/AccessorGuiDisconnected.java @@ -0,0 +1,12 @@ +package org.polyfrost.crashpatch.mixin; + +import net.minecraft.client.gui.screens.DisconnectedScreen; +import net.minecraft.network.DisconnectionDetails; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(DisconnectedScreen.class) +public interface AccessorGuiDisconnected { + @Accessor + DisconnectionDetails getDetails(); +} diff --git a/versions/1.21.1-neoforge/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt b/versions/1.21.1-neoforge/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt new file mode 100644 index 0000000..202514e --- /dev/null +++ b/versions/1.21.1-neoforge/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt @@ -0,0 +1,27 @@ +package org.polyfrost.crashpatch.utils + +import org.polyfrost.crashpatch.crashes.CrashScanStorage.scanReport +import org.polyfrost.crashpatch.mixin.AccessorGuiDisconnected +import net.minecraft.client.gui.screens.DisconnectedScreen +import net.minecraft.client.gui.screens.Screen +import org.polyfrost.crashpatch.CrashPatchConfig +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo +import org.polyfrost.crashpatch.gui.CrashUI +import org.polyfrost.oneconfig.utils.v1.dsl.mc + +object GuiDisconnectedHook { + + @JvmStatic + fun onGUIDisplay(screen: Screen?, ci: CallbackInfo) { + if (screen is DisconnectedScreen && CrashPatchConfig.disconnectCrashPatch) { + val reason = (screen as AccessorGuiDisconnected).details.reason.string + + val scan = scanReport(reason, true) + if (scan != null && scan.solutions.size > 1) { + ci.cancel() + mc.setScreen(CrashUI(reason, null, reason, CrashUI.GuiType.DISCONNECT).create()) + } + } + } + +} \ No newline at end of file From ed1d16f4e5f4918b127328f645692e5e5f176f7a Mon Sep 17 00:00:00 2001 From: Wyvest Date: Sun, 17 Aug 2025 07:45:37 +0900 Subject: [PATCH 34/37] it works now --- build.gradle.kts | 25 ++++++++++ .../crashpatch/mixin/MixinMinecraft.java | 5 -- .../mixin/MixinMinecraft_Debug.java | 20 ++++++++ .../crashpatch/client/CrashPatchClient.kt | 6 +++ .../org/polyfrost/crashpatch/gui/CrashUI.kt | 27 ++++++++-- .../crashpatch/plugin/EarlyMixinPlugin.kt | 48 ++++++++++++++++++ .../crashpatch/plugin/MixinPlugin.kt | 5 ++ src/main/resources/fabric.mod.json | 3 +- .../resources/mixins.crashpatch.init.json | 7 +++ src/main/resources/mixins.crashpatch.json | 3 +- .../mixin/MixinModLoaders_Debug.java | 28 +++++++++++ .../mixin/MixinMinecraft_CrashInitGui.java | 22 +++++++++ .../mixin/MixinMinecraft_PreInitialize.java | 16 ++++-- .../MixinMinecraft_CrashPatchInitUI.java | 49 +++++++++++++++++++ 14 files changed, 247 insertions(+), 17 deletions(-) create mode 100644 src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_Debug.java create mode 100644 src/main/kotlin/org/polyfrost/crashpatch/plugin/EarlyMixinPlugin.kt create mode 100644 src/main/resources/mixins.crashpatch.init.json create mode 100644 versions/1.16.5-fabric/src/main/java/org/polyfrost/crashpatch/mixin/MixinModLoaders_Debug.java create mode 100644 versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_CrashInitGui.java create mode 100644 versions/1.20.4-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_CrashPatchInitUI.java diff --git a/build.gradle.kts b/build.gradle.kts index a2f524e..88e9d5f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,10 @@ plugins { id("dev.deftu.gradle.tools.minecraft.releases") // Applies the Minecraft auto-releasing plugin, which allows you to automatically release your mod to CurseForge and Modrinth. } +if (mcData.isForge) { + loom.forge.mixinConfig("mixins.crashpatch.init.json") +} + toolkitLoomHelper { useOneConfig { version = "1.0.0-alpha.134" @@ -32,6 +36,10 @@ toolkitLoomHelper { } } + useMixinExtras("0.5.0") + + useProperty("mixin.debug.export", "true", GameSide.BOTH) + // Turns off the server-side run configs, as we're building a client-sided mod. disableRunConfigs(GameSide.SERVER) @@ -50,11 +58,20 @@ repositories { maven("https://api.modrinth.com/maven") { content { includeGroup("maven.modrinth") } } + maven("https://maven.bawnorton.com/releases") { + content { includeGroup("com.github.bawnorton.mixinsquared") } + } } dependencies { implementation(includeOrShade("gs.mclo:api:3.0.1")!!) + includeOrShade(implementation(annotationProcessor("com.github.bawnorton.mixinsquared:mixinsquared-common:0.3.3")!!)!!) + includeOrShade("io.github.llamalad7:mixinextras-common:0.5.0") + if (mcData.isFabric) { + includeOrShade(implementation("io.github.llamalad7:mixinextras-fabric:0.5.0")!!) + } if (mcData.version >= MinecraftVersions.VERSION_1_16) { + includeOrShade(implementation("com.github.bawnorton.mixinsquared:mixinsquared-${mcData.loader}:0.3.3")!!) data class CompatDependency( val forge: String, val fabric: String, @@ -101,4 +118,12 @@ tasks { jar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } + + remapJar { + manifest { + attributes(mapOf( + "MixinConfigs" to "mixins.crashpatch.init.json,mixins.crashpatch.json", + )) + } + } } \ No newline at end of file diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index 75aab8c..15a3e6d 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -157,11 +157,6 @@ public void run(CallbackInfo ci) { while (running) { if (!this.hasCrashed || this.crashReporter == null) { try { - if (CrashPatchClient.INSTANCE.getRequestedCrash()) { - CrashPatchClient.INSTANCE.setRequestedCrash(false); - throw new RuntimeException("Crash requested by CrashPatch"); - } - runGameLoop(); } catch (ReportedException e) { crashpatch$clientCrashCount++; diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_Debug.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_Debug.java new file mode 100644 index 0000000..8311dc7 --- /dev/null +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_Debug.java @@ -0,0 +1,20 @@ +package org.polyfrost.crashpatch.mixin; + +import net.minecraft.client.Minecraft; +import org.polyfrost.crashpatch.client.CrashPatchClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class MixinMinecraft_Debug { + + @Inject(method = "runTick", at = @At("RETURN")) + private void debugCrash(CallbackInfo ci) { + if (CrashPatchClient.INSTANCE.getRequestedCrash()) { + CrashPatchClient.INSTANCE.setRequestedCrash(false); + throw new RuntimeException("Crash requested by CrashPatch"); + } + } +} diff --git a/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt b/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt index bba5a3e..59cd26f 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt @@ -49,6 +49,12 @@ object CrashPatchClient { logger.error("CrashHelper failed to preload crash data JSON!") } } + + //#if MC<1.13 + if (System.getProperty("polyfrost.crashpatch.init_crash") == "true") { + throw RuntimeException("Crash requested by CrashPatch") + } + //#endif } fun initialize() { diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt index f3b8eb9..d700bdc 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt @@ -14,6 +14,7 @@ import org.polyfrost.crashpatch.utils.UploadUtils import org.polyfrost.oneconfig.api.ui.v1.Notifications import org.polyfrost.oneconfig.api.ui.v1.OCPolyUIBuilder import org.polyfrost.oneconfig.api.ui.v1.UIManager +import org.polyfrost.oneconfig.internal.OneConfig import org.polyfrost.polyui.PolyUI import org.polyfrost.polyui.animate.Animations import org.polyfrost.polyui.color.Colors @@ -60,6 +61,24 @@ class CrashUI @JvmOverloads constructor( companion object { var leaveWorldCrash = false + var currentInstance: GuiScreen? = null + private set + var currentUI: CrashUI? = null + private set + } + + init { + try { + val initialized = OneConfig::class.java.getDeclaredField("initialized") + initialized.isAccessible = true + if (!initialized.getBoolean(OneConfig.INSTANCE)) { + val registerEventHandlers = OneConfig::class.java.getDeclaredMethod("registerEventHandlers") + registerEventHandlers.isAccessible = true + registerEventHandlers.invoke(OneConfig.INSTANCE) + } + } catch (e: Exception) { + e.printStackTrace() + } } private val crashScan: CrashScan? by lazy { @@ -226,11 +245,7 @@ class CrashUI @JvmOverloads constructor( Group( Button(text = "crashpatch.continue", padding = Vec2(14f, 14f)).onClick { if (type == GuiType.INIT) { - //#if MC < 1.13 shouldCrash = true - //#else - //$$ throw throwable!! - //#endif } else { OmniScreen.closeScreen() } @@ -252,7 +267,9 @@ class CrashUI @JvmOverloads constructor( val screen = UIManager.INSTANCE.createPolyUIScreen(polyUI, 1920f, 1080f, false, true, onClose) polyUI.window = UIManager.INSTANCE.createWindow() - return screen as GuiScreen + currentUI = this + currentInstance = screen as GuiScreen + return screen } private fun createSolutionText(solution: CrashScan.Solution) = Text( diff --git a/src/main/kotlin/org/polyfrost/crashpatch/plugin/EarlyMixinPlugin.kt b/src/main/kotlin/org/polyfrost/crashpatch/plugin/EarlyMixinPlugin.kt new file mode 100644 index 0000000..05aead2 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/plugin/EarlyMixinPlugin.kt @@ -0,0 +1,48 @@ +package org.polyfrost.crashpatch.plugin + +import com.bawnorton.mixinsquared.MixinSquaredBootstrap +import com.llamalad7.mixinextras.MixinExtrasBootstrap +import org.objectweb.asm.tree.ClassNode +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin +import org.spongepowered.asm.mixin.extensibility.IMixinInfo + +class EarlyMixinPlugin : IMixinConfigPlugin { + override fun onLoad(p0: String?) { + MixinExtrasBootstrap.init() + MixinSquaredBootstrap.init() + } + + override fun getRefMapperConfig(): String? { + return null + } + + override fun shouldApplyMixin(p0: String?, p1: String?): Boolean { + return true + } + + override fun acceptTargets( + p0: Set?, + p1: Set? + ) { + } + + override fun getMixins(): List? { + return null + } + + override fun preApply( + p0: String?, + p1: ClassNode?, + p2: String?, + p3: IMixinInfo? + ) { + } + + override fun postApply( + p0: String?, + p1: ClassNode?, + p2: String?, + p3: IMixinInfo? + ) { + } +} \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt b/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt index 6610eef..bf4d732 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt @@ -20,6 +20,11 @@ class MixinPlugin : IMixinConfigPlugin { //#else //$$ result.add("MixinEntryPointCatcher_UseCrashPatchGui") //$$ result.add("MixinInGameCatcher_UseCrashPatchGui") + //$$ result.add("MixinMinecraft_CrashPatchInitUI") + //$$ result.add("MixinMinecraft_CrashInitGui") + //#if FABRIC && MC>=1.20.4 + //$$ result.add("MixinModLoaders_Debug") + //#endif //#endif return result diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 809e20c..0fbd0fc 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -20,7 +20,8 @@ ] }, "mixins": [ - "mixins.${mod_id}.json" + "mixins.${mod_id}.json", + "mixins.${mod_id}.init.json" ], "depends": { "fabricloader": ">=0.15.11", diff --git a/src/main/resources/mixins.crashpatch.init.json b/src/main/resources/mixins.crashpatch.init.json new file mode 100644 index 0000000..098391a --- /dev/null +++ b/src/main/resources/mixins.crashpatch.init.json @@ -0,0 +1,7 @@ +{ + "minVersion": "0.8", + "package": "org.polyfrost.crashpatch.mixin", + "plugin": "org.polyfrost.crashpatch.plugin.EarlyMixinPlugin", + "target": "@env(INIT)", + "priority": 1000 +} \ No newline at end of file diff --git a/src/main/resources/mixins.crashpatch.json b/src/main/resources/mixins.crashpatch.json index 1e373b5..b93add0 100644 --- a/src/main/resources/mixins.crashpatch.json +++ b/src/main/resources/mixins.crashpatch.json @@ -1,5 +1,5 @@ { - "compatibilityLevel": "JAVA_8", + "compatibilityLevel": "${java_version}", "minVersion": "0.8", "package": "org.polyfrost.crashpatch.mixin", "plugin": "org.polyfrost.crashpatch.plugin.MixinPlugin", @@ -12,6 +12,7 @@ "MixinCrashReport", "MixinCrashReportCategory", "MixinGuiConnecting", + "MixinMinecraft_Debug", "MixinMinecraft_OverrideDisconnectedScreen", "MixinMinecraft_PreInitialize" ], diff --git a/versions/1.16.5-fabric/src/main/java/org/polyfrost/crashpatch/mixin/MixinModLoaders_Debug.java b/versions/1.16.5-fabric/src/main/java/org/polyfrost/crashpatch/mixin/MixinModLoaders_Debug.java new file mode 100644 index 0000000..add8fff --- /dev/null +++ b/versions/1.16.5-fabric/src/main/java/org/polyfrost/crashpatch/mixin/MixinModLoaders_Debug.java @@ -0,0 +1,28 @@ +package org.polyfrost.crashpatch.mixin; + +//#if FABRIC +import fudge.notenoughcrashes.fabric.mixinhandlers.ModLoaders; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.io.File; +import java.util.Objects; + +@Mixin(ModLoaders.class) +public class MixinModLoaders_Debug { + + @Inject(method = "fabricEntrypoints", at = @At("HEAD"), remap = false) + private static void debugEntrypointCrashes(File runDir, Object gameInstance, CallbackInfo ci) { + if (Objects.equals(System.getProperty("polyfrost.crashpatch.init_crash"), "true")) { + throw new RuntimeException("Crash requested by CrashPatch"); + } + } + + @Inject(method = "quiltEntrypoints", at = @At("HEAD"), remap = false) + private static void debugEntrypointCrashesQuilt(File runDir, Object gameInstance, CallbackInfo ci) { + debugEntrypointCrashes(runDir, gameInstance, ci); + } +} +//#endif \ No newline at end of file diff --git a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_CrashInitGui.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_CrashInitGui.java new file mode 100644 index 0000000..92c85ff --- /dev/null +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_CrashInitGui.java @@ -0,0 +1,22 @@ +package org.polyfrost.crashpatch.mixin; + +import net.minecraft.client.Minecraft; +import org.polyfrost.crashpatch.gui.CrashUI; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Objects; + +@Mixin(Minecraft.class) +public abstract class MixinMinecraft_CrashInitGui { + + @Inject(method = "runTick", at = @At("HEAD")) + private void crashInitGui(boolean bl, CallbackInfo ci) throws Throwable { + if (CrashUI.Companion.getCurrentUI() != null && CrashUI.Companion.getCurrentUI().getShouldCrash()) { + throw Objects.requireNonNull(CrashUI.Companion.getCurrentUI().getThrowable()); + } + } +} diff --git a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java index 10bfb14..375d033 100644 --- a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java @@ -1,18 +1,24 @@ package org.polyfrost.crashpatch.mixin; import net.minecraft.client.Minecraft; -import net.minecraft.client.main.GameConfig; import org.polyfrost.crashpatch.client.CrashPatchClient; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.ModifyArg; @Mixin(Minecraft.class) public class MixinMinecraft_PreInitialize { - @ModifyVariable(method = "", at = @At("STORE"), argsOnly = true, ordinal = 0) - private static GameConfig preInitialize(GameConfig v) { + // Random injection point that works across 1.16-1.21.x + @ModifyArg(method = "", at = @At(value = "INVOKE", target = + //#if MC<1.18 + "Lorg/apache/logging/log4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;)V" + //#else + //$$ "Lorg/slf4j/Logger;info(Ljava/lang/String;Ljava/lang/Object;)V" + //#endif + , remap = false, ordinal = 0), index = 0, remap = true) + private static String preInitialize(String par1) { CrashPatchClient.INSTANCE.preInitialize(); - return v; + return par1; } } diff --git a/versions/1.20.4-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_CrashPatchInitUI.java b/versions/1.20.4-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_CrashPatchInitUI.java new file mode 100644 index 0000000..9bb5065 --- /dev/null +++ b/versions/1.20.4-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_CrashPatchInitUI.java @@ -0,0 +1,49 @@ +package org.polyfrost.crashpatch.mixin; + +import com.bawnorton.mixinsquared.TargetHandler; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import fudge.notenoughcrashes.gui.InitErrorScreen; +import fudge.notenoughcrashes.mixinhandlers.EntryPointCatcher; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import org.polyfrost.crashpatch.gui.CrashUI; +import org.polyfrost.oneconfig.api.ui.v1.internal.wrappers.PolyUIScreen; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(value = Minecraft.class, priority = 1500) +public class MixinMinecraft_CrashPatchInitUI { + + /** + * If the game has crashed, we set the screen to the init crash screen, but then Minecraft sets the screen back + * to the title screen. We want to prevent that, to keep the screen to be the CrashUI + */ + @TargetHandler( + mixin = "fudge.notenoughcrashes.mixins.client.MixinMinecraftClient", + name = "setScreenDontResetCrashScreen", + prefix = "handler" + ) + @ModifyExpressionValue( + method = "@MixinSquared:Handler", + at = @At( + value = "INVOKE", + target = "Lfudge/notenoughcrashes/mixinhandlers/EntryPointCatcher;crashedDuringStartup()Z" + ) + ) + private boolean setScreenDontResetCrashScreen(boolean original) { + return false; + } + + @Inject( + method = "setScreen", + at = @At("HEAD"), + cancellable = true + ) + private void setScreenDontResetCrashScreen(Screen screen, CallbackInfo ci) { + if (EntryPointCatcher.crashedDuringStartup() && !(screen instanceof PolyUIScreen && screen == CrashUI.Companion.getCurrentInstance())) { + ci.cancel(); + } + } +} From ef80df39a95e9ca49e9680937739e28623e5c521 Mon Sep 17 00:00:00 2001 From: Wyvest Date: Sun, 17 Aug 2025 08:01:30 +0900 Subject: [PATCH 35/37] fix preinit for 1.8/1.12 --- .../crashpatch/mixin/MixinMinecraft_PreInitialize.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java index a7e4e64..d9567ff 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java @@ -10,7 +10,7 @@ @Mixin(Minecraft.class) public class MixinMinecraft_PreInitialize { - @Inject(method = "startGame", at = @At("HEAD")) + @Inject(method = "startGame", at = @At(value = "NEW", target = "(Lnet/minecraft/client/resources/IResourceManager;)Lnet/minecraft/client/renderer/texture/TextureManager;")) private void preInitialize(CallbackInfo ci) { CrashPatchClient.INSTANCE.preInitialize(); } From 5c3cf4dd450418f0073f6a0e12e8b7d639a4675f Mon Sep 17 00:00:00 2001 From: Wyvest Date: Sun, 17 Aug 2025 08:05:06 +0900 Subject: [PATCH 36/37] Add NEC as a dep in the FMJ --- src/main/resources/fabric.mod.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 0fbd0fc..7343b53 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -25,6 +25,7 @@ ], "depends": { "fabricloader": ">=0.15.11", - "fabric-language-kotlin": "*" + "fabric-language-kotlin": "*", + "notenoughcrashes": "*" } } \ No newline at end of file From 30ae881d15f2bc45ee479d588d21eb10960b2780 Mon Sep 17 00:00:00 2001 From: Deftu Date: Tue, 23 Sep 2025 20:04:49 +0200 Subject: [PATCH 37/37] OmniCore V1, cut versions, added 1.21.8 --- build.gradle.kts | 14 ++---- root.gradle.kts | 47 +++++++------------ settings.gradle.kts | 22 ++------- .../crashpatch/mixin/MixinGuiConnecting.java | 2 +- .../polyfrost/crashpatch/CrashPatchConfig.kt | 2 +- .../crashpatch/client/CrashPatchClient.kt | 6 +-- .../org/polyfrost/crashpatch/gui/CrashUI.kt | 7 ++- .../crashpatch/mixin/MixinGuiConnecting.java | 2 +- 8 files changed, 31 insertions(+), 71 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 88e9d5f..abebef6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,7 +23,7 @@ if (mcData.isForge) { toolkitLoomHelper { useOneConfig { - version = "1.0.0-alpha.134" + version = "1.0.0-alpha.151" loaderVersion = "1.1.0-alpha.48" usePolyMixin = true @@ -66,10 +66,6 @@ repositories { dependencies { implementation(includeOrShade("gs.mclo:api:3.0.1")!!) includeOrShade(implementation(annotationProcessor("com.github.bawnorton.mixinsquared:mixinsquared-common:0.3.3")!!)!!) - includeOrShade("io.github.llamalad7:mixinextras-common:0.5.0") - if (mcData.isFabric) { - includeOrShade(implementation("io.github.llamalad7:mixinextras-fabric:0.5.0")!!) - } if (mcData.version >= MinecraftVersions.VERSION_1_16) { includeOrShade(implementation("com.github.bawnorton.mixinsquared:mixinsquared-${mcData.loader}:0.3.3")!!) data class CompatDependency( @@ -98,16 +94,12 @@ dependencies { val nec = mapOf( nec("1.16.5", "4.1.4"), - nec("1.17.1", "4.1.4"), - nec("1.18.2", "4.2.0"), - nec("1.19.2", "5.0.0"), - nec("1.19.4", "4.4.1"), nec("1.20.1", "4.4.9"), nec("1.20.4", "4.4.7"), - nec("1.20.6", "4.4.7"), nec("1.21.1", "4.4.9"), nec("1.21.4", "4.4.8"), - nec("1.21.5", "4.4.9") + nec("1.21.5", "4.4.9"), + nec("1.21.8", "4.4.9"), ) modImplementationCompat(nec[mcData.version.toString()]) diff --git a/root.gradle.kts b/root.gradle.kts index c3988a4..9b34867 100644 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -10,40 +10,24 @@ preprocess { // "1.8.9-forge"(10809, "srg") // } - "1.21.5-fabric"(1_21_05, "yarn") { - "1.21.5-neoforge"(1_21_05, "srg") { - "1.21.4-neoforge"(1_21_04, "srg") { - "1.21.4-fabric"(1_21_04, "yarn") { - "1.21.1-fabric"(1_21_01, "yarn") { - "1.21.1-neoforge"(1_21_01, "srg") { - "1.20.6-neoforge"(1_20_06, "srg") { - "1.20.6-fabric"(1_20_06, "yarn") { + "1.21.8-fabric"(1_21_08, "yarn") { + "1.21.8-neoforge"(1_21_08, "srg") { + "1.21.5-neoforge"(1_21_05, "srg") { + "1.21.5-fabric"(1_21_05, "yarn") { + "1.21.4-fabric"(1_21_04, "yarn") { + "1.21.4-neoforge"(1_21_04, "srg") { + "1.21.1-neoforge"(1_21_01, "srg") { + "1.21.1-fabric"(1_21_01, "yarn") { "1.20.4-fabric"(1_20_04, "yarn") { "1.20.4-forge"(1_20_04, "srg") { "1.20.1-forge"(1_20_01, "srg") { "1.20.1-fabric"(1_20_01, "yarn") { - "1.19.4-fabric"(1_19_04, "yarn") { - "1.19.4-forge"(1_19_04, "srg") { - "1.19.2-forge"(1_19_02, "srg") { - "1.19.2-fabric"(1_19_02, "yarn") { - "1.18.2-fabric"(1_18_02, "yarn") { - "1.18.2-forge"(1_18_02, "srg") { - "1.17.1-forge"(1_17_01, "srg") { - "1.17.1-fabric"(1_17_01, "yarn") { - "1.16.5-fabric"(1_16_05, "yarn"){ - "1.16.5-forge"(1_16_05, "srg") { - "1.12.2-forge"(1_12_02, "srg", rootProject.file("versions/1.16.5-1.8.9.txt")) { - "1.12.2-fabric"(1_12_02, "yarn") { - "1.8.9-fabric"(1_08_09, "yarn"){ - "1.8.9-forge"(1_08_09, "srg") - } - } - } - } - } - } - } - } + "1.16.5-fabric"(1_16_05, "yarn") { + "1.16.5-forge"(1_16_05, "srg") { + "1.12.2-forge"(1_12_02, "srg", rootProject.file("versions/1.16.5-1.8.9.txt")) { + "1.12.2-fabric"(1_12_02, "yarn") { + "1.8.9-fabric"(1_08_09, "yarn") { + "1.8.9-forge"(1_08_09, "srg") } } } @@ -62,5 +46,6 @@ preprocess { } } + strictExtraMappings.set(true) -} \ No newline at end of file +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 129335a..cedac35 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,7 +24,7 @@ pluginManagement { plugins { kotlin("jvm") version("2.0.0") - id("dev.deftu.gradle.multiversion-root") version("2.49.0") + id("dev.deftu.gradle.multiversion-root") version("2.52.0") } } @@ -49,27 +49,12 @@ listOf( "1.16.5-forge", "1.16.5-fabric", - "1.17.1-forge", - "1.17.1-fabric", - - "1.18.2-forge", - "1.18.2-fabric", - - "1.19.2-forge", - "1.19.2-fabric", - - "1.19.4-forge", - "1.19.4-fabric", - "1.20.1-forge", "1.20.1-fabric", "1.20.4-forge", "1.20.4-fabric", - "1.20.6-neoforge", - "1.20.6-fabric", - "1.21.1-neoforge", "1.21.1-fabric", @@ -77,7 +62,10 @@ listOf( "1.21.4-fabric", "1.21.5-neoforge", - "1.21.5-fabric" + "1.21.5-fabric", + + "1.21.8-neoforge", + "1.21.8-fabric", ).forEach { version -> include(":$version") project(":$version").apply { diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java index 1ed0a54..9ab3ada 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -1,6 +1,6 @@ package org.polyfrost.crashpatch.mixin; -import dev.deftu.omnicore.client.OmniDesktop; +import dev.deftu.omnicore.api.client.OmniDesktop; import dev.deftu.textile.minecraft.MCTextFormat; import org.polyfrost.crashpatch.client.CrashPatchClient; import org.polyfrost.crashpatch.hooks.MinecraftHook; diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt index c7def60..76c9a2c 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt @@ -1,6 +1,6 @@ package org.polyfrost.crashpatch -import dev.deftu.omnicore.client.OmniDesktop +import dev.deftu.omnicore.api.client.OmniDesktop import org.polyfrost.oneconfig.api.config.v1.Config import org.polyfrost.oneconfig.api.config.v1.annotations.* import java.net.URI diff --git a/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt b/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt index 59cd26f..b17e985 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt @@ -1,6 +1,5 @@ package org.polyfrost.crashpatch.client -import dev.deftu.omnicore.client.OmniClientCommands import dev.deftu.textile.minecraft.MCSimpleTextHolder import dev.deftu.textile.minecraft.MCTextFormat import org.apache.logging.log4j.LogManager @@ -58,8 +57,6 @@ object CrashPatchClient { } fun initialize() { - OmniClientCommands.initialize() - CommandManager.register(with(CommandManager.literal(CrashPatchConstants.ID)) { executes { CrashPatchConfig.openUI() @@ -73,8 +70,7 @@ object CrashPatchClient { MCSimpleTextHolder("[${CrashPatchConstants.NAME}] Failed to reload JSON file!").withFormatting(MCTextFormat.Companion.RED) } - ctx.source.displayMessage(text) - 1 + ctx.source.replyChat(text) }) then(CommandManager.literal("crash").executes { ctx -> diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt index d700bdc..b417f96 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt @@ -1,12 +1,11 @@ package org.polyfrost.crashpatch.gui import dev.deftu.clipboard.Clipboard -import dev.deftu.omnicore.client.OmniDesktop -import dev.deftu.omnicore.client.OmniScreen +import dev.deftu.omnicore.api.client.OmniDesktop +import dev.deftu.omnicore.api.client.screen.closeScreen import net.minecraft.client.gui.GuiScreen import net.minecraft.crash.CrashReport import org.polyfrost.crashpatch.CrashPatchConstants -import org.polyfrost.crashpatch.client.CrashPatchClient import org.polyfrost.crashpatch.crashes.CrashScanStorage import org.polyfrost.crashpatch.crashes.CrashScan import org.polyfrost.crashpatch.hooks.CrashReportHook @@ -247,7 +246,7 @@ class CrashUI @JvmOverloads constructor( if (type == GuiType.INIT) { shouldCrash = true } else { - OmniScreen.closeScreen() + closeScreen() } }.setPalette { brand.fg }, Button( diff --git a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java index 047885d..7a0eca0 100644 --- a/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -1,7 +1,7 @@ package org.polyfrost.crashpatch.mixin; import com.mojang.blaze3d.vertex.PoseStack; -import dev.deftu.omnicore.client.OmniDesktop; +import dev.deftu.omnicore.api.client.OmniDesktop; import dev.deftu.textile.minecraft.MCTextFormat; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FormattedText;