diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d4a420f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,51 @@ +# Build Workflow + +name: Build with Gradle + +on: + pull_request: + workflow_dispatch: + push: + +concurrency: + group: ${{ github.head_ref || format('{0}-{1}', github.ref, github.run_number) }} + cancel-in-progress: true + +jobs: + build: + name: Build + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 10 + + - name: Set up JDK 8, 16, 17, 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: | + 8 + 16 + 17 + 21 + + - 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- + + - name: Chmod Gradle + run: chmod +x ./gradlew + + - name: Build + run: ./gradlew build --no-daemon diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 1de3da8..0000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: build - -on: - push: - branches: '*' - paths-ignore: - - 'README.md' - - 'LICENSE' - - '.gitignore' - pull_request: - branches: '*' - paths-ignore: - - 'README.md' - - 'LICENSE' - - '.gitignore' - workflow_dispatch: - -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" - - steps: - - uses: actions/checkout@v2 - - - uses: gradle/wrapper-validation-action@v1 - - - uses: actions/setup-java@v2 - with: - distribution: "temurin" - java-version: "17" - - - uses: actions/cache@v2 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - 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 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 diff --git a/build.gradle.kts b/build.gradle.kts index a3b3415..abebef6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,190 +1,121 @@ @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 +import dev.deftu.gradle.utils.includeOrShade +import dev.deftu.gradle.utils.version.MinecraftVersion +import dev.deftu.gradle.utils.version.MinecraftVersions -// 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") // 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.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. } -// 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 - -// 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) +if (mcData.isForge) { + loom.forge.mixinConfig("mixins.crashpatch.init.json") } -// 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" +toolkitLoomHelper { + useOneConfig { + version = "1.0.0-alpha.151" + loaderVersion = "1.1.0-alpha.48" -// 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") -} + usePolyMixin = true + polyMixinVersion = "0.8.4+build.6" -// 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) { - runConfigs { - "client" { - programArgs("--tweakClass", "org.polyfrost.crashpatch.hooks.ModsCheckerPlugin") - //property("fml.coreMods.load", "") - property("mixin.debug.export", "true") - } - } - } - // 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") + applyLoaderTweaker = true + + for (module in arrayOf("commands", "config", "config-impl", "events", "internal", "hud", "ui", "utils")) { + +module } } - // 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) -} + useMixinExtras("0.5.0") -// Configures the output directory for when building from the `src/resources` directory. -sourceSets { - main { - output.setResourcesDir(java.classesDirectory) + 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) + + // 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 Polyfrost maven repository so that we can get the libraries necessary to develop the mod. -repositories { - maven("https://repo.polyfrost.org/releases") + if (mcData.isForge) { + // Configures the Mixin tweaker if we are building for Forge. + useForgeMixin(modData.id) + } } -// 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+") - - 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") +repositories { + maven("https://api.modrinth.com/maven") { + content { includeGroup("maven.modrinth") } + } + maven("https://maven.bawnorton.com/releases") { + content { includeGroup("com.github.bawnorton.mixinsquared") } } } -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 - ) - ) +dependencies { + implementation(includeOrShade("gs.mclo:api:3.0.1")!!) + includeOrShade(implementation(annotationProcessor("com.github.bawnorton.mixinsquared:mixinsquared-common:0.3.3")!!)!!) + 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, + 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}") + } + } } - 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" - ) + + 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" ) - } - } - // 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") - } - } + val nec = mapOf( + nec("1.16.5", "4.1.4"), + nec("1.20.1", "4.4.9"), + nec("1.20.4", "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.8", "4.4.9"), + ) + + modImplementationCompat(nec[mcData.version.toString()]) } +} - // 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) +tasks { + jar { 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. - ) + manifest { + attributes(mapOf( + "MixinConfigs" to "mixins.crashpatch.init.json,mixins.crashpatch.json", + )) } - dependsOn(shadowJar) - archiveClassifier.set("") - enabled = false } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 5c335ba..95bb22e 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.1.0-alpha.1 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 7431fb5..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.4-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 0234352..9b34867 100644 --- a/root.gradle.kts +++ b/root.gradle.kts @@ -1,9 +1,51 @@ 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 { - "1.8.9-forge"(10809, "srg") {} -} \ No newline at end of file + // 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.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.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") + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + + + strictExtraMappings.set(true) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 6973a98..cedac35 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,30 +1,75 @@ @file:Suppress("PropertyName") +import groovy.lang.MissingPropertyException + pluginManagement { repositories { + // Releases + 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 gradlePluginPortal() mavenCentral() - 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 - id("org.polyfrost.multi-version.root") version pgtVersion + kotlin("jvm") version("2.0.0") + id("dev.deftu.gradle.multiversion-root") version("2.52.0") } } -val mod_name: String by settings +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 = mod_name +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-forge", + "1.8.9-fabric", + + "1.12.2-forge", + "1.12.2-fabric", + + "1.16.5-forge", + "1.16.5-fabric", + + "1.20.1-forge", + "1.20.1-fabric", + + "1.20.4-forge", + "1.20.4-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", + + "1.21.8-neoforge", + "1.21.8-fabric", ).forEach { version -> include(":$version") project(":$version").apply { projectDir = file("versions/$version") buildFileName = "../../build.gradle.kts" } -} \ No newline at end of file +} 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 bb00501..0000000 --- a/src/main/java/org/polyfrost/crashpatch/hooks/ModsCheckerPlugin.java +++ /dev/null @@ -1,353 +0,0 @@ -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; -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 extends LaunchWrapperTweaker { - private static final JsonParser PARSER = new JsonParser(); - public static final HashMap> modsMap = new HashMap<>(); //modid : file, version, name - - @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(); - } - - 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); - } - - 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/hooks/StacktraceDeobfuscator.java b/src/main/java/org/polyfrost/crashpatch/hooks/StacktraceDeobfuscator.java index 5aa19d9..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; @@ -8,6 +10,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 +24,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(); @@ -103,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 9335159..76f7fb6 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinCrashReport.java @@ -6,11 +6,10 @@ 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; @@ -30,14 +29,22 @@ 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) { - ModContainer 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 86acce2..9ab3ada 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -1,14 +1,14 @@ package org.polyfrost.crashpatch.mixin; -import cc.polyfrost.oneconfig.libs.universal.ChatColor; -import cc.polyfrost.oneconfig.libs.universal.UDesktop; -import org.polyfrost.crashpatch.CrashPatch; +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; 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; +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; @@ -16,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 { @@ -23,123 +24,43 @@ 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() { - 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" : "") + "."; + @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 - 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) { - String[] list = wrapFormattedStringToWidth(getText(), width).split("\n"); + List list = this.fontRendererObj.listFormattedStringToWidth(crashpatch$getText(), width); 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)))) { - UDesktop.browse(URI.create("https://discord.gg/eh7tNFezct")); + 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")); } } } } - public void drawSplitCenteredString(String text, int x, int y, int color) { - for (String line : 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); - - if (str.length() <= i) - { - return str; + @Unique + public void crashpatch$drawSplitCenteredString(String text, int x, int y, int color) { + for (String line : this.fontRendererObj.listFormattedStringToWidth(text, width)) { + drawCenteredString(this.fontRendererObj, line, x, y, color); + y += this.fontRendererObj.FONT_HEIGHT + 2; } - 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); - } - } - - private int 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 += 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 || 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; - } - - private static boolean 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 0227110..74bbda9 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiDupesFound.java @@ -1,17 +1,19 @@ package org.polyfrost.crashpatch.mixin; -import cc.polyfrost.oneconfig.libs.universal.UDesktop; -import net.minecraft.client.Minecraft; +//#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.client.CrashPatchClient; 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; import net.minecraftforge.fml.common.ModContainer; -import org.polyfrost.crashpatch.CrashPatchKt; 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; @@ -30,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")); } @@ -40,7 +48,7 @@ private void onInit(CallbackInfo ci) { protected void actionPerformed(GuiButton button) { switch (button.id) { case 0: - UDesktop.open(new File(CrashPatchKt.getMcDir(), "mods")); + OmniDesktop.open(new File(CrashPatchClient.getMcDir(), "mods")); break; case 1: FMLCommonHandler.instance().exitJava(0, false); @@ -53,39 +61,47 @@ 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 " + CrashPatchKt.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); + 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); } } - 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); - 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; } - 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; } + } +//#endif diff --git a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java index 91b6d7f..15a3e6d 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft.java @@ -1,26 +1,20 @@ package org.polyfrost.crashpatch.mixin; +//#if MC<1.13 +//#if FORGE +import net.minecraftforge.fml.client.SplashProgress; +import net.minecraftforge.fml.common.FMLCommonHandler; +//#endif -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; import net.minecraft.client.resources.IReloadableResourceManager; import net.minecraft.client.resources.LanguageManager; @@ -29,22 +23,21 @@ 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; -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; import org.lwjgl.opengl.Display; -import org.lwjgl.opengl.GL11; -import org.lwjgl.opengl.GL12; -import org.lwjgl.opengl.GL14; -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.polyfrost.crashpatch.client.CrashPatchClient; +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.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; @@ -55,6 +48,7 @@ import java.util.Queue; import java.util.concurrent.FutureTask; +@SuppressWarnings("AccessStaticViaInstance") @Mixin(value = Minecraft.class, priority = -9000) public abstract class MixinMinecraft implements MinecraftHook { @@ -110,9 +104,6 @@ public abstract class MixinMinecraft implements MinecraftHook { @Shadow protected abstract void runGameLoop(); - @Shadow - public abstract void freeMemory(); - @Shadow public abstract void shutdownMinecraftApplet(); @@ -164,7 +155,7 @@ public void run(CallbackInfo ci) { } try { while (running) { - if (!hasCrashed || crashReporter == null) { + if (!this.hasCrashed || this.crashReporter == null) { try { runGameLoop(); } catch (ReportedException e) { @@ -172,7 +163,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++; @@ -180,16 +171,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) { @@ -198,11 +189,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 */ @@ -211,7 +197,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); @@ -219,19 +205,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 CrashGui(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); } @@ -242,56 +228,83 @@ 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; 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) { } StateManager.INSTANCE.resetStates(); - 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); + 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")); } 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(); + GlUtil.INSTANCE.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(); + 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 */ @@ -301,39 +314,42 @@ 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); - 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; + this.running = true; try { + //#if FORGE //noinspection deprecation SplashProgress.pause();// Disable the forge splash progress screen + //#endif GlStateManager.disableTexture2D(); GlStateManager.enableTexture2D(); } catch (Throwable ignored) { } - crashpatch$runGUILoop(new CrashGui(report, CrashGui.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); + this.logger.error("An uncaught exception occured while displaying the init error screen, making normal report instead", t); crashpatch$letDie = true; } displayCrashReport(report); @@ -343,23 +359,24 @@ private void onGUIDisplay(GuiScreen i, CallbackInfo ci) { /** * @author Runemoro */ - private void crashpatch$runGUILoop(CrashGui screen) throws Throwable { + 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(Stage.START, 0)); - leftClickCounter = 10000; - currentScreen.handleInput(); - currentScreen.updateScreen(); + //EventManager.INSTANCE.post(new RenderEvent.Start()); todo + 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); @@ -373,23 +390,23 @@ 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); - if (screen.getShouldCrash()) { + this.currentScreen.drawScreen(mouseX, mouseY, 0); + if (crashUI.getShouldCrash()) { crashpatch$letDie = true; - throw Objects.requireNonNull(screen.getThrowable()); + 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)); + //EventManager.INSTANCE.post(new RenderEvent(Stage.END, 0)); todo updateDisplay(); Thread.yield(); @@ -398,68 +415,16 @@ 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() { - 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); - } } - - +//#endif \ No newline at end of file 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/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/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..d9567ff --- /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(value = "NEW", target = "(Lnet/minecraft/client/resources/IResourceManager;)Lnet/minecraft/client/renderer/texture/TextureManager;")) + 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 acc1c29..f9625ef 100644 --- a/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java +++ b/src/main/java/org/polyfrost/crashpatch/mixin/MixinTileEntityRendererDispatcher.java @@ -1,9 +1,12 @@ package org.polyfrost.crashpatch.mixin; +//#if MC < 1.13 +//#if FORGE import org.polyfrost.crashpatch.crashes.StateManager; 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; @@ -14,7 +17,14 @@ //TODO: this could be completely useless, check with smarter people @Mixin(TileEntityRendererDispatcher.class) public abstract class MixinTileEntityRendererDispatcher implements StateManager.IResettable { - private boolean drawingBatch = false; + + //#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) { @@ -23,12 +33,18 @@ public void onInit(CallbackInfo ci) { @Override public void resetState() { - if (drawingBatch) drawingBatch = false; + //#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 (!drawingBatch) { + if (!crashpatch$drawingBatch) { return false; } else { return instance.hasFastRenderer(); @@ -37,16 +53,20 @@ 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 } +//#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 f0f363d..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; @@ -24,6 +24,9 @@ private void onInitEnd(int bufferSizeIn, CallbackInfo ci) { @Override public void resetState() { - if (isDrawing) finishDrawing(); + if (this.isDrawing) { + finishDrawing(); + } } } +//#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/CrashPatch.kt deleted file mode 100644 index b916902..0000000 --- a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatch.kt +++ /dev/null @@ -1,94 +0,0 @@ -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.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 java.io.File - - -@Mod(modid = CrashPatch.MODID, version = CrashPatch.VERSION, name = CrashPatch.NAME, modLanguageAdapter = "cc.polyfrost.oneconfig.utils.KotlinLanguageAdapter") -object CrashPatch { - const val MODID = "@ID@" - const val NAME = "@NAME@" - const val VERSION = "@VER@" - 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" } } - - @Mod.EventHandler - fun onPreInit(e: FMLPreInitializationEvent) { - DeobfuscatingRewritePolicy.install() - Multithreading.runAsync { - logger.info("Is SkyClient: $isSkyclient") - if (!CrashHelper.loadJson()) { - logger.error("CrashHelper failed to preload crash data JSON!") - } - } - } - - @Mod.EventHandler - fun onInit(e: FMLInitializationEvent) { - CommandManager.INSTANCE.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") - class CrashPatchCommand { - @Main - fun main() { - CrashPatchConfig.openGui() - } - - @SubCommand - 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!")) - } - } - - 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) -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 - } -} -val mcDir = File(System.getProperty("user.dir")) \ No newline at end of file diff --git a/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt new file mode 100644 index 0000000..76c9a2c --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/CrashPatchConfig.kt @@ -0,0 +1,85 @@ +package org.polyfrost.crashpatch + +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 + +object CrashPatchConfig : Config("crashpatch.json", "/assets/crashpatch/crashpatch_dark.svg", "CrashPatch", Category.QOL) { + + // Toggles + @Switch( + title = "Catch crashes during gameplay", + description = "Catch crashes whilst in-game, and prevent the game from closing", + subcategory = "Patches" + ) + var inGameCrashPatch = true + + @Switch( + title = "Patch crashes during launch", + description = "Catch crashes during initialization, & display a message.", + subcategory = "Patches" + ) + var initCrashPatch = true + + @Switch( + title = "Display disconnection causes", + description = "Display a message when a reason is found for a disconnect.", + subcategory = "Patches" + ) + var disconnectCrashPatch = true + + @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", + min = 1f, + max = 20f, + step = 1F, + subcategory = "Limits" + ) + var leaveLimit = 3 + + @Slider( + title = "Crash Limit", + min = 1f, + max = 20f, + step = 1F, + subcategory = "Limits" + ) + var crashLimit = 5 + + @Switch( + title = "Deobfuscate Crash Log", + description = "Makes certain class names more readable through deobfuscation", + subcategory = "Logs" + ) + var deobfuscateCrashLog = true + + @Dropdown( + title = "Log uploader", + description = "The method used to upload the crash log.", + subcategory = "Logs" + ) + var crashLogUploadMethod = UploadMethod.HASTEBIN + + @Button( + title = "Polyfrost support", + text = "Discord" + ) + fun supportDiscord() { + OmniDesktop.browse(URI.create("https://polyfrost.org/discord")) + } + + enum class UploadMethod(val text: String) { + HASTEBIN("hst.sh"), + MCLOGS("mclo.gs (Aternos)") + } + +} \ 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/client/CrashPatchClient.kt b/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt new file mode 100644 index 0000000..b17e985 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/client/CrashPatchClient.kt @@ -0,0 +1,85 @@ +package org.polyfrost.crashpatch.client + +import dev.deftu.textile.minecraft.MCSimpleTextHolder +import dev.deftu.textile.minecraft.MCTextFormat +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.oneconfig.api.commands.v1.CommandManager +import org.polyfrost.oneconfig.utils.v1.Multithreading +import org.polyfrost.oneconfig.utils.v1.dsl.openUI +import java.io.File + +object CrashPatchClient { + + 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 -> + 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() + } + + var requestedCrash = false + + fun preInitialize() { + //#if MC<1.13 + org.polyfrost.crashpatch.crashes.DeobfuscatingRewritePolicy.install() + //#endif + Multithreading.submit { + logger.info("Is SkyClient: $isSkyclient") + if (!CrashScanStorage.downloadJson()) { + 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() { + CommandManager.register(with(CommandManager.literal(CrashPatchConstants.ID)) { + executes { + CrashPatchConfig.openUI() + 1 + } + + then(CommandManager.literal("reload").executes { ctx -> + val text = if (CrashScanStorage.downloadJson()) { + MCSimpleTextHolder("[${CrashPatchConstants.NAME}] Successfully reloaded JSON file!").withFormatting(MCTextFormat.Companion.GREEN) + } else { + MCSimpleTextHolder("[${CrashPatchConstants.NAME}] Failed to reload JSON file!").withFormatting(MCTextFormat.Companion.RED) + } + + ctx.source.replyChat(text) + }) + + then(CommandManager.literal("crash").executes { ctx -> + requestedCrash = true + 1 + }) + }) + + CrashPatchConfig // Initialize the config + } + +} \ 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 deleted file mode 100644 index 93ffad5..0000000 --- a/src/main/kotlin/org/polyfrost/crashpatch/config/CrashPatchConfig.kt +++ /dev/null @@ -1,88 +0,0 @@ -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 java.net.URI - - -object CrashPatchConfig : Config(Mod("CrashPatch", ModType.UTIL_QOL, "/assets/crashpatch/crashpatch_dark.svg"), "crashpatch.json") { - - // Toggles - @Switch( - name = "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", - description = "Catch crashes during initialization, & display a message.", - subcategory = "Patches" - ) - var initCrashPatch = true - - @Switch( - name = "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 - - @Slider( - name = "World Leave Limit", - min = 1f, - max = 20f, - step = 1, - subcategory = "Limits" - ) - var leaveLimit = 3 - - @Slider( - name = "Crash Limit", - min = 1f, - max = 20f, - step = 1, - subcategory = "Limits" - ) - var crashLimit = 5 - - @Switch( - name = "Deobfuscate Crash Log", - description = "Makes certain class names more readable through deobfuscation", - size = 2, - subcategory = "Logs" - ) - var deobfuscateCrashLog = true - - @Dropdown( - name = "Log uploader", - description = "The method used to upload the crash log.", - options = ["hst.sh", "mclo.gs (Aternos)"], - subcategory = "Logs" - ) - var crashLogUploadMethod = 0 - - @Button( - name = "Polyfrost support", - text = "Discord" - ) - var supportDiscord = Runnable { browse(URI.create("https://polyfrost.cc/discord/")) } - - init { - initialize() - } -} \ 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/CrashScanStorage.kt similarity index 73% rename from src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt rename to src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt index fef026c..c204e61 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashHelper.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/CrashScanStorage.kt @@ -1,54 +1,64 @@ package org.polyfrost.crashpatch.crashes -import cc.polyfrost.oneconfig.libs.universal.wrappers.message.UTextComponent -import cc.polyfrost.oneconfig.utils.NetworkUtils -import org.polyfrost.crashpatch.gameDir -import org.polyfrost.crashpatch.mcDir import com.google.gson.JsonObject +import dev.deftu.textile.minecraft.MCTextFormat +import org.apache.logging.log4j.LogManager +import org.polyfrost.crashpatch.client.CrashPatchClient +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(CrashPatchClient.mcDir, "OneConfig/CrashPatch/cache.json") + } + + private val String.mappedPlaceholders: String + get() = this + .replace("%pathindicator%", "") + .replace("%gameroot%", CrashPatchClient.gameDir.absolutePath.removeSuffix(File.separator)) + .replace("%profileroot%", File(CrashPatchClient.mcDir, "OneConfig").parentFile.absolutePath.removeSuffix(File.separator)) + + private var skyclientData: JsonObject? = null @JvmStatic - fun loadJson(): Boolean { + fun downloadJson(): Boolean { return try { - skyclientJson = - NetworkUtils.getJsonElement("https://raw.githubusercontent.com/SkyblockClient/CrashData/main/crashes.json").asJsonObject + skyclientData = JsonUtils.parseFromUrl("https://raw.githubusercontent.com/Polyfrost/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 @@ -56,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() @@ -76,6 +86,7 @@ object CrashHelper { } else { triggersToIgnore.add(index) } + responses[type["name"].asString] = arrayListOf() } @@ -85,19 +96,22 @@ 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) { val causeJson = cause.asJsonObject var theReport = report if (causeJson.has("unformatted") && causeJson["unformatted"].asBoolean) { - theReport = UTextComponent.stripFormatting(theReport) + theReport = MCTextFormat.strip(theReport) } + when (causeJson["method"].asString) { "contains" -> { if (theReport.contains(causeJson["value"].asString)) { @@ -136,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/crashes/DeobfuscatingRewritePolicy.kt b/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt index d178d49..2955d64 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/crashes/DeobfuscatingRewritePolicy.kt @@ -1,6 +1,6 @@ package org.polyfrost.crashpatch.crashes -import org.polyfrost.crashpatch.config.CrashPatchConfig +//#if MC<1.13 import org.polyfrost.crashpatch.hooks.StacktraceDeobfuscator import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.core.LogEvent @@ -9,12 +9,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) } } + return source } @@ -45,3 +47,4 @@ class DeobfuscatingRewritePolicy : RewritePolicy { } } } +//#endif \ No newline at end of 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/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/CrashGui.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGui.kt deleted file mode 100644 index 53708e9..0000000 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashGui.kt +++ /dev/null @@ -1,454 +0,0 @@ -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 diff --git a/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt new file mode 100644 index 0000000..b417f96 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/CrashUI.kt @@ -0,0 +1,285 @@ +package org.polyfrost.crashpatch.gui + +import dev.deftu.clipboard.Clipboard +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.crashes.CrashScanStorage +import org.polyfrost.crashpatch.crashes.CrashScan +import org.polyfrost.crashpatch.hooks.CrashReportHook +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 +import org.polyfrost.polyui.color.PolyColor +import org.polyfrost.polyui.color.rgba +import org.polyfrost.polyui.component.extensions.* +import org.polyfrost.polyui.component.impl.* +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.seconds +import org.polyfrost.polyui.utils.image +import org.polyfrost.polyui.utils.mapToArray +import java.io.File +import java.net.URI +import java.util.function.Consumer + +class CrashUI @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 + //#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 + ) + + 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 { + return@lazy CrashScanStorage.scanReport(scanText, type == GuiType.DISCONNECT) + .let { return@let if (it != null && it.solutions.isNotEmpty()) it else null } + } + var shouldCrash = false + + fun create(): GuiScreen { + val builder = OCPolyUIBuilder.create() + .blurs() + .atResolution(1920f, 1080f) + .backgroundColor(rgba(21, 21, 21)) + .size(650f, 600f) + .renderer(UIManager.INSTANCE.renderer).translatorDelegate("assets/crashpatch") + + val onClose = Consumer { _: PolyUI -> + leaveWorldCrash = false + } + + var selectedSolution: CrashScan.Solution? = crashScan?.solutions?.firstOrNull() + var block: Block? = null + + val polyUI = builder.make( + Group( + Image("/assets/crashpatch/WarningTriangle.svg".image(), size = Vec2(20F, 20F)).named("WarningTriangle") + .padded( + 0F, + 34F, + 0F, + 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) "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 } + .padded(0f, 8f, 0f, 0f), + + Block( + Block( + // Selector + 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 + + 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 -> + val rightPad = when { + solution == crashScan?.solutions?.last() -> 0f + else -> 24f + } + + Text(solution.name) + .setFont { PolyUI.defaultFonts.medium } + .padded(0f, 0f, rightPad, 0f) + .onClick { + selectedSolution = solution + + 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(12f, 0f, 0f, 0f), + + // Buttons + Group( + 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, CrashPatchConstants.NAME, "Copied to clipboard!") + } + } + + selectedSolution != null + }.setPalette { createCustomButtonPalette(GRAY_600) }, + Button(leftImage = "/assets/crashpatch/upload.svg".image()).onClick { + selectedSolution?.let { solution -> + val link = UploadUtils.upload(solution.solutions.joinToString(separator = "") { it + "\n" } + "\n\n" + (if (!solution.crashReport) scanText else "")) + Clipboard.getInstance().setString(link) + + if (OmniDesktop.browse(URI.create(link))) { + Notifications.enqueue(Notifications.Type.Success, CrashPatchConstants.NAME, "Link copied to clipboard and opened in browser") + } else { + Notifications.enqueue(Notifications.Type.Warning, CrashPatchConstants.NAME, "Couldn't open link in browser, copied to clipboard instead.") + } + } + + selectedSolution != null + }.setPalette { createCustomButtonPalette(GRAY_600) }, + ), + + alignment = Align( + pad = Vec2.ZERO, + main = Align.Content.SpaceBetween, + mode = Align.Mode.Horizontal + ), + size = Vec2(550f, 37f), + color = GRAY_600 + ).radii(8f, 8f, 0f, 0f), + + // Selected solution goes here... + 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.Content.Start, + cross = Align.Content.Start, + mode = Align.Mode.Vertical, + pad = Vec2.ZERO + ), + size = Vec2(550f, 158f), + color = GRAY_700 + ).also { block = it }.padded(0f, 40f, 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("crashpatch.link.discord.polyfrost", fontSize = 16f).setPalette { brand.fg }.padded(4f, 0f, 0f, 0f), + ).onClick { + OmniDesktop.browse(URI.create("crashpatch.link.discord.polyfrost")) + true + }, + + // Buttons + Group( + Button(text = "crashpatch.continue", padding = Vec2(14f, 14f)).onClick { + if (type == GuiType.INIT) { + shouldCrash = true + } else { + closeScreen() + } + }.setPalette { brand.fg }, + Button( + text = "crashpatch.log", + rightImage = "/assets/crashpatch/open-external.svg".image(), + padding = Vec2(14f, 14f) + ).onClick { + file?.let { OmniDesktop.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) + ), + ) + + val screen = UIManager.INSTANCE.createPolyUIScreen(polyUI, 1920f, 1080f, false, true, onClose) + polyUI.window = UIManager.INSTANCE.createWindow() + currentUI = this + currentInstance = screen as GuiScreen + return screen + } + + 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(normal, GRAY_700, GRAY_700, PolyColor.TRANSPARENT) + + 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 91aff29..541f792 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/gui/constants.kt @@ -1,38 +1,6 @@ package org.polyfrost.crashpatch.gui -import cc.polyfrost.oneconfig.renderer.font.Font -import cc.polyfrost.oneconfig.utils.color.ColorUtils +import org.polyfrost.polyui.color.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 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 BLUE_400 = ColorUtils.getColor(77, 135, 229) // yeah -internal val BLUE_600 = ColorUtils.getColor(20, 82, 204, 255) // brand.hover - -internal val HYPERLINK_BLUE = ColorUtils.getColor(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 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 GRAY_700 = rgba(34, 35, 38) // log background +internal val GRAY_600 = rgba(42, 44, 48) // log header 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..83be181 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/identifier/ModIdentifier.kt @@ -0,0 +1,194 @@ +package org.polyfrost.crashpatch.identifier +//#if MC<1.13 +//#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 net.minecraft.crash.CrashReport +import java.io.File +import org.apache.logging.log4j.LogManager + +private typealias ModMap = Map> + +object ModIdentifier { + + private val logger = LogManager.getLogger() + + fun identifyFromStacktrace(crashReport: CrashReport, 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 + +} +//#endif \ No newline at end of file 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/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 new file mode 100644 index 0000000..bf4d732 --- /dev/null +++ b/src/main/kotlin/org/polyfrost/crashpatch/plugin/MixinPlugin.kt @@ -0,0 +1,62 @@ +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 { + val result = mutableListOf() + + //#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") + //$$ result.add("MixinMinecraft_CrashPatchInitUI") + //$$ result.add("MixinMinecraft_CrashInitGui") + //#if FABRIC && MC>=1.20.4 + //$$ result.add("MixinModLoaders_Debug") + //#endif + //#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, + targetClass: ClassNode, + mixinClassName: String, + mixinInfo: IMixinInfo + ) { + // no-op + } + + override fun postApply( + targetClassName: String, + targetClass: ClassNode, + mixinClassName: String, + mixinInfo: IMixinInfo + ) { + // no-op + } + +} \ No newline at end of file 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..ba67fc0 --- /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-fabric source set, removed in 1.16 and later +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/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt index 7e9b395..c7650b4 100644 --- a/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt +++ b/src/main/kotlin/org/polyfrost/crashpatch/utils/GuiDisconnectedHook.kt @@ -1,23 +1,30 @@ 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 +import org.polyfrost.crashpatch.CrashPatchConfig 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.CrashUI +import org.polyfrost.oneconfig.utils.v1.dsl.mc object GuiDisconnectedHook { - fun onGUIDisplay(i: GuiScreen?, ci: CallbackInfo) { - if (i is GuiDisconnected && CrashPatchConfig.disconnectCrashPatch) { - val gui = i as AccessorGuiDisconnected - val scan = scanReport(gui.message.formattedText, true) + + @JvmStatic + fun onGUIDisplay(screen: GuiScreen?, ci: CallbackInfo) { + if (screen is GuiDisconnected && CrashPatchConfig.disconnectCrashPatch) { + 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(CrashGui(gui.message.formattedText, null, gui.reason, CrashGui.GuiType.DISCONNECT)) + mc.displayGuiScreen(CrashUI(reason, null, 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 71% rename from src/main/kotlin/org/polyfrost/crashpatch/utils/InternetUtils.kt rename to src/main/kotlin/org/polyfrost/crashpatch/utils/UploadUtils.kt index 2d14210..8dd60a2 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 org.polyfrost.crashpatch.CrashPatchConstants 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", CrashPatchConstants.VERSION, "1.8.9") + } + + private val sessionIdRegex = Regex("((Session ID is|--accessToken|Your new API key is) (\\S+))") -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,12 +34,12 @@ 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 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 @@ -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 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 dff66cc..0000000 Binary files a/src/main/resources/assets/crashpatch/fonts/JetBrainsMono-Regular.ttf and /dev/null differ diff --git a/src/main/resources/assets/crashpatch/fonts/OFL.txt b/src/main/resources/assets/crashpatch/fonts/OFL.txt deleted file mode 100644 index 8bee414..0000000 --- a/src/main/resources/assets/crashpatch/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -https://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..7343b53 --- /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" + ], + "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": [ + { + "value": "org.polyfrost.crashpatch.CrashPatchEntrypoint" + } + ] + }, + "mixins": [ + "mixins.${mod_id}.json", + "mixins.${mod_id}.init.json" + ], + "depends": { + "fabricloader": ">=0.15.11", + "fabric-language-kotlin": "*", + "notenoughcrashes": "*" + } +} \ No newline at end of file diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info index d438afa..03ed1ae 100644 --- a/src/main/resources/mcmod.info +++ b/src/main/resources/mcmod.info @@ -3,13 +3,12 @@ "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", "authorList": [ - "Polyfrost", - "SkyClient" + "Polyfrost" ], "credits": "vfyjxf for the 1.7.10 port of VanillaFix (BetterCrashes), Runemoro for VanillaFix, natanfudge for NotEnoughCrashes.", "screenshots": [], diff --git a/src/main/resources/mixin.crashpatch.json b/src/main/resources/mixin.crashpatch.json deleted file mode 100644 index eb1957a..0000000 --- a/src/main/resources/mixin.crashpatch.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compatibilityLevel": "JAVA_8", - "minVersion": "0.7", - "package": "org.polyfrost.crashpatch.mixin", - "refmap": "mixin.crashpatch.refmap.json", - "injectors": { - "maxShiftBy": 5 - }, - "mixins": [ - "AccessorGuiDisconnected", - "MixinCrashReport", - "MixinCrashReportCategory", - "MixinGuiConnecting", - "MixinGuiDupesFound", - "MixinMinecraft", - "MixinTileEntityRendererDispatcher", - "MixinWorldRenderer" - ], - "verbose": true -} \ No newline at end of file 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 new file mode 100644 index 0000000..b93add0 --- /dev/null +++ b/src/main/resources/mixins.crashpatch.json @@ -0,0 +1,20 @@ +{ + "compatibilityLevel": "${java_version}", + "minVersion": "0.8", + "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_Debug", + "MixinMinecraft_OverrideDisconnectedScreen", + "MixinMinecraft_PreInitialize" + ], + "verbose": true +} \ 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..b83068f --- /dev/null +++ b/versions/1.12.2-fabric/src/main/kotlin/org/polyfrost/crashpatch/utils/GlUtil.kt @@ -0,0 +1,205 @@ +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 +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) + } +} +//#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-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/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..137ab62 --- /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(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) { + // 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/MixinGuiConnecting.java b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java new file mode 100644 index 0000000..7a0eca0 --- /dev/null +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinGuiConnecting.java @@ -0,0 +1,91 @@ +package org.polyfrost.crashpatch.mixin; + +import com.mojang.blaze3d.vertex.PoseStack; +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; +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( + //#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()); + } + } + + @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( + //#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)) { + //#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 new file mode 100644 index 0000000..7f1216e --- /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, 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", 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.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 new file mode 100644 index 0000000..375d033 --- /dev/null +++ b/versions/1.16.5-forge/src/main/java/org/polyfrost/crashpatch/mixin/MixinMinecraft_PreInitialize.java @@ -0,0 +1,24 @@ +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.ModifyArg; + +@Mixin(Minecraft.class) +public class MixinMinecraft_PreInitialize { + + // 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 par1; + } +} 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 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 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(); + } + } +} 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