From 7f4424518c2d0c9066da88fc5bdcea9cf119bfda Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Wed, 24 Sep 2025 16:07:38 +0700 Subject: [PATCH 01/21] Update main to Gradle kotlin script --- build.gradle => build.gradle.bak | 0 build.gradle.kts | 216 ++++++++++++++++ .../cruciblemc/forgegradle/DevExtension.java | 238 +++++++++--------- 3 files changed, 335 insertions(+), 119 deletions(-) rename build.gradle => build.gradle.bak (100%) create mode 100644 build.gradle.kts diff --git a/build.gradle b/build.gradle.bak similarity index 100% rename from build.gradle rename to build.gradle.bak diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..910d372b --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,216 @@ +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.bundling.Zip +import org.gradle.api.publish.maven.MavenPublication +import java.io.ByteArrayOutputStream + +plugins { + base + id("maven-publish") + id("java-library") + id("crucible") // Assuming this is a custom plugin still available +} + +buildscript { + repositories { + mavenCentral() + // mavenLocal() // for testing plugin pre release + maven { + name = "juanmuscaria" + url = uri("https://github.com/juanmuscaria/maven/raw/master") + } + maven { + name = "forge" + url = uri("https://maven.minecraftforge.net/") + } + } + dependencies { + classpath("com.anatawa12.forge:ForgeGradle:1.2-1.1.0") + } +} + +repositories { + maven { + name = "juanmuscaria" + url = uri("https://github.com/juanmuscaria/maven/raw/master") + } + maven { + name = "thermos" + url = uri("https://github.com/juanmuscaria/maven/raw/master/ThermosLibs") + } + maven { + name = "forge" + url = uri("https://maven.minecraftforge.net/") + } + maven { + name = "minecraft" + url = uri("https://libraries.minecraft.net/") + } + maven { + name = "gtnh" + url = uri("https://nexus.gtnewhorizons.com/repository/public/") + } +} + +configure { + version = "1.7.10" + mcpVersion = "9.08" + mainClass = "cpw.mods.fml.relauncher.ServerLaunchWrapper" + tweakClass = "cpw.mods.fml.common.launcher.FMLTweaker" + installerVersion = "1.4" + + // Repos used on the generated subprojects + repos = arrayOf("https://github.com/juanmuscaria/maven/raw/master/ThermosLibs", + "https://github.com/juanmuscaria/maven/raw/master", + "https://maven.minecraftforge.net/", + "https://oss.sonatype.org/content/repositories/snapshots/", + "https://nexus.gtnewhorizons.com/repository/public/", + "https://libraries.minecraft.net/") +} + +extensions.configure("ext") { + set("mcVersion", "1.7.10") + set("forgeVersion", "1614") + set("revision", "1.0.7") +} + +group = "io.github.cruciblemc" + +val crucibleVersion = "5.4" // TODO: Not hardcode +version = if (gitInfo("branch") != "master") { + "${extra["mcVersion"]}-${gitInfo("branch")}-${gitInfo("hash")}" +} else { + "${extra["mcVersion"]}-$crucibleVersion" +} + +configurations { + create("libraries") +} + +dependencies { + "libraries"("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.10") { + exclude(group = "org.apache.logging.log4j") + } + "libraries"("org.apache.commons:commons-lang3:3.12.0") + "libraries"("org.apache.commons:commons-compress:1.21") + "libraries"("org.ow2.asm:asm:9.8") + "libraries"("org.ow2.asm:asm-commons:9.8") + "libraries"("org.ow2.asm:asm-tree:9.8") + "libraries"("org.ow2.asm:asm-analysis:9.8") + "libraries"("org.ow2.asm:asm-util:9.8") + "libraries"("org.ow2.asm:asm-deprecated:7.1") + "libraries"("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1") + "libraries"("javax.servlet:javax.servlet-api:4.0.1") + "libraries"("com.sun.xml.bind:jaxb-impl:3.0.2") + "libraries"("org.openjdk.nashorn:nashorn-core:15.4") + "libraries"("it.unimi.dsi:fastutil:8.5.12") + // ... continue with the rest of your long libraries list +} + +tasks.register("packageJavadoc") { + dependsOn(":eclipse:cauldron:javadoc") + archiveClassifier.set("javadoc") + destinationDirectory.set(layout.buildDirectory.dir("libs")) +} + +tasks.named("jar").configure { + manifest { + attributes( + mapOf( + "Thermos-Git-Branch" to gitInfo("branch"), + "Thermos-Git-Hash" to gitInfo("fullHash"), + "Thermos-Group" to project.group, + "Thermos-Channel" to project.name, + "Thermos-Version" to project.version, + "Thermos-Legacy" to true, + "Implementation-Vendor" to "CrucibleMC Team", + "Implementation-Title" to project.name, + "Implementation-Version" to project.version, + "Specification-Vendor" to "Bukkit Team", + "Specification-Title" to "Bukkit", + "Specification-Version" to "1.7.10-R0.1-SNAPSHOT", + "Forge-Version" to "10.13.4.1614", + "TweakClass" to "cpw.mods.fml.common.launcher.FMLTweaker", + "Main-Class" to "cpw.mods.fml.relauncher.ServerLaunchWrapper", + "Class-Path" to generateClasspath(), + "Crucible-Libs" to generateMavenLibs() + ) + ) + } +} + +tasks.register("packageLibraries") { + archiveFileName.set("libraries.zip") + destinationDirectory.set(layout.buildDirectory.dir("distributions")) + + outputs.upToDateWhen { false } + + from(configurations.named("libraries").get().resolvedConfiguration.resolvedArtifacts.map { art -> + val id = art.moduleVersion.id + val intoPath = "${id.group.replace('.', '/')}/${id.name}/${id.version}/" + zipTree(art.file).matching { }.let { copySpec -> + //copySpec.into(intoPath) + } + art.file + }) + + group = "crucible" + description = "Package all necessary libraries to run Crucible, in case the server cannot download them at runtime" +} + +publishing { + publications { + create("crucible") { + artifact(tasks.named("packageApi")) + artifact(tasks.named("packageJavadoc")) + } + } + repositories { + maven { + name = "filesystem" + url = uri("${buildDir}/repo") + } + } +} + +// Git helper +fun gitInfo(key: String): String { + val cache = project.extra.properties.getOrPut("gitInfoCached") { + if (file(".git").exists()) { + mapOf( + "hash" to runGit("log", "--format=%h", "-n", "1"), + "fullHash" to runGit("log", "--format=%H", "-n", "1"), + "branch" to runGit("symbolic-ref", "--short", "HEAD"), + "message" to runGit("log", "--format=%B", "-n", "1") + ) + } else { + mapOf( + "hash" to "NOT_A_GIT", + "fullHash" to "NOT_A_GIT", + "branch" to "NOT_A_GIT", + "message" to "NOT_A_GIT" + ) + } + } as Map<*, *> + return cache[key] as String +} + +fun runGit(vararg args: String): String { + val stdout = ByteArrayOutputStream() + project.exec { + commandLine("git", *args) + standardOutput = stdout + } + return stdout.toString().trim() +} + +fun generateClasspath(): String = + configurations["libraries"].resolvedConfiguration.resolvedArtifacts.joinToString(" ") { art -> + val id = art.moduleVersion.id + "libraries/${id.group.replace('.', '/')}/${id.name}/${id.version}/${id.name}-${id.version}.jar" + } + +fun generateMavenLibs(): String = + configurations["libraries"].resolvedConfiguration.resolvedArtifacts.joinToString(" ") { art -> + val id = art.moduleVersion.id + "${id.group}:${id.name}:${id.version}" + } \ No newline at end of file diff --git a/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/DevExtension.java b/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/DevExtension.java index 7d6da07b..1665dac7 100644 --- a/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/DevExtension.java +++ b/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/DevExtension.java @@ -7,122 +7,122 @@ import org.gradle.util.ClosureBackedAction; public class DevExtension extends BaseExtension { - private String fmlDir; - private String forgeDir; - private String bukkitDir; - private String mainClass; - private String tweakClass; - private String[] repos = new String[0]; - private boolean makeJavadoc = true; - private String installerVersion = "null"; - private Action subprojects = null; - private Action cleanProject = null; - private Action dirtyProject = null; - - public DevExtension(DevBasePlugin plugin) { - super(plugin); - } - - public String getFmlDir() { - return fmlDir == null ? project.getProjectDir().getPath().replace('\\', '/') : fmlDir.replace('\\', '/'); - } - - public void setFmlDir(String fmlDir) { - this.fmlDir = fmlDir; - } - - public String getForgeDir() { - return forgeDir == null ? project.getProjectDir().getPath().replace('\\', '/') : forgeDir.replace('\\', '/'); - } - - public void setForgeDir(String forgeDir) { - this.forgeDir = forgeDir; - } - - public String getBukkitDir() { - return bukkitDir == null ? project.getProjectDir().getPath().replace('\\', '/') : bukkitDir.replace('\\', '/'); - } - - public void setBukkitDir(String bukkitDir) { - this.bukkitDir = bukkitDir; - } - - public String getMainClass() { - return mainClass == null ? "" : mainClass; - } - - public void setMainClass(String mainClass) { - this.mainClass = mainClass; - } - - public String getInstallerVersion() { - return installerVersion; - } - - public void setInstallerVersion(String installerVersion) { - this.installerVersion = installerVersion; - } - - public String getTweakClass() { - return tweakClass == null ? "" : tweakClass; - } - - public void setTweakClass(String tweakClass) { - this.tweakClass = tweakClass; - } - - public Action getSubprojects() { - return subprojects; - } - - public void setSubprojects(Action subprojects) { - this.subprojects = subprojects; - } - - @SuppressWarnings("rawtypes") - public void subprojects(Closure subprojects) { - this.subprojects = new ClosureBackedAction<>(subprojects); - } - - public Action getCleanProject() { - return cleanProject; - } - - public void setCleanProject(Action cleanProject) { - this.cleanProject = cleanProject; - } - - @SuppressWarnings("rawtypes") - public void cleanProject(Closure subprojects) { - this.cleanProject = new ClosureBackedAction(subprojects); - } - - public Action getDirtyProject() { - return dirtyProject; - } - - public void setDirtyProject(Action dirtyProject) { - this.dirtyProject = dirtyProject; - } - - @SuppressWarnings("rawtypes") - public void dirtyProject(Closure subprojects) { - this.dirtyProject = new ClosureBackedAction<>(subprojects); - } - - public boolean getMakeJavadoc() { - return makeJavadoc; - } - - public void setMakeJavadoc(boolean makeJavadoc) { - this.makeJavadoc = makeJavadoc; - } - - public String[] getRepos() { - return repos; - } - - public void setRepos(String[] repos) { - this.repos = repos; - } -} + private String fmlDir; + private String forgeDir; + private String bukkitDir; + private String mainClass; + private String tweakClass; + private String[] repos = new String[0]; + private boolean makeJavadoc = true; + private String installerVersion = "null"; + private Action subprojects = null; + private Action cleanProject = null; + private Action dirtyProject = null; + + public DevExtension(DevBasePlugin plugin) { + super(plugin); + } + + public String getFmlDir() { + return fmlDir == null ? project.getProjectDir().getPath().replace('\\', '/') : fmlDir.replace('\\', '/'); + } + + public void setFmlDir(String fmlDir) { + this.fmlDir = fmlDir; + } + + public String getForgeDir() { + return forgeDir == null ? project.getProjectDir().getPath().replace('\\', '/') : forgeDir.replace('\\', '/'); + } + + public void setForgeDir(String forgeDir) { + this.forgeDir = forgeDir; + } + + public String getBukkitDir() { + return bukkitDir == null ? project.getProjectDir().getPath().replace('\\', '/') : bukkitDir.replace('\\', '/'); + } + + public void setBukkitDir(String bukkitDir) { + this.bukkitDir = bukkitDir; + } + + public String getMainClass() { + return mainClass == null ? "" : mainClass; + } + + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + public String getInstallerVersion() { + return installerVersion; + } + + public void setInstallerVersion(String installerVersion) { + this.installerVersion = installerVersion; + } + + public String getTweakClass() { + return tweakClass == null ? "" : tweakClass; + } + + public void setTweakClass(String tweakClass) { + this.tweakClass = tweakClass; + } + + public Action getSubprojects() { + return subprojects; + } + + public void setSubprojects(Action subprojects) { + this.subprojects = subprojects; + } + + @SuppressWarnings("rawtypes") + public void subprojects(Closure subprojects) { + this.subprojects = new ClosureBackedAction<>(subprojects); + } + + public Action getCleanProject() { + return cleanProject; + } + + public void setCleanProject(Action cleanProject) { + this.cleanProject = cleanProject; + } + + @SuppressWarnings("rawtypes") + public void cleanProject(Closure subprojects) { + this.cleanProject = new ClosureBackedAction(subprojects); + } + + public Action getDirtyProject() { + return dirtyProject; + } + + public void setDirtyProject(Action dirtyProject) { + this.dirtyProject = dirtyProject; + } + + @SuppressWarnings("rawtypes") + public void dirtyProject(Closure subprojects) { + this.dirtyProject = new ClosureBackedAction<>(subprojects); + } + + public boolean getMakeJavadoc() { + return makeJavadoc; + } + + public void setMakeJavadoc(boolean makeJavadoc) { + this.makeJavadoc = makeJavadoc; + } + + public String[] getRepos() { + return repos; + } + + public void setRepos(String[] repos) { + this.repos = repos; + } +} \ No newline at end of file From 45bb7a63dc5249b7968fbd5935a6850a15155a2b Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Wed, 24 Sep 2025 17:06:14 +0700 Subject: [PATCH 02/21] Small update rfb and legacy build tasks --- build.gradle.kts | 51 ++++++- .../cruciblemc/forgegradle/DevExtension.java | 134 ++++++------------ .../tasks/DeterministicDecompileTask.java | 13 +- 3 files changed, 94 insertions(+), 104 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 910d372b..088d6155 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,7 +59,7 @@ configure { installerVersion = "1.4" // Repos used on the generated subprojects - repos = arrayOf("https://github.com/juanmuscaria/maven/raw/master/ThermosLibs", + repos = listOf("https://github.com/juanmuscaria/maven/raw/master/ThermosLibs", "https://github.com/juanmuscaria/maven/raw/master", "https://maven.minecraftforge.net/", "https://oss.sonatype.org/content/repositories/snapshots/", @@ -82,12 +82,14 @@ version = if (gitInfo("branch") != "master") { "${extra["mcVersion"]}-$crucibleVersion" } -configurations { - create("libraries") +val libraries by configurations.creating { + isCanBeConsumed = false; isCanBeResolved = true; } +configurations["implementation"].extendsFrom(libraries) + dependencies { - "libraries"("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.10") { + "libraries"("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.11") { exclude(group = "org.apache.logging.log4j") } "libraries"("org.apache.commons:commons-lang3:3.12.0") @@ -103,7 +105,46 @@ dependencies { "libraries"("com.sun.xml.bind:jaxb-impl:3.0.2") "libraries"("org.openjdk.nashorn:nashorn-core:15.4") "libraries"("it.unimi.dsi:fastutil:8.5.12") - // ... continue with the rest of your long libraries list + + // Other libs + "libraries"("commons-cli:commons-cli:1.3@jar") + "libraries"("org.slf4j:slf4j-simple:1.6.2@jar") + "libraries"("org.eclipse.jetty:jetty-servlet:9.0.3.v20130506@jar") + "libraries"("commons-io:commons-io:2.4@jar") + "libraries"("net.sf.opencsv:opencsv:2.0@jar") + "libraries"("com.beust:jcommander:1.30@jar") + "libraries"("io.github.cruciblemc:launchwrapper:1.13@jar") + "libraries"("com.typesafe.akka:akka-actor_2.11:2.3.3") + "libraries"("com.typesafe:config:1.2.1") + "libraries"("org.scala-lang:scala-actors-migration_2.11:1.1.0") + "libraries"("org.scala-lang:scala-compiler:2.11.7") + "libraries"("org.scala-lang.plugins:scala-continuations-library_2.11:1.0.2") + "libraries"("org.scala-lang.plugins:scala-continuations-plugin_2.11.2:1.0.2") + "libraries"("org.scala-lang:scala-library:2.11.7") + "libraries"("org.scala-lang:scala-parser-combinators:2.11.0-M4") + "libraries"("org.scala-lang:scala-reflect:2.11.7") + "libraries"("org.scala-lang:scala-swing:2.11.0-M7") + "libraries"("org.scala-lang:scala-xml:2.11.0-M4") + "libraries"("net.sf.jopt-simple:jopt-simple:5.0.1") + "libraries"("lzma:lzma:0.0.1") + "libraries"("org.yaml:snakeyaml:1.9") + "libraries"("commons-lang:commons-lang:2.6") + "libraries"("org.avaje:ebean:2.7.3") + "libraries"("jline:jline:2.6") + "libraries"("net.md-5:SpecialSource:1.10.0") + "libraries"("net.sourceforge.argo:argo:2.25") + "libraries"("com.googlecode.json-simple:json-simple:1.1") + "libraries"("org.xerial:sqlite-jdbc:3.7.2") + "libraries"("mysql:mysql-connector-java:5.1.14") + "libraries"("javax.persistence:persistence-api:1.0.2") + "libraries"("pw.prok:KImagine:0.2.0@jar") + "libraries"("org.apache.httpcomponents:httpclient:4.4.1") + "libraries"("net.openhft:affinity:3.0.1") + "libraries"("org.fusesource.jansi:jansi:1.11") + "libraries"("com.koloboke:koloboke-impl-jdk8:1.0.0") + "libraries"("java3d:vecmath:1.3.1") + "libraries"("net.minecraft:server:1.7.10") + "libraries"(project(":eclipse:cauldron")) } tasks.register("packageJavadoc") { diff --git a/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/DevExtension.java b/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/DevExtension.java index 1665dac7..cc0b92e5 100644 --- a/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/DevExtension.java +++ b/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/DevExtension.java @@ -1,128 +1,84 @@ package io.github.cruciblemc.forgegradle; -import groovy.lang.Closure; +import lombok.Getter; +import lombok.Setter; import net.minecraftforge.gradle.common.BaseExtension; import org.gradle.api.Action; import org.gradle.api.Project; -import org.gradle.util.ClosureBackedAction; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; public class DevExtension extends BaseExtension { + @Setter private String fmlDir; + @Setter private String forgeDir; + @Setter private String bukkitDir; - private String mainClass; - private String tweakClass; - private String[] repos = new String[0]; + @Getter + private String mainClass = ""; + @Getter + private String tweakClass = ""; + @Getter + private List repos = new ArrayList<>(); + @Setter + @Getter private boolean makeJavadoc = true; + @Setter + @Getter private String installerVersion = "null"; - private Action subprojects = null; - private Action cleanProject = null; - private Action dirtyProject = null; + + @Setter + @Getter + private Action subprojects; + @Setter + @Getter + private Action cleanProject; + @Setter + @Getter + private Action dirtyProject; public DevExtension(DevBasePlugin plugin) { super(plugin); } public String getFmlDir() { - return fmlDir == null ? project.getProjectDir().getPath().replace('\\', '/') : fmlDir.replace('\\', '/'); - } - - public void setFmlDir(String fmlDir) { - this.fmlDir = fmlDir; + return normalizeOrDefault(fmlDir); } public String getForgeDir() { - return forgeDir == null ? project.getProjectDir().getPath().replace('\\', '/') : forgeDir.replace('\\', '/'); - } - - public void setForgeDir(String forgeDir) { - this.forgeDir = forgeDir; + return normalizeOrDefault(forgeDir); } public String getBukkitDir() { - return bukkitDir == null ? project.getProjectDir().getPath().replace('\\', '/') : bukkitDir.replace('\\', '/'); - } - - public void setBukkitDir(String bukkitDir) { - this.bukkitDir = bukkitDir; - } - - public String getMainClass() { - return mainClass == null ? "" : mainClass; + return normalizeOrDefault(bukkitDir); } public void setMainClass(String mainClass) { - this.mainClass = mainClass; - } - - public String getInstallerVersion() { - return installerVersion; - } - - public void setInstallerVersion(String installerVersion) { - this.installerVersion = installerVersion; - } - - public String getTweakClass() { - return tweakClass == null ? "" : tweakClass; + this.mainClass = mainClass == null ? "" : mainClass; } public void setTweakClass(String tweakClass) { - this.tweakClass = tweakClass; - } - - public Action getSubprojects() { - return subprojects; + this.tweakClass = tweakClass == null ? "" : tweakClass; } - public void setSubprojects(Action subprojects) { - this.subprojects = subprojects; + public void setRepos(List repos) { + this.repos = new ArrayList<>(repos); } - @SuppressWarnings("rawtypes") - public void subprojects(Closure subprojects) { - this.subprojects = new ClosureBackedAction<>(subprojects); + public void setRepos(String... repos) { + this.repos = new ArrayList<>(Arrays.asList(repos)); } - public Action getCleanProject() { - return cleanProject; - } - - public void setCleanProject(Action cleanProject) { - this.cleanProject = cleanProject; - } - - @SuppressWarnings("rawtypes") - public void cleanProject(Closure subprojects) { - this.cleanProject = new ClosureBackedAction(subprojects); - } - - public Action getDirtyProject() { - return dirtyProject; - } - - public void setDirtyProject(Action dirtyProject) { - this.dirtyProject = dirtyProject; - } - - @SuppressWarnings("rawtypes") - public void dirtyProject(Closure subprojects) { - this.dirtyProject = new ClosureBackedAction<>(subprojects); - } - - public boolean getMakeJavadoc() { - return makeJavadoc; - } - - public void setMakeJavadoc(boolean makeJavadoc) { - this.makeJavadoc = makeJavadoc; - } - - public String[] getRepos() { - return repos; - } + // --- Helpers --- - public void setRepos(String[] repos) { - this.repos = repos; + private String normalizeOrDefault(String path) { + if (path == null) { + return project.getProjectDir().getPath().replace('\\', '/'); + } + return path.replace('\\', '/'); } } \ No newline at end of file diff --git a/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/tasks/DeterministicDecompileTask.java b/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/tasks/DeterministicDecompileTask.java index 597a4652..31c105bc 100644 --- a/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/tasks/DeterministicDecompileTask.java +++ b/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/tasks/DeterministicDecompileTask.java @@ -22,6 +22,7 @@ import net.minecraftforge.gradle.patching.ContextualPatch.PatchReport; import net.minecraftforge.gradle.patching.ContextualPatch.PatchStatus; import net.minecraftforge.gradle.tasks.abstractutil.CachedTask; +import org.gradle.api.Action; import org.gradle.api.file.FileCollection; import org.gradle.api.logging.LogLevel; import org.gradle.api.tasks.*; @@ -156,12 +157,11 @@ private void crucibleFixJar(File patchFiles, File jar) throws Throwable { } private void decompile(final File inJar, final File outJar, final File fernFlower) { - this.getProject().javaexec(new Closure(this) { + this.getProject().getProviders().javaexec(new Action<>() { private static final long serialVersionUID = 4608694547855396167L; @Override - public JavaExecSpec call() { - JavaExecSpec exec = (JavaExecSpec) this.getDelegate(); + public void execute(JavaExecSpec exec) { exec.args( fernFlower.getAbsolutePath(), @@ -182,13 +182,6 @@ public JavaExecSpec call() { exec.setStandardOutput(DevConstants.getTaskLogStream(DeterministicDecompileTask.this.getProject(), DeterministicDecompileTask.this.getName() + ".log")); exec.setMaxHeapSize("512M"); - - return exec; - } - - @Override - public JavaExecSpec call(Object obj) { - return this.call(); } }); } From 997c0b282bd5281d0ddf718339d383cf353278d4 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Wed, 14 Jan 2026 12:03:56 +0700 Subject: [PATCH 03/21] Remove custom lwjgl3ify --- build.gradle | 8 +- .../bootstrap/CrucibleCoremodHook.java | 1 - .../bootstrap/CrucibleServerMainHook.java | 2 - .../eigenraven/lwjgl3ify/IExtensibleEnum.java | 31 -- .../me/eigenraven/lwjgl3ify/UnsafeHacks.java | 99 ------- .../lwjgl3ify/WasFinalObjectHolder.java | 10 - .../eigenraven/lwjgl3ify/api/ConfigUtils.java | 34 --- .../eigenraven/lwjgl3ify/api/Lwjgl3Aware.java | 13 - .../lwjgl3ify/api/MakeEnumExtensible.java | 13 - .../core/ExtensibleEnumTransformerHelper.java | 278 ------------------ ...xConstantPoolInterfaceMethodRefHelper.java | 95 ------ .../core/LwjglRedirectTransformer.java | 86 ------ .../UnfinalizeObjectHoldersTransformer.java | 125 -------- 13 files changed, 4 insertions(+), 791 deletions(-) delete mode 100644 src/main/java/me/eigenraven/lwjgl3ify/IExtensibleEnum.java delete mode 100644 src/main/java/me/eigenraven/lwjgl3ify/UnsafeHacks.java delete mode 100644 src/main/java/me/eigenraven/lwjgl3ify/WasFinalObjectHolder.java delete mode 100644 src/main/java/me/eigenraven/lwjgl3ify/api/ConfigUtils.java delete mode 100644 src/main/java/me/eigenraven/lwjgl3ify/api/Lwjgl3Aware.java delete mode 100644 src/main/java/me/eigenraven/lwjgl3ify/api/MakeEnumExtensible.java delete mode 100644 src/main/java/me/eigenraven/lwjgl3ify/core/ExtensibleEnumTransformerHelper.java delete mode 100644 src/main/java/me/eigenraven/lwjgl3ify/core/FixConstantPoolInterfaceMethodRefHelper.java delete mode 100644 src/main/java/me/eigenraven/lwjgl3ify/core/LwjglRedirectTransformer.java delete mode 100644 src/main/java/me/eigenraven/lwjgl3ify/core/UnfinalizeObjectHoldersTransformer.java diff --git a/build.gradle b/build.gradle index 69482412..0c1a02a6 100644 --- a/build.gradle +++ b/build.gradle @@ -89,8 +89,9 @@ dependencies { libraries('com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.10') { exclude group: 'org.apache.logging.log4j' } - libraries 'org.apache.commons:commons-lang3:3.12.0' - libraries 'org.apache.commons:commons-compress:1.21' + libraries 'com.google.code.gson:gson:2.13.2' + libraries 'org.apache.commons:commons-lang3:3.17.0' + libraries 'org.apache.commons:commons-compress:1.27.1' libraries 'org.ow2.asm:asm:9.8' libraries 'org.ow2.asm:asm-commons:9.8' libraries 'org.ow2.asm:asm-tree:9.8' @@ -108,10 +109,9 @@ dependencies { libraries 'commons-cli:commons-cli:1.3@jar' libraries 'org.slf4j:slf4j-simple:1.6.2@jar' libraries 'org.eclipse.jetty:jetty-servlet:9.0.3.v20130506@jar' - libraries 'commons-io:commons-io:2.4@jar' + libraries 'commons-io:commons-io:2.18.0' libraries 'net.sf.opencsv:opencsv:2.0@jar' libraries 'com.beust:jcommander:1.30@jar' - libraries 'io.github.cruciblemc:launchwrapper:1.13@jar' libraries 'com.typesafe.akka:akka-actor_2.11:2.3.3' libraries 'com.typesafe:config:1.2.1' libraries 'org.scala-lang:scala-actors-migration_2.11:1.1.0' diff --git a/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java b/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java index 9621729b..c829d9ab 100644 --- a/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java +++ b/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java @@ -15,6 +15,5 @@ public static void coremodHandleLaunch(File mcDir, LaunchClassLoader classLoader } catch (ClassNotFoundException e) { throw new RuntimeException(e); } - Lwjgl3ifyGlue.doCoremodWork(classLoader); } } diff --git a/src/main/java/io/github/crucible/bootstrap/CrucibleServerMainHook.java b/src/main/java/io/github/crucible/bootstrap/CrucibleServerMainHook.java index 7b9db192..d1958377 100644 --- a/src/main/java/io/github/crucible/bootstrap/CrucibleServerMainHook.java +++ b/src/main/java/io/github/crucible/bootstrap/CrucibleServerMainHook.java @@ -58,8 +58,6 @@ public static void relaunchMain(String[] args) throws Exception { System.setProperty((String) entry.getKey(), (String) entry.getValue()); } - Lwjgl3ifyGlue.checkJava(); - if(System.getProperty("java.class.loader") == null) { System.setProperty("rfb.skipClassLoaderCheck", "true"); } diff --git a/src/main/java/me/eigenraven/lwjgl3ify/IExtensibleEnum.java b/src/main/java/me/eigenraven/lwjgl3ify/IExtensibleEnum.java deleted file mode 100644 index e1a55261..00000000 --- a/src/main/java/me/eigenraven/lwjgl3ify/IExtensibleEnum.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors SPDX-License-Identifier: LGPL-2.1-only - */ -package me.eigenraven.lwjgl3ify; - -/** - * To be implemented on vanilla enums that should be enhanced with ASM to be extensible. If this is implemented on a - * class, the class must define a static method called "create" which takes a String (enum name), and the rest of the - * parameters matching a constructor. - *

- * For example, an enum with the constructor {@code MyEnum(Object foo)} would require the method: - * - *

- *
- * public static MyEnum create(String name, Object foo) {
- *     throw new IllegalStateException("Enum not extended");
- * }
- * 
- * - * The method contents will be replaced with ASM at runtime. Multiple {@code create} methods can be - * defined as long as each matches a constructor. - */ -public interface IExtensibleEnum { - - /** - * Called by generated factory code to do any post-constructor setup required by the enum. Should not be called - * manually. - */ - @Deprecated - default void init() {} -} \ No newline at end of file diff --git a/src/main/java/me/eigenraven/lwjgl3ify/UnsafeHacks.java b/src/main/java/me/eigenraven/lwjgl3ify/UnsafeHacks.java deleted file mode 100644 index ac8a6711..00000000 --- a/src/main/java/me/eigenraven/lwjgl3ify/UnsafeHacks.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Minecraft Forge Copyright (c) 2016-2019. This library is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by the Free Software Foundation version 2.1 of - * the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. You should have received a copy of the GNU Lesser General Public License along with this - * library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - * USA - */ -package me.eigenraven.lwjgl3ify; - -import java.lang.reflect.Field; -import java.util.Optional; - -@SuppressWarnings({ "restriction", "sunapi" }) -public class UnsafeHacks { - - private static final sun.misc.Unsafe UNSAFE; - - static { - try { - final Field theUnsafe = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); - theUnsafe.setAccessible(true); - UNSAFE = (sun.misc.Unsafe) theUnsafe.get(null); - } catch (IllegalAccessException | NoSuchFieldException e) { - throw new RuntimeException("BARF!", e); - } - } - - @SuppressWarnings("unchecked") - public static T newInstance(Class clazz) { - try { - return (T) UNSAFE.allocateInstance(clazz); - } catch (InstantiationException e) { - throw new RuntimeException(e); - } - } - - @SuppressWarnings("unchecked") - public static T getField(Field field, Object object) { - if (object == null) { - long offset = UNSAFE.staticFieldOffset(field); - Object base = UNSAFE.staticFieldBase(field); - return (T) UNSAFE.getObject(base, offset); - } else { - long offset = UNSAFE.objectFieldOffset(field); - return (T) UNSAFE.getObject(object, offset); - } - } - - public static void setField(Field data, Object object, Object value) { - if (object == null) { - long offset = UNSAFE.staticFieldOffset(data); - Object base = UNSAFE.staticFieldBase(data); - UNSAFE.putObject(base, offset, value); - } else { - long offset = UNSAFE.objectFieldOffset(data); - UNSAFE.putObject(object, offset, value); - } - } - - public static int getIntField(Field field, Object object) { - if (object == null) { - long offset = UNSAFE.staticFieldOffset(field); - Object base = UNSAFE.staticFieldBase(field); - return UNSAFE.getInt(base, offset); - } else { - long offset = UNSAFE.objectFieldOffset(field); - return UNSAFE.getInt(object, offset); - } - } - - public static void setIntField(Field data, Object object, int value) { - if (object == null) { - long offset = UNSAFE.staticFieldOffset(data); - Object base = UNSAFE.staticFieldBase(data); - UNSAFE.putInt(base, offset, value); - } else { - long offset = UNSAFE.objectFieldOffset(data); - UNSAFE.putInt(object, offset, value); - } - } - - // Make sure we don't crash if any future versions change field names - private static Optional findField(Class clazz, String name) { - for (Field f : clazz.getDeclaredFields()) { - if (f.getName() - .equals(name)) { - return Optional.of(f); - } - } - return Optional.empty(); - } - - public static void cleanEnumCache(Class> enumClass) throws Exception { - findField(Class.class, "enumConstantDirectory").ifPresent(f -> setField(f, enumClass, null)); - findField(Class.class, "enumConstants").ifPresent(f -> setField(f, enumClass, null)); - } -} \ No newline at end of file diff --git a/src/main/java/me/eigenraven/lwjgl3ify/WasFinalObjectHolder.java b/src/main/java/me/eigenraven/lwjgl3ify/WasFinalObjectHolder.java deleted file mode 100644 index f91c600c..00000000 --- a/src/main/java/me/eigenraven/lwjgl3ify/WasFinalObjectHolder.java +++ /dev/null @@ -1,10 +0,0 @@ -package me.eigenraven.lwjgl3ify; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -public @interface WasFinalObjectHolder {} \ No newline at end of file diff --git a/src/main/java/me/eigenraven/lwjgl3ify/api/ConfigUtils.java b/src/main/java/me/eigenraven/lwjgl3ify/api/ConfigUtils.java deleted file mode 100644 index 228854a7..00000000 --- a/src/main/java/me/eigenraven/lwjgl3ify/api/ConfigUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -package me.eigenraven.lwjgl3ify.api; - -import io.github.crucible.CrucibleConfigs; -import org.apache.logging.log4j.Logger; - -import java.util.HashSet; -import java.util.Set; - -/** - * Proxy everything to Crucible - * TODO: perhaps we need to ensure this class is loaded by us? - */ -public class ConfigUtils { - - public ConfigUtils(Logger logger) { - // NO-OP - } - - public boolean isLwjgl3ifyLoaded() { - return true; - } - - public Set getExtensibleEnums() { - return new HashSet<>(CrucibleConfigs.configs.lwjgl3ify_extensibleEnums); - } - - public void addExtensibleEnum(String className) { - CrucibleConfigs.configs.lwjgl3ify_extensibleEnums.add(className); - } - - public boolean isConfigLoaded() { - return true; - } -} diff --git a/src/main/java/me/eigenraven/lwjgl3ify/api/Lwjgl3Aware.java b/src/main/java/me/eigenraven/lwjgl3ify/api/Lwjgl3Aware.java deleted file mode 100644 index 28760fbf..00000000 --- a/src/main/java/me/eigenraven/lwjgl3ify/api/Lwjgl3Aware.java +++ /dev/null @@ -1,13 +0,0 @@ -package me.eigenraven.lwjgl3ify.api; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Mark a class to not be transformed for lwjgl3 compatibility - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface Lwjgl3Aware {} \ No newline at end of file diff --git a/src/main/java/me/eigenraven/lwjgl3ify/api/MakeEnumExtensible.java b/src/main/java/me/eigenraven/lwjgl3ify/api/MakeEnumExtensible.java deleted file mode 100644 index bc1346b0..00000000 --- a/src/main/java/me/eigenraven/lwjgl3ify/api/MakeEnumExtensible.java +++ /dev/null @@ -1,13 +0,0 @@ -package me.eigenraven.lwjgl3ify.api; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Mark an enum for an automatic IExtensibleEnum implementation. - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface MakeEnumExtensible {} \ No newline at end of file diff --git a/src/main/java/me/eigenraven/lwjgl3ify/core/ExtensibleEnumTransformerHelper.java b/src/main/java/me/eigenraven/lwjgl3ify/core/ExtensibleEnumTransformerHelper.java deleted file mode 100644 index f98312e5..00000000 --- a/src/main/java/me/eigenraven/lwjgl3ify/core/ExtensibleEnumTransformerHelper.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors SPDX-License-Identifier: LGPL-2.1-only - */ -package me.eigenraven.lwjgl3ify.core; - -import java.util.List; -import java.util.stream.Collectors; - -import io.github.crucible.CrucibleModContainer; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.objectweb.asm.Label; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.commons.InstructionAdapter; -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldNode; -import org.objectweb.asm.tree.MethodNode; - -import me.eigenraven.lwjgl3ify.IExtensibleEnum; -import me.eigenraven.lwjgl3ify.api.MakeEnumExtensible; - -public class ExtensibleEnumTransformerHelper { - - private final Logger LOGGER = LogManager.getLogger("lwjgl3ify"); - private final Type STRING = Type.getType(String.class); - private final Type ENUM = Type.getType(Enum.class); - public final Type MARKER_IFACE = Type.getType(IExtensibleEnum.class); - public final Type MARKER_ANNOTATION = Type.getType(MakeEnumExtensible.class); - private final Type ARRAY_UTILS = Type.getType("Lorg/apache/commons/lang3/ArrayUtils;"); // Don't directly reference - // this to prevent class - // loading. - private final String ADD_DESC = Type - .getMethodDescriptor(Type.getType(Object[].class), Type.getType(Object[].class), Type.getType(Object.class)); - private final Type UNSAFE_HACKS = Type.getType("Lme/eigenraven/lwjgl3ify/UnsafeHacks;"); // Again, not direct - // reference to prevent - // class loading. - private final String CLEAN_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Class.class)); - private final String NAME_DESC = Type.getMethodDescriptor(STRING); - private final String EQUALS_DESC = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, STRING); - public static final String CREATE_METHOD_NAME = "dynamicCreate"; - - /** - * @return Were changes made? - */ - public boolean processClassWithFlags(final ClassNode classNode, final Type classType) { - if ((classNode.access & Opcodes.ACC_ENUM) == 0) return false; - - Type array = Type.getType("[" + classType.getDescriptor()); - final int flags = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC; - - FieldNode values = classNode.fields.stream() - .filter(f -> f.desc.contentEquals(array.getDescriptor()) && ((f.access & flags) == flags)) - .findFirst() - .orElse(null); - - boolean process = false; - if (classNode.interfaces.contains(MARKER_IFACE.getInternalName())) { - process = true; - } else if (classNode.visibleAnnotations != null && !classNode.visibleAnnotations.isEmpty()) { - for (AnnotationNode annotation : classNode.visibleAnnotations) { - if (annotation.desc.equals(MARKER_ANNOTATION.getDescriptor())) { - process = true; - } - } - } - if (!process) { - return false; - } - - List constructors = classNode.methods.stream() - .filter(m -> m.name.equals("")) - .collect(Collectors.toList()); - - // Static methods named "create" with first argument as a string - List candidates = constructors.stream() - .map(ctor -> { - final String[] exceptions = ctor.exceptions == null ? null : ctor.exceptions.toArray(new String[0]); - final Type ctorDesc = Type.getMethodType(ctor.desc); - final Type creatorDesc = Type - .getMethodType(classType, ArrayUtils.remove(ctorDesc.getArgumentTypes(), 1)); - final MethodNode creator = new MethodNode( - ctor.access, - CREATE_METHOD_NAME, - creatorDesc.getDescriptor(), - null, - exceptions); - creator.access = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC; - return creator; - }) - .collect(Collectors.toList()); - - if (candidates.isEmpty()) { - throw new IllegalStateException( - "IExtensibleEnum has no candidate factory methods: " + classType.getClassName()); - } - - classNode.methods.addAll(candidates); - - candidates.forEach(mtd -> { - Type[] args = Type.getArgumentTypes(mtd.desc); - if (args.length == 0 || !args[0].equals(STRING)) { - if (LOGGER.isErrorEnabled()) { - String sb = "Enum has create method without String as first parameter:\n" + " Enum: " - + classType.getDescriptor() - + "\n" - + " Target: " - + mtd.name - + mtd.desc - + "\n"; - LOGGER.error(sb); - } - throw new IllegalStateException( - "Enum has create method without String as first parameter: " + mtd.name + mtd.desc); - } - - Type ret = Type.getReturnType(mtd.desc); - if (!ret.equals(classType)) { - if (LOGGER.isErrorEnabled()) { - String sb = "Enum has create method with incorrect return type:\n" + " Enum: " - + classType.getDescriptor() - + "\n" - + " Target: " - + mtd.name - + mtd.desc - + "\n" - + " Found: " - + ret.getClassName() - + ", Expected: " - + classType.getClassName(); - LOGGER.error(sb); - } - throw new IllegalStateException( - "Enum has create method with incorrect return type: " + mtd.name + mtd.desc); - } - - Type[] ctrArgs = new Type[args.length + 1]; - ctrArgs[0] = STRING; - ctrArgs[1] = Type.INT_TYPE; - for (int x = 1; x < args.length; x++) ctrArgs[1 + x] = args[x]; - - String desc = Type.getMethodDescriptor(Type.VOID_TYPE, ctrArgs); - - MethodNode ctr = classNode.methods.stream() - .filter(m -> m.name.equals("") && m.desc.equals(desc)) - .findFirst() - .orElse(null); - if (ctr == null) { - if (LOGGER.isErrorEnabled()) { - StringBuilder sb = new StringBuilder(); - sb.append("Enum has create method with no matching constructor:\n"); - sb.append(" Enum: ") - .append(classType.getDescriptor()) - .append("\n"); - sb.append(" Candidate: ") - .append(mtd.desc) - .append("\n"); - sb.append(" Target: ") - .append(desc) - .append("\n"); - classNode.methods.stream() - .filter(m -> m.name.equals("")) - .forEach( - m -> sb.append(" : ") - .append(m.desc) - .append("\n")); - LOGGER.error(sb.toString()); - } - throw new IllegalStateException("Enum has create method with no matching constructor: " + desc); - } - - if (values == null) { - if (LOGGER.isErrorEnabled()) { - StringBuilder sb = new StringBuilder(); - sb.append("Enum has create method but we could not find $VALUES. Found:\n"); - classNode.fields.stream() - .filter(f -> (f.access & Opcodes.ACC_STATIC) != 0) - .forEach( - m -> sb.append(" ") - .append(m.name) - .append(" ") - .append(m.desc) - .append("\n")); - LOGGER.error(sb.toString()); - } - throw new IllegalStateException("Enum has create method but we could not find $VALUES"); - } - - values.access &= values.access & ~Opcodes.ACC_FINAL; // Strip the final so JITer doesn't inline things. - - mtd.access |= Opcodes.ACC_SYNCHRONIZED; - mtd.instructions.clear(); - mtd.localVariables.clear(); - if (mtd.tryCatchBlocks != null) { - mtd.tryCatchBlocks.clear(); - } - if (mtd.visibleLocalVariableAnnotations != null) { - mtd.visibleLocalVariableAnnotations.clear(); - } - if (mtd.invisibleLocalVariableAnnotations != null) { - mtd.invisibleLocalVariableAnnotations.clear(); - } - InstructionAdapter ins = new InstructionAdapter(mtd); - - int vars = 0; - for (Type arg : args) vars += arg.getSize(); - - { - vars += 1; // int x - Label for_start = new Label(); - Label for_condition = new Label(); - Label for_inc = new Label(); - - ins.iconst(0); - ins.store(vars, Type.INT_TYPE); - ins.goTo(for_condition); - // if (!VALUES[x].name().equalsIgnoreCase(name)) goto for_inc - ins.mark(for_start); - ins.getstatic(classType.getInternalName(), values.name, values.desc); - ins.load(vars, Type.INT_TYPE); - ins.aload(array); - ins.invokevirtual(ENUM.getInternalName(), "name", NAME_DESC, false); - ins.load(0, STRING); - ins.invokevirtual(STRING.getInternalName(), "equalsIgnoreCase", EQUALS_DESC, false); - ins.ifeq(for_inc); - // return VALUES[x]; - ins.getstatic(classType.getInternalName(), values.name, values.desc); - ins.load(vars, Type.INT_TYPE); - ins.aload(array); - ins.areturn(classType); - // x++ - ins.mark(for_inc); - ins.iinc(vars, 1); - // if (x < VALUES.length) goto for_start - ins.mark(for_condition); - ins.load(vars, Type.INT_TYPE); - ins.getstatic(classType.getInternalName(), values.name, values.desc); - ins.arraylength(); - ins.ificmplt(for_start); - } - - { - vars += 1; // enum ret; - // ret = new ThisType(name, VALUES.length, args..) - ins.anew(classType); - ins.dup(); - ins.load(0, STRING); - ins.getstatic(classType.getInternalName(), values.name, values.desc); - ins.arraylength(); - int idx = 1; - for (int x = 1; x < args.length; x++) { - ins.load(idx, args[x]); - idx += args[x].getSize(); - } - ins.invokespecial(classType.getInternalName(), "", desc, false); - ins.store(vars, classType); - // VALUES = ArrayUtils.add(VALUES, ret) - ins.getstatic(classType.getInternalName(), values.name, values.desc); - ins.load(vars, classType); - ins.invokestatic(ARRAY_UTILS.getInternalName(), "add", ADD_DESC, false); - ins.checkcast(array); - ins.putstatic(classType.getInternalName(), values.name, values.desc); - // EnumHelper.cleanEnumCache(ThisType.class) - ins.visitLdcInsn(classType); - ins.invokestatic(UNSAFE_HACKS.getInternalName(), "cleanEnumCache", CLEAN_DESC, false); - // init ret - ins.load(vars, classType); - ins.invokeinterface(MARKER_IFACE.getInternalName(), "init", "()V"); - // return ret - ins.load(vars, classType); - ins.areturn(classType); - } - }); - return true; - } -} \ No newline at end of file diff --git a/src/main/java/me/eigenraven/lwjgl3ify/core/FixConstantPoolInterfaceMethodRefHelper.java b/src/main/java/me/eigenraven/lwjgl3ify/core/FixConstantPoolInterfaceMethodRefHelper.java deleted file mode 100644 index 29b0f363..00000000 --- a/src/main/java/me/eigenraven/lwjgl3ify/core/FixConstantPoolInterfaceMethodRefHelper.java +++ /dev/null @@ -1,95 +0,0 @@ -package me.eigenraven.lwjgl3ify.core; - -import java.util.concurrent.atomic.AtomicBoolean; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.objectweb.asm.Handle; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.InvokeDynamicInsnNode; -import org.objectweb.asm.tree.MethodNode; - -/** - * Fixes a compilation bug of the java 8 compiler leading to the following exception at runtime: - * cpw.mods.fml.common.LoaderException: java.lang.IncompatibleClassChangeError: Inconsistent constant pool data in - * classfile for class com/gtnewhorizon/structurelib/alignment/IAlignmentLimits. Method 'boolean - * lambda$static$0(net.minecraftforge.common.util.ForgeDirection, - * com.gtnewhorizon.structurelib.alignment.enumerable.Rotation, - * com.gtnewhorizon.structurelib.alignment.enumerable.Flip)' at index 77 is CONSTANT_MethodRef and should be - * CONSTANT_InterfaceMethodRef - */ -public class FixConstantPoolInterfaceMethodRefHelper { - private final Logger LOGGER = LogManager.getLogger("lwjgl3ify"); - - public boolean transform(ClassNode node) { - if (System.getProperty("java.specification.version", "1.8") - .trim() - .startsWith("1.8")) { - return false; - } - final boolean iAmAnInterface = ((node.access & Opcodes.ACC_INTERFACE) != 0); - boolean changesMade = false; - final String internalClassName = node.name; - if (node.methods != null) { - for (MethodNode method : node.methods) { - if (method.instructions != null) { - for (AbstractInsnNode insn : method.instructions) { - changesMade |= validateInstruction(internalClassName, iAmAnInterface, insn); - } - } - } - } - return changesMade; - } - - private boolean validateInstruction(String internalClassName, boolean iAmAnInterface, AbstractInsnNode rawInsn) { - AtomicBoolean changed = new AtomicBoolean(false); - switch (rawInsn.getType()) { - case AbstractInsnNode.INVOKE_DYNAMIC_INSN: - final InvokeDynamicInsnNode insn = (InvokeDynamicInsnNode) rawInsn; - insn.bsm = fixHandle(internalClassName, iAmAnInterface, insn.bsm, changed); - if (insn.bsmArgs != null) { - for (int i = 0; i < insn.bsmArgs.length; i++) { - final Object arg = insn.bsmArgs[i]; - if (arg instanceof Handle) { - Handle handle = (Handle) arg; - insn.bsmArgs[i] = fixHandle(internalClassName, iAmAnInterface, handle, changed); - } - } - } - break; - default: - // no-op - } - return changed.get(); - } - - private Handle fixHandle(String internalClassName, boolean iAmAnInterface, Handle handle, AtomicBoolean changed) { - if (!handle.isInterface()) { - final boolean fixSelfReference = handle.getOwner() - .equals(internalClassName) && iAmAnInterface; - boolean fixJavaReference = false; - if (!fixSelfReference && handle.getOwner() - .startsWith("java/")) { - final String regularName = handle.getOwner() - .replace('/', '.'); - try { - final Class javaClass = Class.forName(regularName); - if (javaClass.isInterface()) { - fixJavaReference = true; - } - } catch (ClassNotFoundException cnfe) { - // no-op - LOGGER.warn("Reference to non-existing java class {} found.", regularName, cnfe); - } - } - if (fixSelfReference || fixJavaReference) { - changed.set(true); - return new Handle(handle.getTag(), handle.getOwner(), handle.getName(), handle.getDesc(), true); - } - } - return handle; - } -} \ No newline at end of file diff --git a/src/main/java/me/eigenraven/lwjgl3ify/core/LwjglRedirectTransformer.java b/src/main/java/me/eigenraven/lwjgl3ify/core/LwjglRedirectTransformer.java deleted file mode 100644 index e0a6b329..00000000 --- a/src/main/java/me/eigenraven/lwjgl3ify/core/LwjglRedirectTransformer.java +++ /dev/null @@ -1,86 +0,0 @@ -package me.eigenraven.lwjgl3ify.core; - -import io.github.crucible.CrucibleModContainer; -import io.github.crucible.bootstrap.CrucibleMetadata; -import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware; -import net.minecraft.launchwrapper.IClassTransformer; -import org.objectweb.asm.*; -import org.objectweb.asm.commons.ClassRemapper; -import org.objectweb.asm.commons.Remapper; - -public class LwjglRedirectTransformer extends Remapper implements IClassTransformer { - - int remaps = 0, calls = 0; - - public static LwjglRedirectTransformer activeInstance = null; - - public LwjglRedirectTransformer() { - // Only use the last constructed transformer - activeInstance = this; - } - - @Override - public byte[] transform(String name, String transformedName, byte[] basicClass) { - if (this != activeInstance) { - return basicClass; - } - if (basicClass == null) { - return null; - } - if (name.contains("lwjgl3ify")) { - return basicClass; - } - ClassReader reader = new ClassReader(basicClass); - ClassWriter writer = new ClassWriter(0); - ClassVisitor visitor = new EscapingClassRemapper(writer); - - try { - reader.accept(visitor, ClassReader.EXPAND_FRAMES); - } catch (Lwjgl3AwareException e) { - return basicClass; - } catch (Exception e) { - CrucibleModContainer.logger.warn("Couldn't remap class {}", transformedName, e); - return basicClass; - } - - return writer.toByteArray(); - } - - final String[] fromPrefixes = new String[] { "org/lwjgl/", "javax/xml/bind/", "javax/servlet/" }; - - final String[] toPrefixes = new String[] { "org/lwjglx/", "jakarta/xml/bind/", "jakarta/servlet/" }; - - @Override - public String map(String typeName) { - if (typeName == null) { - return null; - } - calls++; - for (int pfx = 0; pfx < fromPrefixes.length; pfx++) { - if (typeName.startsWith(fromPrefixes[pfx])) { - remaps++; - return toPrefixes[pfx] + typeName.substring(fromPrefixes[pfx].length()); - } - } - - return typeName; - } - - public static class Lwjgl3AwareException extends RuntimeException { - } - - public class EscapingClassRemapper extends ClassRemapper { - - public EscapingClassRemapper(ClassWriter writer) { - super(writer, LwjglRedirectTransformer.this); - } - - @Override - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - if (desc.equals(Type.getDescriptor(Lwjgl3Aware.class))) { - throw new Lwjgl3AwareException(); - } - return super.visitAnnotation(desc, visible); - } - } -} \ No newline at end of file diff --git a/src/main/java/me/eigenraven/lwjgl3ify/core/UnfinalizeObjectHoldersTransformer.java b/src/main/java/me/eigenraven/lwjgl3ify/core/UnfinalizeObjectHoldersTransformer.java deleted file mode 100644 index f184e8ae..00000000 --- a/src/main/java/me/eigenraven/lwjgl3ify/core/UnfinalizeObjectHoldersTransformer.java +++ /dev/null @@ -1,125 +0,0 @@ -package me.eigenraven.lwjgl3ify.core; - -import java.util.ArrayList; -import java.util.List; - -import io.github.crucible.CrucibleConfigs; -import net.minecraft.launchwrapper.IClassTransformer; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldNode; - -import me.eigenraven.lwjgl3ify.WasFinalObjectHolder; - -public class UnfinalizeObjectHoldersTransformer implements IClassTransformer { - private final Logger LOGGER = LogManager.getLogger("lwjgl3ify"); - // Keep ClassNode-operating transformers together for efficiency (don't read/write the class multiple times) - final ExtensibleEnumTransformerHelper enumTransformer = new ExtensibleEnumTransformerHelper(); - final FixConstantPoolInterfaceMethodRefHelper cpiMethodRefTransformer = new FixConstantPoolInterfaceMethodRefHelper(); - - private static boolean isHolder(List annotations) { - if (annotations == null) { - return false; - } - for (AnnotationNode annotationNode : annotations) { - // Java 17 uses $ instead of / - final String desc = annotationNode.desc.replace('$', '/'); - if (desc.contains("cpw/mods/fml/common/registry/GameRegistry/ObjectHolder")) { - return true; - } - if (desc.contains("cpw/mods/fml/common/registry/GameRegistry/ItemStackHolder")) { - return true; - } - } - return false; - } - - @Override - public byte[] transform(String name, String transformedName, byte[] basicClass) { - if (basicClass == null) { - return null; - } - - if (transformedName.startsWith("me.eigenraven.lwjgl3ify")) { - return basicClass; - } - try { - final ClassReader reader = new ClassReader(basicClass); - final ClassNode node = new ClassNode(); - reader.accept(node, ClassReader.EXPAND_FRAMES); - final Type classType = Type.getType("L" + name.replace('.', '/') + ";"); - - boolean transformClass = false; - boolean workDone = false; - if (transformedName.equals("net.minecraft.init.Blocks") - || transformedName.equals("net.minecraft.init.Items")) { - transformClass = true; - } - transformClass |= isHolder(node.visibleAnnotations); - if (transformedName.equals("team.chisel.init.ChiselBlocks")) { - LOGGER.debug("chiselblocks"); - } - int fieldsModified = 0; - for (FieldNode field : node.fields) { - boolean transform = transformClass; - if (!transform) { - transform = isHolder(field.visibleAnnotations); - } - if (transform) { - workDone = true; - if ((field.access & Opcodes.ACC_FINAL) != 0) { - if (field.visibleAnnotations == null) { - field.visibleAnnotations = new ArrayList<>(1); - field.visibleAnnotations - .add(new AnnotationNode(Type.getDescriptor(WasFinalObjectHolder.class))); - } - field.access = field.access & (~Opcodes.ACC_FINAL); - } - fieldsModified++; - } - } - if (workDone) { - LOGGER.info("Unfinalized {} Holder fields in {}", fieldsModified, transformedName); - } - - if (CrucibleConfigs.configs.lwjgl3ify_extensibleEnums - .contains(transformedName)) { - if (node.interfaces == null) { - node.interfaces = new ArrayList<>(1); - } - node.interfaces.add(enumTransformer.MARKER_IFACE.getInternalName()); - } - - final boolean enumsTransformed = enumTransformer.processClassWithFlags(node, classType); - - if (enumsTransformed) { - workDone = true; - LOGGER.info("Dynamicized enum {}={}", name, transformedName); - } - - final boolean ifaceMethodRefsTransformed = cpiMethodRefTransformer.transform(node); - - if (ifaceMethodRefsTransformed) { - workDone = true; - LOGGER - .warn("Fixed missing CONSTANT_InterfaceMethodRef miscompilation in {}={}", name, transformedName); - } - - if (workDone) { - final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - node.accept(writer); - return writer.toByteArray(); - } - } catch (Exception e) { - LOGGER.error("Error when unfinalizing ObjectHolder transformer", e); - } - return basicClass; - } -} \ No newline at end of file From 4625da60c76f02c707e8df3344b2a750a0daa6a6 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Wed, 14 Jan 2026 13:18:17 +0700 Subject: [PATCH 04/21] Fix wrong ClassPatchManager, ObjectHolderRegistry, EnumHelper --- .../patcher/ClassPatchManager.java.patch | 57 --------- .../registry/ObjectHolderRegistry.java.patch | 22 ---- .../net/minecraft/world/WorldType.java.patch | 4 +- .../chunk/storage/AnvilChunkLoader.java.patch | 42 ++++--- .../common/DimensionManager.java.patch | 40 ++++--- .../common/util/EnumHelper.java.patch | 108 ++++-------------- .../crucible/util/BukkitEnumHelper.java | 76 ++++++++++++ .../craftbukkit/v1_7_R4/block/CraftBlock.java | 3 +- .../v1_7_R4/entity/CraftEntity.java | 4 +- 9 files changed, 155 insertions(+), 201 deletions(-) delete mode 100644 patches/cpw/mods/fml/common/patcher/ClassPatchManager.java.patch delete mode 100644 patches/cpw/mods/fml/common/registry/ObjectHolderRegistry.java.patch create mode 100644 src/main/java/io/github/crucible/util/BukkitEnumHelper.java diff --git a/patches/cpw/mods/fml/common/patcher/ClassPatchManager.java.patch b/patches/cpw/mods/fml/common/patcher/ClassPatchManager.java.patch deleted file mode 100644 index eb3c6262..00000000 --- a/patches/cpw/mods/fml/common/patcher/ClassPatchManager.java.patch +++ /dev/null @@ -1,57 +0,0 @@ ---- ../src-base/minecraft/cpw/mods/fml/common/patcher/ClassPatchManager.java -+++ ../src-work/minecraft/cpw/mods/fml/common/patcher/ClassPatchManager.java -@@ -11,14 +11,15 @@ - import java.util.jar.JarEntry; - import java.util.jar.JarInputStream; - import java.util.jar.JarOutputStream; --import java.util.jar.Pack200; - import java.util.regex.Pattern; - -+import org.apache.commons.compress.harmony.unpack200.Archive; - import org.apache.logging.log4j.Level; - - import net.minecraft.launchwrapper.LaunchClassLoader; - --import LZMA.LzmaInputStream; -+import org.apache.commons.compress.harmony.unpack200.Pack200UnpackerAdapter; -+import org.apache.logging.log4j.Level; - - import com.google.common.base.Joiner; - import com.google.common.base.Throwables; -@@ -30,11 +31,13 @@ - import com.google.common.io.ByteStreams; - import com.google.common.io.Files; - -+import LZMA.LzmaInputStream; - import cpw.mods.fml.relauncher.FMLRelaunchLog; - import cpw.mods.fml.relauncher.Side; - import cpw.mods.fml.repackage.com.nothome.delta.GDiffPatcher; - - public class ClassPatchManager { -+ - public static final ClassPatchManager INSTANCE = new ClassPatchManager(); - - public static final boolean dumpPatched = Boolean.parseBoolean(System.getProperty("fml.dumpPatchedClasses", "false")); -@@ -155,10 +158,20 @@ - FMLRelaunchLog.log(Level.ERROR, "The binary patch set is missing. Either you are in a development environment, or things are not going to work!"); - return; - } -- LzmaInputStream binpatchesDecompressed = new LzmaInputStream(binpatchesCompressed); -+ // Crucible start - apply lwjgl3ify patch -+ // LzmaInputStream binpatchesDecompressed = new LzmaInputStream(binpatchesCompressed); -+ LzmaInputStream binpatchesDecompressedLzma = new LzmaInputStream(binpatchesCompressed); -+ // The Apache pack200 stream chokes on a lzmainputstream for some reason -+ byte[] decompressed = ByteStreams.toByteArray(binpatchesDecompressedLzma); -+ binpatchesDecompressedLzma.close(); -+ ByteArrayInputStream binpatchesDecompressed = new ByteArrayInputStream(decompressed); - ByteArrayOutputStream jarBytes = new ByteArrayOutputStream(); - JarOutputStream jos = new JarOutputStream(jarBytes); -- Pack200.newUnpacker().unpack(binpatchesDecompressed, jos); -+ -+ //Pack200.newUnpacker().unpack(binpatchesDecompressed, jos); -+ new Pack200UnpackerAdapter().unpack(binpatchesDecompressed, jos); -+ // Crucible end -+ - jis = new JarInputStream(new ByteArrayInputStream(jarBytes.toByteArray())); - } - catch (Exception e) diff --git a/patches/cpw/mods/fml/common/registry/ObjectHolderRegistry.java.patch b/patches/cpw/mods/fml/common/registry/ObjectHolderRegistry.java.patch deleted file mode 100644 index 23de36ff..00000000 --- a/patches/cpw/mods/fml/common/registry/ObjectHolderRegistry.java.patch +++ /dev/null @@ -1,22 +0,0 @@ ---- ../src-base/minecraft/cpw/mods/fml/common/registry/ObjectHolderRegistry.java -+++ ../src-work/minecraft/cpw/mods/fml/common/registry/ObjectHolderRegistry.java -@@ -12,6 +12,7 @@ - import cpw.mods.fml.common.discovery.ASMDataTable; - import cpw.mods.fml.common.discovery.ASMDataTable.ASMData; - import cpw.mods.fml.common.registry.GameRegistry.ObjectHolder; -+import me.eigenraven.lwjgl3ify.WasFinalObjectHolder; - - /** - * Internal registry for tracking {@link ObjectHolder} references -@@ -111,6 +112,11 @@ - for (Field f : clazz.getFields()) - { - int mods = f.getModifiers(); -+ // Crucible start - lwjgl3ify patch -+ if (f.isAnnotationPresent(WasFinalObjectHolder.class)) { -+ mods |= Modifier.FINAL; -+ } -+ // Crucible end - boolean isMatch = Modifier.isPublic(mods) && Modifier.isStatic(mods) && Modifier.isFinal(mods); - if (!isMatch || f.isAnnotationPresent(ObjectHolder.class)) - { diff --git a/patches/net/minecraft/world/WorldType.java.patch b/patches/net/minecraft/world/WorldType.java.patch index 3fcd4197..6b94f495 100644 --- a/patches/net/minecraft/world/WorldType.java.patch +++ b/patches/net/minecraft/world/WorldType.java.patch @@ -4,7 +4,7 @@ import net.minecraft.world.gen.layer.GenLayerZoom; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; -+import net.minecraftforge.common.util.EnumHelper; // Cauldron ++import io.github.crucible.util.BukkitEnumHelper; public class WorldType { @@ -15,7 +15,7 @@ + // Cauldron start - add worldtype for bukkit if it does not already exist + if (org.bukkit.WorldType.getByName(p_i1960_2_) == null) + { -+ EnumHelper.addBukkitWorldType(p_i1960_2_); ++ BukkitEnumHelper.addBukkitWorldType(p_i1960_2_); + } + // Cauldron end } diff --git a/patches/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch b/patches/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch index ac772a00..a80299e7 100644 --- a/patches/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch +++ b/patches/net/minecraft/world/chunk/storage/AnvilChunkLoader.java.patch @@ -8,21 +8,27 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -@@ -33,6 +34,13 @@ +@@ -26,6 +27,7 @@ + import net.minecraft.world.storage.IThreadedFileIO; + import net.minecraft.world.storage.ThreadedFileIOBase; + import net.minecraftforge.common.MinecraftForge; ++import io.github.crucible.util.BukkitEnumHelper; + import net.minecraftforge.event.world.ChunkDataEvent; + + import org.apache.logging.log4j.Level; +@@ -33,6 +35,11 @@ import org.apache.logging.log4j.Logger; import cpw.mods.fml.common.FMLLog; +// Cauldron start -+import java.util.Map; +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.cauldron.CauldronUtils; -+import net.minecraftforge.common.util.EnumHelper; +import cpw.mods.fml.common.asm.transformers.SideTransformer; +// Cauldron end public class AnvilChunkLoader implements IChunkLoader, IThreadedFileIO { -@@ -41,6 +49,7 @@ +@@ -41,6 +48,7 @@ private Set pendingAnvilChunksCoordinates = new HashSet(); private Object syncLockObject = new Object(); public final File chunkSaveLocation; @@ -30,7 +36,7 @@ private static final String __OBFID = "CL_00000384"; public AnvilChunkLoader(File p_i2003_1_) -@@ -68,18 +77,22 @@ +@@ -68,18 +76,22 @@ } } @@ -54,7 +60,7 @@ return chunk; } -@@ -156,8 +169,8 @@ +@@ -156,8 +168,8 @@ if (!chunk.isAtLocation(p_75822_2_, p_75822_3_)) { logger.error("Chunk file at " + p_75822_2_ + "," + p_75822_3_ + " is in the wrong location; relocating. (Expected " + p_75822_2_ + ", " + p_75822_3_ + ", got " + chunk.xPosition + ", " + chunk.zPosition + ")"); @@ -65,7 +71,7 @@ // Have to move tile entities since we don't load them at this stage NBTTagList tileEntities = p_75822_4_.getCompoundTag("Level").getTagList("TileEntities", 10); -@@ -187,8 +200,18 @@ +@@ -187,8 +199,18 @@ public void saveChunk(World p_75816_1_, Chunk p_75816_2_) throws MinecraftException, IOException { @@ -85,7 +91,7 @@ try { NBTTagCompound nbttagcompound = new NBTTagCompound(); -@@ -230,7 +253,7 @@ +@@ -230,7 +252,7 @@ public boolean writeNextIO() { @@ -94,7 +100,7 @@ Object object = this.syncLockObject; synchronized (this.syncLockObject) -@@ -240,15 +263,15 @@ +@@ -240,15 +262,15 @@ return false; } @@ -114,7 +120,7 @@ } catch (Exception exception) { -@@ -259,7 +282,7 @@ +@@ -259,7 +281,7 @@ return true; } @@ -123,7 +129,7 @@ { DataOutputStream dataoutputstream = RegionFileCache.getChunkOutputStream(this.chunkSaveLocation, p_75821_1_.chunkCoordinate.chunkXPos, p_75821_1_.chunkCoordinate.chunkZPos); CompressedStreamTools.write(p_75821_1_.nbtTags, dataoutputstream); -@@ -307,19 +330,19 @@ +@@ -307,19 +329,19 @@ if (extendedblockstorage.getBlockMSBArray() != null) { @@ -148,7 +154,7 @@ } nbttaglist.appendTag(nbttagcompound1); -@@ -379,17 +402,16 @@ +@@ -379,17 +401,16 @@ } p_75820_3_.setTag("TileEntities", nbttaglist3); @@ -172,7 +178,7 @@ NBTTagCompound nbttagcompound2 = new NBTTagCompound(); nbttagcompound2.setInteger("i", Block.getIdFromBlock(nextticklistentry.func_151351_a())); nbttagcompound2.setInteger("x", nextticklistentry.xCoord); -@@ -401,7 +423,6 @@ +@@ -401,7 +422,6 @@ } p_75820_3_.setTag("TileTicks", nbttaglist1); @@ -180,7 +186,7 @@ } private Chunk readChunkFromNBT(World p_75823_1_, NBTTagCompound p_75823_2_) -@@ -455,6 +476,8 @@ +@@ -455,6 +475,8 @@ public void loadEntities(World p_75823_1_, NBTTagCompound p_75823_2_, Chunk chunk) { @@ -189,7 +195,7 @@ NBTTagList nbttaglist1 = p_75823_2_.getTagList("Entities", 10); if (nbttaglist1 != null) -@@ -468,24 +491,31 @@ +@@ -468,24 +490,31 @@ if (entity2 != null) { chunk.addEntity(entity2); @@ -230,7 +236,7 @@ NBTTagList nbttaglist2 = p_75823_2_.getTagList("TileEntities", 10); if (nbttaglist2 != null) -@@ -497,11 +527,35 @@ +@@ -497,11 +526,35 @@ if (tileentity != null) { @@ -252,7 +258,7 @@ + SideTransformer.allowInvalidSide = false; + } + // inject TE into InventoryType to support inventory events -+ EnumHelper.addInventoryType(tileentity); ++ BukkitEnumHelper.addInventoryType(tileentity); + this.checkedTileEntities.add(tileentity.getClass()); + } + // Cauldron end @@ -266,7 +272,7 @@ if (p_75823_2_.hasKey("TileTicks", 9)) { NBTTagList nbttaglist3 = p_75823_2_.getTagList("TileTicks", 10); -@@ -515,6 +569,7 @@ +@@ -515,6 +568,7 @@ } } } diff --git a/patches/net/minecraftforge/common/DimensionManager.java.patch b/patches/net/minecraftforge/common/DimensionManager.java.patch index 6844fd93..188edc5b 100644 --- a/patches/net/minecraftforge/common/DimensionManager.java.patch +++ b/patches/net/minecraftforge/common/DimensionManager.java.patch @@ -1,14 +1,23 @@ --- ../src-base/minecraft/net/minecraftforge/common/DimensionManager.java +++ ../src-work/minecraft/net/minecraftforge/common/DimensionManager.java -@@ -11,6 +11,7 @@ +@@ -11,6 +11,8 @@ import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; +import io.github.crucible.CrucibleConfigs; ++import io.github.crucible.util.BukkitEnumHelper; import org.apache.logging.log4j.Level; import com.google.common.collect.HashMultiset; -@@ -34,6 +35,15 @@ +@@ -21,7 +23,6 @@ + import cpw.mods.fml.common.FMLLog; + import net.minecraft.nbt.NBTTagCompound; + import net.minecraft.server.MinecraftServer; +-import net.minecraft.world.MinecraftException; + import net.minecraft.world.World; + import net.minecraft.world.WorldManager; + import net.minecraft.world.WorldProvider; +@@ -34,6 +35,14 @@ import net.minecraft.world.storage.ISaveHandler; import net.minecraft.world.storage.SaveHandler; import net.minecraftforge.event.world.WorldEvent; @@ -16,7 +25,6 @@ +import net.minecraft.server.dedicated.DedicatedServer; +import net.minecraft.world.chunk.storage.AnvilSaveHandler; +import net.minecraftforge.cauldron.CauldronUtils; -+import net.minecraftforge.common.util.EnumHelper; +import org.bukkit.World.Environment; +import org.bukkit.WorldCreator; +import org.bukkit.generator.ChunkGenerator; @@ -24,7 +32,7 @@ public class DimensionManager { -@@ -46,6 +56,11 @@ +@@ -46,6 +55,11 @@ private static BitSet dimensionMap = new BitSet(Long.SIZE << 4); private static ConcurrentMap weakWorldMap = new MapMaker().weakKeys().weakValues().makeMap(); private static Multiset leakedWorlds = HashMultiset.create(); @@ -36,7 +44,7 @@ public static boolean registerProviderType(int id, Class provider, boolean keepLoaded) { -@@ -53,7 +68,23 @@ +@@ -53,7 +67,23 @@ { return false; } @@ -60,7 +68,7 @@ spawnSettings.put(id, keepLoaded); return true; } -@@ -157,28 +188,33 @@ +@@ -157,28 +187,33 @@ public static Integer[] getIDs(boolean check) { @@ -108,7 +116,7 @@ return getIDs(); } public static Integer[] getIDs() -@@ -191,12 +227,23 @@ +@@ -191,12 +226,23 @@ if (world != null) { worlds.put(id, world); @@ -133,7 +141,7 @@ worlds.remove(id); MinecraftServer.getServer().worldTickTimes.remove(id); FMLLog.info("Unloading dimension %d", id); -@@ -224,6 +271,7 @@ +@@ -224,6 +270,7 @@ } public static void initDimension(int dim) { @@ -141,7 +149,7 @@ WorldServer overworld = getWorld(0); if (overworld == null) { -@@ -231,6 +279,12 @@ +@@ -231,6 +278,12 @@ } try { @@ -154,7 +162,7 @@ DimensionManager.getProviderType(dim); } catch (Exception e) -@@ -242,9 +296,63 @@ +@@ -242,9 +295,63 @@ ISaveHandler savehandler = overworld.getSaveHandler(); WorldSettings worldSettings = new WorldSettings(overworld.getWorldInfo()); @@ -219,7 +227,7 @@ if (!mcServer.isSinglePlayer()) { world.getWorldInfo().setGameType(mcServer.getGameType()); -@@ -253,6 +361,79 @@ +@@ -253,6 +360,79 @@ mcServer.func_147139_a(mcServer.func_147135_j()); } @@ -299,7 +307,7 @@ public static WorldServer getWorld(int id) { return worlds.get(id); -@@ -266,7 +447,7 @@ +@@ -266,7 +446,7 @@ public static boolean shouldLoadSpawn(int dim) { int id = getProviderType(dim); @@ -308,7 +316,7 @@ } static -@@ -306,7 +487,8 @@ +@@ -306,7 +486,8 @@ } public static void unloadWorld(int id) { @@ -318,7 +326,7 @@ } /* -@@ -315,26 +497,9 @@ +@@ -315,26 +496,9 @@ public static void unloadWorlds(Hashtable worldTickTimes) { for (int id : unloadQueue) { WorldServer w = worlds.get(id); @@ -347,7 +355,7 @@ } } unloadQueue.clear(); -@@ -425,4 +590,45 @@ +@@ -425,4 +589,45 @@ return null; } } @@ -359,7 +367,7 @@ + if (env == null) // Cauldron if environment not found, register one + { + providerName = providerName.replace("WorldProvider", ""); -+ env = EnumHelper.addBukkitEnvironment(dim, providerName.toUpperCase()); ++ env = BukkitEnumHelper.addBukkitEnvironment(dim, providerName.toUpperCase()); + Environment.registerEnvironment(env); + } + return env; diff --git a/patches/net/minecraftforge/common/util/EnumHelper.java.patch b/patches/net/minecraftforge/common/util/EnumHelper.java.patch index be667f59..d724d7fa 100644 --- a/patches/net/minecraftforge/common/util/EnumHelper.java.patch +++ b/patches/net/minecraftforge/common/util/EnumHelper.java.patch @@ -1,6 +1,6 @@ --- ../src-base/minecraft/net/minecraftforge/common/util/EnumHelper.java +++ ../src-work/minecraft/net/minecraftforge/common/util/EnumHelper.java -@@ -1,8 +1,11 @@ +@@ -1,9 +1,11 @@ package net.minecraftforge.common.util; +import java.lang.invoke.MethodHandle; @@ -8,32 +8,24 @@ import java.lang.reflect.*; import java.util.*; +-import cpw.mods.fml.common.FMLLog; +import com.google.common.base.Throwables; - import cpw.mods.fml.common.FMLLog; import net.minecraft.block.BlockPressurePlate.Sensitivity; import net.minecraft.block.material.Material; -@@ -21,186 +24,120 @@ + import net.minecraft.enchantment.EnumEnchantmentType; +@@ -20,187 +22,110 @@ + import net.minecraft.util.MovingObjectPosition.MovingObjectType; import net.minecraft.world.EnumSkyBlock; import net.minecraft.world.gen.structure.StructureStrongholdPieces.Stronghold.Door; - import net.minecraftforge.classloading.FMLForgePlugin; +-import net.minecraftforge.classloading.FMLForgePlugin; +// Cauldron start -+import cpw.mods.fml.relauncher.ReflectionHelper; -+import net.minecraft.inventory.IInventory; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.tileentity.TileEntity; -public class EnumHelper -{ - private static Object reflectionFactory = null; +import org.apache.commons.lang3.ArrayUtils; -+import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; -+import org.bukkit.World; -+import org.bukkit.WorldType; -+import org.bukkit.block.Biome; -+import org.bukkit.entity.EntityType; -+import org.bukkit.event.inventory.InventoryType; +// Cauldron end + +public class EnumHelper { @@ -270,7 +262,7 @@ System.arraycopy(lookup, 1, paramTypes, 0, paramTypes.length); } return addEnum(enumType, enumName, paramTypes, paramValues); -@@ -210,81 +147,186 @@ +@@ -210,81 +135,130 @@ } @SuppressWarnings("unchecked") @@ -351,28 +343,12 @@ - FMLLog.severe(" %s %s: %s", mods, field.getName(), field.getType().getName()); - } - return null; +- } + private static final Class extensibleEnumIface; + private static final MethodHandle setFieldHandle; -+ -+ static { -+ try { -+ extensibleEnumIface = Class.forName("me.eigenraven.lwjgl3ify.IExtensibleEnum"); -+ Class unsafeHacks = Class.forName("me.eigenraven.lwjgl3ify.UnsafeHacks"); -+ Method setFieldM = unsafeHacks.getMethod("setField", Field.class, Object.class, Object.class); -+ setFieldHandle = MethodHandles.publicLookup() -+ .unreflect(setFieldM); -+ } catch (ReflectiveOperationException e) { -+ throw new RuntimeException(e); - } -+ } - valuesField.setAccessible(true); -+ // Cauldron start -+ public static Biome addBukkitBiome(String name) -+ { -+ return (Biome)addEnum(Biome.class, name, new Class[0], new Object[0]); -+ } - +- - try - { - T[] previousValues = (T[])valuesField.get(enumType); @@ -381,67 +357,33 @@ - values.add(newValue); - setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0))); - cleanEnumCache(enumType); -+ public static World.Environment addBukkitEnvironment(int id, String name) -+ { -+ return (World.Environment)addEnum(World.Environment.class, name, new Class[] { Integer.TYPE }, new Object[] { Integer.valueOf(id) }); -+ } - +- - return newValue; -- } ++ static { ++ try { ++ extensibleEnumIface = Class.forName("me.eigenraven.lwjgl3ify.IExtensibleEnum"); ++ Class unsafeHacks = Class.forName("me.eigenraven.lwjgl3ify.UnsafeHacks"); ++ Method setFieldM = unsafeHacks.getMethod("setField", Field.class, Object.class, Object.class); ++ setFieldHandle = MethodHandles.publicLookup() ++ .unreflect(setFieldM); ++ } catch (ReflectiveOperationException e) { ++ throw new RuntimeException(e); + } - catch (Exception e) - { - e.printStackTrace(); - throw new RuntimeException(e.getMessage(), e); - } -+ public static WorldType addBukkitWorldType(String name) -+ { -+ WorldType worldType = addEnum(WorldType.class, name, new Class [] { String.class }, new Object[] { name }); -+ Map BY_NAME = ReflectionHelper.getPrivateValue(WorldType.class, null, "BY_NAME"); -+ BY_NAME.put(name.toUpperCase(), worldType); -+ -+ return worldType; } - static -+ public static EntityType addBukkitEntityType(String name, Class clazz, int typeId, boolean independent) { -+ String entityType = name.replace("-", "_").toUpperCase(); -+ EntityType bukkitType = addEnum(EntityType.class, entityType, new Class[] { String.class, Class.class, Integer.TYPE, Boolean.TYPE }, new Object[] { name, clazz, typeId, independent }); -+ -+ Map NAME_MAP = ReflectionHelper.getPrivateValue(EntityType.class, null, "NAME_MAP"); -+ Map ID_MAP = ReflectionHelper.getPrivateValue(EntityType.class, null, "ID_MAP"); -+ -+ NAME_MAP.put(name.toLowerCase(), bukkitType); -+ ID_MAP.put((short)typeId, bukkitType); -+ -+ -+ return bukkitType; -+ } -+ -+ public static InventoryType addInventoryType(TileEntity tileentity) - { +- { - if (!isSetup) -+ if (!IInventory.class.isAssignableFrom(tileentity.getClass())) return null; -+ String id = (String)TileEntity.classToNameMap.get(tileentity.getClass()); -+ -+ try - { +- { - setup(); -+ IInventory teInv = (IInventory)tileentity; -+ int size = teInv.getSizeInventory(); -+ return addEnum(org.bukkit.event.inventory.InventoryType.class, id, new Class[]{Integer.TYPE, String.class}, new Object[]{size, id}); - } -+ catch (Throwable e) -+ { -+ if (MinecraftServer.getServer().tileEntityConfig.enableTEInventoryWarning.getValue()) -+ { -+ logger.log(Level.WARN, "Could not create inventory type " + tileentity.getClass().getName() + " Exception: " + e.toString()); -+ logger.log(Level.WARN, "Could not determine default inventory size for type " + tileentity.getClass().getName() + " using size of 9"); -+ } -+ return addEnum(org.bukkit.event.inventory.InventoryType.class, id, new Class[]{Integer.TYPE, String.class}, new Object[]{9, id}); -+ } - } -+ -+// @SuppressWarnings("unchecked") +- } +- } ++ // @SuppressWarnings("unchecked") +// public static > T replaceEnum(Class enumType, String enumName, int ordinal, Class[] paramTypes, Object[] paramValues) +// { +// diff --git a/src/main/java/io/github/crucible/util/BukkitEnumHelper.java b/src/main/java/io/github/crucible/util/BukkitEnumHelper.java new file mode 100644 index 00000000..b22be111 --- /dev/null +++ b/src/main/java/io/github/crucible/util/BukkitEnumHelper.java @@ -0,0 +1,76 @@ +package io.github.crucible.util; + +import cpw.mods.fml.relauncher.ReflectionHelper; +import net.minecraft.inventory.IInventory; +import net.minecraft.server.MinecraftServer; +import net.minecraft.tileentity.TileEntity; +import net.minecraftforge.common.util.EnumHelper; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.bukkit.World; +import org.bukkit.WorldType; +import org.bukkit.block.Biome; +import org.bukkit.entity.EntityType; +import org.bukkit.event.inventory.InventoryType; + +import java.util.Map; + +public class BukkitEnumHelper { + private static final Logger logger = LogManager.getLogger(); + // Cauldron start + public static Biome addBukkitBiome(String name) + { + return (Biome) EnumHelper.addEnum(Biome.class, name, new Class[0], new Object[0]); + } + + public static World.Environment addBukkitEnvironment(int id, String name) + { + return (World.Environment) EnumHelper.addEnum(World.Environment.class, name, new Class[] { Integer.TYPE }, new Object[] { Integer.valueOf(id) }); + } + + public static WorldType addBukkitWorldType(String name) + { + WorldType worldType = EnumHelper.addEnum(WorldType.class, name, new Class [] { String.class }, new Object[] { name }); + Map BY_NAME = ReflectionHelper.getPrivateValue(WorldType.class, null, "BY_NAME"); + BY_NAME.put(name.toUpperCase(), worldType); + + return worldType; + } + + public static EntityType addBukkitEntityType(String name, Class clazz, int typeId, boolean independent) { + String entityType = name.replace("-", "_").toUpperCase(); + EntityType bukkitType = EnumHelper.addEnum(EntityType.class, entityType, new Class[] { String.class, Class.class, Integer.TYPE, Boolean.TYPE }, new Object[] { name, clazz, typeId, independent }); + + Map NAME_MAP = ReflectionHelper.getPrivateValue(EntityType.class, null, "NAME_MAP"); + Map ID_MAP = ReflectionHelper.getPrivateValue(EntityType.class, null, "ID_MAP"); + + NAME_MAP.put(name.toLowerCase(), bukkitType); + ID_MAP.put((short)typeId, bukkitType); + + + return bukkitType; + } + + public static InventoryType addInventoryType(TileEntity tileentity) + { + if (!IInventory.class.isAssignableFrom(tileentity.getClass())) return null; + String id = (String)TileEntity.classToNameMap.get(tileentity.getClass()); + + try + { + IInventory teInv = (IInventory)tileentity; + int size = teInv.getSizeInventory(); + return EnumHelper.addEnum(InventoryType.class, id, new Class[]{Integer.TYPE, String.class}, new Object[]{size, id}); + } + catch (Throwable e) + { + if (MinecraftServer.getServer().tileEntityConfig.enableTEInventoryWarning.getValue()) + { + logger.log(Level.WARN, "Could not create inventory type " + tileentity.getClass().getName() + " Exception: " + e.toString()); + logger.log(Level.WARN, "Could not determine default inventory size for type " + tileentity.getClass().getName() + " using size of 9"); + } + return EnumHelper.addEnum(InventoryType.class, id, new Class[]{Integer.TYPE, String.class}, new Object[]{9, id}); + } + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/v1_7_R4/block/CraftBlock.java b/src/main/java/org/bukkit/craftbukkit/v1_7_R4/block/CraftBlock.java index 8021f905..542adced 100644 --- a/src/main/java/org/bukkit/craftbukkit/v1_7_R4/block/CraftBlock.java +++ b/src/main/java/org/bukkit/craftbukkit/v1_7_R4/block/CraftBlock.java @@ -14,6 +14,7 @@ import net.minecraft.world.EnumSkyBlock; import net.minecraft.world.biome.BiomeGenBase; import net.minecraftforge.cauldron.block.CraftCustomContainer; +import io.github.crucible.util.BukkitEnumHelper; import org.bukkit.Chunk; import org.bukkit.Location; import org.bukkit.Material; @@ -191,7 +192,7 @@ public static BiomeGenBase biomeToBiomeBase(Biome bio) { int id = BiomeGenBase.getBiome(i).biomeID; System.out.println("Adding biome mapping " + BiomeGenBase.getBiome(i).biomeID + " " + name + " at BiomeBase[" + i + "]"); - net.minecraftforge.common.util.EnumHelper.addBukkitBiome(name); // Forge + BukkitEnumHelper.addBukkitBiome(name); // Forge BIOME_MAPPING[BiomeGenBase.getBiome(i).biomeID] = Enum.valueOf(Biome.class, name); // Cauldron end } diff --git a/src/main/java/org/bukkit/craftbukkit/v1_7_R4/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/v1_7_R4/entity/CraftEntity.java index 2ea2aea7..242e8d58 100644 --- a/src/main/java/org/bukkit/craftbukkit/v1_7_R4/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/v1_7_R4/entity/CraftEntity.java @@ -3,7 +3,7 @@ import net.minecraft.entity.Entity; import io.github.crucible.entity.CraftCustomEntity; import net.minecraftforge.common.DimensionManager; -import net.minecraftforge.common.util.EnumHelper; +import io.github.crucible.util.BukkitEnumHelper; import net.minecraftforge.common.util.FakePlayerFactory; import org.bukkit.EntityEffect; import org.bukkit.Location; @@ -501,7 +501,7 @@ public static void initMappings() { int entityId = getEntityTypeIDfromClass(entityClass); Class bukkitEntityClass = CraftEntity.getEntityClass(entityClass); - EnumHelper.addBukkitEntityType(entityName, bukkitEntityClass, entityId, false); + BukkitEnumHelper.addBukkitEntityType(entityName, bukkitEntityClass, entityId, false); } } // Cauldron end From 8723d6e9eb26c9ec8ba63a6ba838a08735e23f29 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:07:03 +0700 Subject: [PATCH 05/21] Drop pw.prok.imagine --- build.gradle | 2 - .../fml/relauncher/CoreModManager.java.patch | 7 +- .../bootstrap/CrucibleCoremodHook.java | 3 + .../patches/RecurrentComplexTransformer.java | 79 ++++++++++------ .../crucible/patches/StreamsTransformer.java | 77 +++++++++------ .../java/thermos/ThermosClassTransformer.java | 94 ++++++++++++++----- 6 files changed, 169 insertions(+), 93 deletions(-) diff --git a/build.gradle b/build.gradle index 0c1a02a6..50e54003 100644 --- a/build.gradle +++ b/build.gradle @@ -105,7 +105,6 @@ dependencies { libraries 'it.unimi.dsi:fastutil:8.5.12' // Other libs - libraries 'pw.prok:Damask:0.1.2@jar' libraries 'commons-cli:commons-cli:1.3@jar' libraries 'org.slf4j:slf4j-simple:1.6.2@jar' libraries 'org.eclipse.jetty:jetty-servlet:9.0.3.v20130506@jar' @@ -135,7 +134,6 @@ dependencies { libraries 'org.xerial:sqlite-jdbc:3.7.2' libraries 'mysql:mysql-connector-java:5.1.14' libraries 'javax.persistence:persistence-api:1.0.2' - libraries 'pw.prok:KImagine:0.2.0@jar' libraries 'org.apache.httpcomponents:httpclient:4.4.1' libraries 'net.openhft:affinity:3.0.1' libraries 'org.fusesource.jansi:jansi:1.11' diff --git a/patches/cpw/mods/fml/relauncher/CoreModManager.java.patch b/patches/cpw/mods/fml/relauncher/CoreModManager.java.patch index 496115d1..43a48556 100644 --- a/patches/cpw/mods/fml/relauncher/CoreModManager.java.patch +++ b/patches/cpw/mods/fml/relauncher/CoreModManager.java.patch @@ -29,7 +29,7 @@ import net.minecraft.launchwrapper.ITweaker; import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.LaunchClassLoader; -@@ -57,10 +61,11 @@ +@@ -57,6 +61,7 @@ import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions; public class CoreModManager { @@ -37,11 +37,6 @@ private static final Attributes.Name COREMODCONTAINSFMLMOD = new Attributes.Name("FMLCorePluginContainsFMLMod"); private static final Attributes.Name MODTYPE = new Attributes.Name("ModType"); private static final Attributes.Name MODSIDE = new Attributes.Name("ModSide"); -- private static String[] rootPlugins = { "cpw.mods.fml.relauncher.FMLCorePlugin", "net.minecraftforge.classloading.FMLForgePlugin" }; -+ private static String[] rootPlugins = { "cpw.mods.fml.relauncher.FMLCorePlugin", "net.minecraftforge.classloading.FMLForgePlugin", "pw.prok.imagine.ImagineLoadingPlugin" }; - private static List loadedCoremods = Lists.newArrayList(); - private static List loadPlugins; - private static boolean deobfuscatedEnvironment; @@ -70,6 +75,7 @@ private static List accessTransformers = Lists.newArrayList(); diff --git a/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java b/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java index c829d9ab..85df6e8d 100644 --- a/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java +++ b/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java @@ -15,5 +15,8 @@ public static void coremodHandleLaunch(File mcDir, LaunchClassLoader classLoader } catch (ClassNotFoundException e) { throw new RuntimeException(e); } + classLoader.registerTransformer("io.github.crucible.patches.RecurrentComplexTransformer"); + classLoader.registerTransformer("io.github.crucible.patches.StreamsTransformer"); + classLoader.registerTransformer("thermos.ThermosClassTransformer"); } } diff --git a/src/main/java/io/github/crucible/patches/RecurrentComplexTransformer.java b/src/main/java/io/github/crucible/patches/RecurrentComplexTransformer.java index 5a9ac637..93ffe5aa 100644 --- a/src/main/java/io/github/crucible/patches/RecurrentComplexTransformer.java +++ b/src/main/java/io/github/crucible/patches/RecurrentComplexTransformer.java @@ -1,41 +1,62 @@ package io.github.crucible.patches; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.MethodInsnNode; -import pw.prok.imagine.asm.ImagineASM; -import pw.prok.imagine.asm.Transformer; +import cpw.mods.fml.common.FMLLog; +import net.minecraft.launchwrapper.IClassTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.*; import static org.objectweb.asm.Opcodes.INVOKESTATIC; -@Transformer.RegisterTransformer -public class RecurrentComplexTransformer implements Transformer { +public class RecurrentComplexTransformer implements IClassTransformer { + private static final String TARGET_CLASS = + "ivorius.reccomplex.structures.generic.matchers.BiomeMatcher"; + @Override - public void transform(ImagineASM asm) { - if (asm.is("ivorius.reccomplex.structures.generic.matchers.BiomeMatcher")) { - System.out.println("[Crucible] Found ivorius.reccomplex.structures.generic.matchers.BiomeMatcher, trying to patch it!"); - InsnList instructions = asm.method("ofTypes", "([Lnet/minecraftforge/common/BiomeDictionary$Type;)Ljava/lang/String;").instructions(); - - AbstractInsnNode abstractInsnNode = instructions.getFirst(); - boolean appliedPatch = false; - while (abstractInsnNode != null) { - if (abstractInsnNode.getOpcode() == INVOKESTATIC) { - MethodInsnNode methodInsnNode = (MethodInsnNode) abstractInsnNode; - if ("joptsimple/internal/Strings".equals(methodInsnNode.owner) && - "join".equals(methodInsnNode.name) && - "(Ljava/util/List;Ljava/lang/String;)Ljava/lang/String;" - .equals(methodInsnNode.desc)) { - methodInsnNode.owner = "io/github/crucible/patches/AsmHooks"; - appliedPatch = true; - System.out.println("[Crucible] Patched joptsimple.internal.Strings#join() call!"); + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (!transformedName.equals(TARGET_CLASS)) { + return basicClass; + } + + FMLLog.info("[Crucible] Found %s, attempting ASM patch", TARGET_CLASS); + + ClassNode classNode = new ClassNode(); + new ClassReader(basicClass).accept(classNode, 0); + + boolean patched = false; + + for (MethodNode method : classNode.methods) { + if (!method.name.equals("ofTypes")) continue; + if (!method.desc.equals("([Lnet/minecraftforge/common/BiomeDictionary$Type;)Ljava/lang/String;")) + continue; + + InsnList insns = method.instructions; + + for (AbstractInsnNode insn = insns.getFirst(); insn != null; insn = insn.getNext()) { + if (insn.getOpcode() == INVOKESTATIC && insn instanceof MethodInsnNode) { + MethodInsnNode m = (MethodInsnNode) insn; + + if (m.owner.equals("joptsimple/internal/Strings") + && m.name.equals("join") + && m.desc.equals("(Ljava/util/List;Ljava/lang/String;)Ljava/lang/String;")) { + + m.owner = "io/github/crucible/patches/AsmHooks"; + patched = true; + + FMLLog.info("[Crucible] Patched Strings.join() call in BiomeMatcher"); + break; } } - abstractInsnNode = abstractInsnNode.getNext(); - } - if (!appliedPatch) { - System.out.println("[Crucible] RecurrentComplexTransformer: " + - "unable to find joptsimple.internal.Strings#join(), skipping it!"); } } + + if (!patched) { + FMLLog.warning("[Crucible] Failed to patch BiomeMatcher: join() call not found"); + return basicClass; + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + classNode.accept(writer); + return writer.toByteArray(); } } diff --git a/src/main/java/io/github/crucible/patches/StreamsTransformer.java b/src/main/java/io/github/crucible/patches/StreamsTransformer.java index fb947bee..2c6f82b7 100644 --- a/src/main/java/io/github/crucible/patches/StreamsTransformer.java +++ b/src/main/java/io/github/crucible/patches/StreamsTransformer.java @@ -1,42 +1,59 @@ package io.github.crucible.patches; +import net.minecraft.launchwrapper.IClassTransformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.InsnNode; -import pw.prok.imagine.asm.ImagineASM; -import pw.prok.imagine.asm.Transformer; +import org.objectweb.asm.tree.*; import java.util.Iterator; -@Transformer.RegisterTransformer -public class StreamsTransformer implements Transformer { +public class StreamsTransformer implements IClassTransformer { + private static final String TARGET_CLASS = + "streams.world.gen.structure.RiverComponent$"; + @Override - public void transform(ImagineASM asm) { - if (asm.is("streams.world.gen.structure.RiverComponent$")) { - System.out.println("[Crucible] Found streams.world.gen.structure.RiverComponent$, trying to patch it!"); - InsnList instructions = asm.method("", "()V").instructions(); //We just need to replace a number - AbstractInsnNode toReplace = null; - Iterator i = instructions.iterator(); - while (i.hasNext()) { - AbstractInsnNode ins = i.next(); - if (ins.getOpcode() == Opcodes.ICONST_2) { - if (ins.getNext() instanceof FieldInsnNode) { - FieldInsnNode fieldAccess = (FieldInsnNode) ins.getNext(); - if (fieldAccess.name.contains("MinSourceBackWallHeight")) { - toReplace = fieldAccess.getPrevious(); - break; - } - } - } + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (!transformedName.equals(TARGET_CLASS)) { + return basicClass; + } + + System.out.println("[Crucible] Found " + TARGET_CLASS + ", patching…"); + + ClassNode classNode = new ClassNode(); + ClassReader reader = new ClassReader(basicClass); + reader.accept(classNode, 0); + + for (MethodNode method : classNode.methods) { + if ("".equals(method.name) && "()V".equals(method.desc)) { + patchConstructor(method); } - if (toReplace == null) { - System.out.println("[Crucible] Unable to find MinSourceBackWallHeight, skipping patch!"); - } else { - instructions.set(toReplace, new InsnNode(Opcodes.ICONST_0)); - System.out.println("[Crucible] Patched MinSourceBackWallHeight's previous opcode!"); + } + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + classNode.accept(writer); + return writer.toByteArray(); + } + + private void patchConstructor(MethodNode method) { + InsnList insns = method.instructions; + + for (Iterator it = insns.iterator(); it.hasNext(); ) { + AbstractInsnNode insn = it.next(); + + if (insn.getOpcode() == Opcodes.ICONST_2 && + insn.getNext() instanceof FieldInsnNode) { + + FieldInsnNode field = (FieldInsnNode) insn.getNext(); + + if (field.name.contains("MinSourceBackWallHeight")) { + insns.set(insn, new InsnNode(Opcodes.ICONST_0)); + System.out.println("[Crucible] Patched MinSourceBackWallHeight"); + return; + } } } + + System.out.println("[Crucible] Failed to find MinSourceBackWallHeight"); } } diff --git a/src/main/java/thermos/ThermosClassTransformer.java b/src/main/java/thermos/ThermosClassTransformer.java index 73f9ed24..385fd00f 100644 --- a/src/main/java/thermos/ThermosClassTransformer.java +++ b/src/main/java/thermos/ThermosClassTransformer.java @@ -1,40 +1,82 @@ package thermos; import cpw.mods.fml.common.FMLLog; +import net.minecraft.launchwrapper.IClassTransformer; import org.apache.logging.log4j.Level; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.InsnNode; -import org.objectweb.asm.tree.IntInsnNode; -import pw.prok.imagine.asm.ImagineASM; -import pw.prok.imagine.asm.Transformer; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.*; import static org.objectweb.asm.Opcodes.*; -@Transformer.RegisterTransformer -public class ThermosClassTransformer implements Transformer { +public class ThermosClassTransformer implements IClassTransformer { + private static final String TARGET_CLASS = + "climateControl/utils/ChunkGeneratorExtractor"; + @Override - public void transform(final ImagineASM asm) { - if (asm.is("climateControl.utils.ChunkGeneratorExtractor")) { - boolean undergroundBiomesInstalled = false; - try { - Class.forName("exterminatorJeff.undergroundBiomes.worldGen.ChunkProviderWrapper"); - undergroundBiomesInstalled = true; - } catch (Exception ignored) { - } - if (!undergroundBiomesInstalled) { - FMLLog.log(Level.INFO, "Thermos: Patching " + asm.getActualName() + " for compatibility with Climate Control"); - extractFrom(asm, asm.method("extractFrom", - "(Lnet/minecraft/world/WorldServer;)Lnet/minecraft/world/chunk/IChunkProvider;").instructions()); + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (!TARGET_CLASS.equals(transformedName.replace('.', '/'))) { + return basicClass; + } + + if (isUndergroundBiomesPresent()) { + return basicClass; + } + + FMLLog.log(Level.INFO, + "Thermos: Patching ChunkGeneratorExtractor for Climate Control compatibility"); + + ClassNode cn = new ClassNode(); + ClassReader cr = new ClassReader(basicClass); + cr.accept(cn, 0); + + for (MethodNode mn : cn.methods) { + if (mn.name.equals("extractFrom") + && mn.desc.equals("(Lnet/minecraft/world/WorldServer;)Lnet/minecraft/world/chunk/IChunkProvider;")) { + + patchMethod(mn); + break; } } + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + cn.accept(cw); + return cw.toByteArray(); + } + + private void patchMethod(MethodNode mn) { + mn.instructions.clear(); + + boolean obf = isObfuscated(); + + String worldClass = obf ? "ahb" : "net/minecraft/world/World"; + String fieldName = obf ? "v" : "chunkProvider"; + String fieldDesc = obf ? "Lapu;" : + "Lnet/minecraft/world/chunk/IChunkProvider;"; + + InsnList insns = new InsnList(); + insns.add(new VarInsnNode(ALOAD, 1)); + insns.add(new FieldInsnNode(GETFIELD, worldClass, fieldName, fieldDesc)); + insns.add(new InsnNode(ARETURN)); + + mn.instructions.add(insns); + } + + private boolean isUndergroundBiomesPresent() { + try { + Class.forName("exterminatorJeff.undergroundBiomes.worldGen.ChunkProviderWrapper"); + return true; + } catch (Throwable ignored) { + return false; + } } - public void extractFrom(ImagineASM asm, InsnList list) { - //Pair fieldChunkProvider = asm.field("net/minecraft/world/World", "chunkProvider"); - list.clear(); - list.add(new IntInsnNode(ALOAD, 1)); - list.add(new FieldInsnNode(GETFIELD, "ahb", "v", "Lapu;")); - list.add(new InsnNode(ARETURN)); + private boolean isObfuscated() { + try { + Class.forName("net.minecraft.world.World"); + return false; + } catch (Throwable t) { + return true; + } } } From 9ddba601d016780b5aa5e32e4e290285df31e416 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sat, 21 Feb 2026 10:58:30 +0700 Subject: [PATCH 06/21] Remove unnessesary patches from EnumHelper for use without lwjgl3ify --- .../common/util/EnumHelper.java.patch | 459 ------------------ 1 file changed, 459 deletions(-) delete mode 100644 patches/net/minecraftforge/common/util/EnumHelper.java.patch diff --git a/patches/net/minecraftforge/common/util/EnumHelper.java.patch b/patches/net/minecraftforge/common/util/EnumHelper.java.patch deleted file mode 100644 index d724d7fa..00000000 --- a/patches/net/minecraftforge/common/util/EnumHelper.java.patch +++ /dev/null @@ -1,459 +0,0 @@ ---- ../src-base/minecraft/net/minecraftforge/common/util/EnumHelper.java -+++ ../src-work/minecraft/net/minecraftforge/common/util/EnumHelper.java -@@ -1,9 +1,11 @@ - package net.minecraftforge.common.util; - -+import java.lang.invoke.MethodHandle; -+import java.lang.invoke.MethodHandles; - import java.lang.reflect.*; - import java.util.*; - --import cpw.mods.fml.common.FMLLog; -+import com.google.common.base.Throwables; - import net.minecraft.block.BlockPressurePlate.Sensitivity; - import net.minecraft.block.material.Material; - import net.minecraft.enchantment.EnumEnchantmentType; -@@ -20,187 +22,110 @@ - import net.minecraft.util.MovingObjectPosition.MovingObjectType; - import net.minecraft.world.EnumSkyBlock; - import net.minecraft.world.gen.structure.StructureStrongholdPieces.Stronghold.Door; --import net.minecraftforge.classloading.FMLForgePlugin; -+// Cauldron start - --public class EnumHelper --{ -- private static Object reflectionFactory = null; -+import org.apache.commons.lang3.ArrayUtils; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+// Cauldron end -+ -+public class EnumHelper { -+ private static final Logger logger = LogManager.getLogger(); -+ private static final Map>, Map>> enumConstants = new HashMap<>(); -+ private static Object reflectionFactory = null; - private static Method newConstructorAccessor = null; -- private static Method newInstance = null; -- private static Method newFieldAccessor = null; -- private static Method fieldAccessorSet = null; -- private static boolean isSetup = false; -+ private static Method newInstance = null; -+ private static Method newFieldAccessor = null; -+ private static Method fieldAccessorSet = null; -+ private static boolean isSetup = false; - -- //Some enums are decompiled with extra arguments, so lets check for that -+ // Some enums are decompiled with extra arguments, so lets check for that - @SuppressWarnings("rawtypes") -- private static Class[][] commonTypes = -- { -- {EnumAction.class}, -- {ArmorMaterial.class, int.class, int[].class, int.class}, -- {EnumArt.class, String.class, int.class, int.class, int.class, int.class}, -- {EnumCreatureAttribute.class}, -- {EnumCreatureType.class, Class.class, int.class, Material.class, boolean.class, boolean.class}, -- {Door.class}, -- {EnumEnchantmentType.class}, -- {EnumEntitySize.class}, -- {Sensitivity.class}, -- {MovingObjectType.class}, -- {EnumSkyBlock.class, int.class}, -- {EnumStatus.class}, -- {ToolMaterial.class, int.class, int.class, float.class, float.class, int.class}, -- {EnumRarity.class, EnumChatFormatting.class, String.class} -- }; -+ private static Class[][] commonTypes = {{EnumAction.class}, -+ {ArmorMaterial.class, int.class, int[].class, int.class}, -+ {EnumArt.class, String.class, int.class, int.class, int.class, int.class}, {EnumCreatureAttribute.class}, -+ {EnumCreatureType.class, Class.class, int.class, Material.class, boolean.class, boolean.class}, -+ {Door.class}, {EnumEnchantmentType.class}, {EnumEntitySize.class}, {Sensitivity.class}, -+ {MovingObjectType.class}, {EnumSkyBlock.class, int.class}, {EnumStatus.class}, -+ {ToolMaterial.class, int.class, int.class, float.class, float.class, int.class}, -+ {EnumRarity.class, EnumChatFormatting.class, String.class}}; - -- public static EnumAction addAction(String name) -- { -+ public static EnumAction addAction(String name) { - return addEnum(EnumAction.class, name); - } -- public static ArmorMaterial addArmorMaterial(String name, int durability, int[] reductionAmounts, int enchantability) -- { -+ -+ public static ArmorMaterial addArmorMaterial(String name, int durability, int[] reductionAmounts, int enchantability) { - return addEnum(ArmorMaterial.class, name, durability, reductionAmounts, enchantability); - } -- public static EnumArt addArt(String name, String tile, int sizeX, int sizeY, int offsetX, int offsetY) -- { -+ -+ public static EnumArt addArt(String name, String tile, int sizeX, int sizeY, int offsetX, int offsetY) { - return addEnum(EnumArt.class, name, tile, sizeX, sizeY, offsetX, offsetY); - } -- public static EnumCreatureAttribute addCreatureAttribute(String name) -- { -+ -+ public static EnumCreatureAttribute addCreatureAttribute(String name) { - return addEnum(EnumCreatureAttribute.class, name); - } -+ - @SuppressWarnings("rawtypes") -- public static EnumCreatureType addCreatureType(String name, Class typeClass, int maxNumber, Material material, boolean peaceful, boolean animal) -- { -+ public static EnumCreatureType addCreatureType(String name, Class typeClass, int maxNumber, Material material, boolean peaceful, boolean animal) { - return addEnum(EnumCreatureType.class, name, typeClass, maxNumber, material, peaceful, animal); - } -- public static Door addDoor(String name) -- { -+ -+ public static Door addDoor(String name) { - return addEnum(Door.class, name); - } -- public static EnumEnchantmentType addEnchantmentType(String name) -- { -+ -+ public static EnumEnchantmentType addEnchantmentType(String name) { - return addEnum(EnumEnchantmentType.class, name); - } -- public static EnumEntitySize addEntitySize(String name) -- { -+ -+ public static EnumEntitySize addEntitySize(String name) { - return addEnum(EnumEntitySize.class, name); - } -- public static Sensitivity addSensitivity(String name) -- { -+ -+ public static Sensitivity addSensitivity(String name) { - return addEnum(Sensitivity.class, name); - } -- public static MovingObjectType addMovingObjectType(String name) -- { -+ -+ public static MovingObjectType addMovingObjectType(String name) { - return addEnum(MovingObjectType.class, name); - } -- public static EnumSkyBlock addSkyBlock(String name, int lightValue) -- { -+ -+ public static EnumSkyBlock addSkyBlock(String name, int lightValue) { - return addEnum(EnumSkyBlock.class, name, lightValue); - } -- public static EnumStatus addStatus(String name) -- { -+ -+ public static EnumStatus addStatus(String name) { - return addEnum(EnumStatus.class, name); - } -- public static ToolMaterial addToolMaterial(String name, int harvestLevel, int maxUses, float efficiency, float damage, int enchantability) -- { -+ -+ public static ToolMaterial addToolMaterial(String name, int harvestLevel, int maxUses, float efficiency, float damage, int enchantability) { - return addEnum(ToolMaterial.class, name, harvestLevel, maxUses, efficiency, damage, enchantability); - } -- public static EnumRarity addRarity(String name, EnumChatFormatting color, String displayName) -- { -+ -+ public static EnumRarity addRarity(String name, EnumChatFormatting color, String displayName) { - return addEnum(EnumRarity.class, name, color, displayName); - } - -- private static void setup() -- { -- if (isSetup) -- { -- return; -+ public static void setFailsafeFieldValue(Field field, Object target, Object value) throws Exception { -+ try { -+ setFieldHandle.invokeExact(field, target, value); -+ } catch (Throwable e) { -+ Throwables.propagate(e); - } -- -- try -- { -- Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory"); -- reflectionFactory = getReflectionFactory.invoke(null); -- newConstructorAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newConstructorAccessor", Constructor.class); -- newInstance = Class.forName("sun.reflect.ConstructorAccessor").getDeclaredMethod("newInstance", Object[].class); -- newFieldAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class); -- fieldAccessorSet = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class); -- } -- catch (Exception e) -- { -- e.printStackTrace(); -- } -- -- isSetup = true; - } - -- /* -- * Everything below this is found at the site below, and updated to be able to compile in Eclipse/Java 1.6+ -- * Also modified for use in decompiled code. -- * Found at: http://niceideas.ch/roller2/badtrash/entry/java_create_enum_instances_dynamically -- */ -- private static Object getConstructorAccessor(Class enumClass, Class[] additionalParameterTypes) throws Exception -- { -- Class[] parameterTypes = new Class[additionalParameterTypes.length + 2]; -- parameterTypes[0] = String.class; -- parameterTypes[1] = int.class; -- System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length); -- return newConstructorAccessor.invoke(reflectionFactory, enumClass.getDeclaredConstructor(parameterTypes)); -- } -- -- private static < T extends Enum> T makeEnum(Class enumClass, String value, int ordinal, Class[] additionalTypes, Object[] additionalValues) throws Exception -- { -- Object[] parms = new Object[additionalValues.length + 2]; -- parms[0] = value; -- parms[1] = Integer.valueOf(ordinal); -- System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length); -- return enumClass.cast(newInstance.invoke(getConstructorAccessor(enumClass, additionalTypes), new Object[] {parms})); -- } -- -- public static void setFailsafeFieldValue(Field field, Object target, Object value) throws Exception -- { -- field.setAccessible(true); -- Field modifiersField = Field.class.getDeclaredField("modifiers"); -- modifiersField.setAccessible(true); -- modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); -- Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false); -- fieldAccessorSet.invoke(fieldAccessor, target, value); -- } -- -- private static void blankField(Class enumClass, String fieldName) throws Exception -- { -- for (Field field : Class.class.getDeclaredFields()) -- { -- if (field.getName().contains(fieldName)) -- { -- field.setAccessible(true); -- setFailsafeFieldValue(field, enumClass, null); -- break; -- } -- } -- } -- -- private static void cleanEnumCache(Class enumClass) throws Exception -- { -- blankField(enumClass, "enumConstantDirectory"); -- blankField(enumClass, "enumConstants"); -- } -- -- public static > T addEnum(Class enumType, String enumName, Object... paramValues) -- { -- setup(); -+ public static > T addEnum(Class enumType, String enumName, Object... paramValues) { - return addEnum(commonTypes, enumType, enumName, paramValues); - } - - @SuppressWarnings("rawtypes") -- public static > T addEnum(Class[][] map, Class enumType, String enumName, Object... paramValues) -- { -- for (Class[] lookup : map) -- { -- if (lookup[0] == enumType) -- { -+ public static > T addEnum(Class[][] map, Class enumType, String enumName, -+ Object... paramValues) { -+ for (Class[] lookup : map) { -+ if (lookup[0] == enumType) { - Class[] paramTypes = new Class[lookup.length - 1]; -- if (paramTypes.length > 0) -- { -+ if (paramTypes.length > 0) { - System.arraycopy(lookup, 1, paramTypes, 0, paramTypes.length); - } - return addEnum(enumType, enumName, paramTypes, paramValues); -@@ -210,81 +135,130 @@ - } - - @SuppressWarnings("unchecked") -- public static > T addEnum(Class enumType, String enumName, Class[] paramTypes, Object[] paramValues) -- { -- if (!isSetup) -- { -- setup(); -+ public static > T addEnum(Class enumType, String enumName, Class[] paramTypes, -+ Object[] paramValues) { -+ if (!extensibleEnumIface.isAssignableFrom(enumType)) { -+ throw new RuntimeException( -+ "Enum " + enumType.getName() + " was not made extensible, add it to Crucible.yml"); - } - -- Field valuesField = null; -- Field[] fields = enumType.getDeclaredFields(); -+ synchronized (enumConstants) { -+ Map> enumMap = enumConstants.computeIfAbsent(enumType, k -> new HashMap<>()); -+ if (enumMap.containsKey(enumName.toUpperCase())) { -+ // Inside the addEnum method -+ int suffix = 1; -+ String newName; -+ do { -+ newName = enumName + "$" + suffix; -+ suffix++; -+ } while (enumMap.containsKey(newName.toUpperCase())); - -- for (Field field : fields) -- { -- String name = field.getName(); -- if (name.equals("$VALUES") || name.equals("ENUM$VALUES")) //Added 'ENUM$VALUES' because Eclipse's internal compiler doesn't follow standards -- { -- valuesField = field; -- break; -+ // Log Enum Name Change -+ logger.info("Duplicate Enum found! Remapping {} to {}", enumName, newName); -+ enumName = newName; - } -- } -+ try { -+ paramTypes = ArrayUtils.add(paramTypes, 0, String.class); -+ paramValues = ArrayUtils.add(paramValues, 0, enumName); - -- int flags = (FMLForgePlugin.RUNTIME_DEOBF ? Modifier.PUBLIC : Modifier.PRIVATE) | Modifier.STATIC | Modifier.FINAL | 0x1000 /*SYNTHETIC*/; -- if (valuesField == null) -- { -- String valueType = String.format("[L%s;", enumType.getName().replace('.', '/')); -+ final Method creatorHandle = enumType.getMethod("dynamicCreate", paramTypes); -+ T newValue = (T) creatorHandle.invoke(null, paramValues); - -- for (Field field : fields) -- { -- if ((field.getModifiers() & flags) == flags && -- field.getType().getName().replace('.', '/').equals(valueType)) //Apparently some JVMs return .'s and some don't.. -- { -- valuesField = field; -- break; -- } -+ // Add Enum Name / Class to Map -+ enumMap.put(enumName.toUpperCase(), newValue); -+ return newValue; -+ } catch (Exception e) { -+ throw new RuntimeException("Failed to add enum constant: " + enumName, e); - } - } -+ } - -- if (valuesField == null) -- { -- FMLLog.severe("Could not find $VALUES field for enum: %s", enumType.getName()); -- FMLLog.severe("Runtime Deobf: %s", FMLForgePlugin.RUNTIME_DEOBF); -- FMLLog.severe("Flags: %s", String.format("%16s", Integer.toBinaryString(flags)).replace(' ', '0')); -- FMLLog.severe("Fields:"); -- for (Field field : fields) -- { -- String mods = String.format("%16s", Integer.toBinaryString(field.getModifiers())).replace(' ', '0'); -- FMLLog.severe(" %s %s: %s", mods, field.getName(), field.getType().getName()); -- } -- return null; -- } -+ private static final Class extensibleEnumIface; -+ private static final MethodHandle setFieldHandle; - -- valuesField.setAccessible(true); -- -- try -- { -- T[] previousValues = (T[])valuesField.get(enumType); -- List values = new ArrayList(Arrays.asList(previousValues)); -- T newValue = (T)makeEnum(enumType, enumName, values.size(), paramTypes, paramValues); -- values.add(newValue); -- setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(enumType, 0))); -- cleanEnumCache(enumType); -- -- return newValue; -+ static { -+ try { -+ extensibleEnumIface = Class.forName("me.eigenraven.lwjgl3ify.IExtensibleEnum"); -+ Class unsafeHacks = Class.forName("me.eigenraven.lwjgl3ify.UnsafeHacks"); -+ Method setFieldM = unsafeHacks.getMethod("setField", Field.class, Object.class, Object.class); -+ setFieldHandle = MethodHandles.publicLookup() -+ .unreflect(setFieldM); -+ } catch (ReflectiveOperationException e) { -+ throw new RuntimeException(e); - } -- catch (Exception e) -- { -- e.printStackTrace(); -- throw new RuntimeException(e.getMessage(), e); -- } - } - -- static -- { -- if (!isSetup) -- { -- setup(); -- } -- } -+ // @SuppressWarnings("unchecked") -+// public static > T replaceEnum(Class enumType, String enumName, int ordinal, Class[] paramTypes, Object[] paramValues) -+// { -+// -+// Field valuesField = null; -+// Field[] fields = enumType.getDeclaredFields(); -+// -+// for (Field field : fields) -+// { -+// String name = field.getName(); -+// if (name.equals("$VALUES") || name.equals("ENUM$VALUES")) //Added 'ENUM$VALUES' because Eclipse's internal compiler doesn't follow standards -+// { -+// valuesField = field; -+// break; -+// } -+// } -+// -+// int flags = (FMLForgePlugin.RUNTIME_DEOBF ? Modifier.PUBLIC : Modifier.PRIVATE) | Modifier.STATIC | Modifier.FINAL | 0x1000 /*SYNTHETIC*/; -+// if (valuesField == null) -+// { -+// String valueType = String.format("[L%s;", enumType.getName().replace('.', '/')); -+// -+// for (Field field : fields) -+// { -+// if ((field.getModifiers() & flags) == flags && -+// field.getType().getName().replace('.', '/').equals(valueType)) //Apparently some JVMs return .'s and some don't.. -+// { -+// valuesField = field; -+// break; -+// } -+// } -+// } -+// -+// if (valuesField == null) -+// { -+// FMLLog.severe("Could not find $VALUES field for enum: %s", enumType.getName()); -+// FMLLog.severe("Runtime Deobf: %s", FMLForgePlugin.RUNTIME_DEOBF); -+// FMLLog.severe("Flags: %s", String.format("%16s", Integer.toBinaryString(flags)).replace(' ', '0')); -+// FMLLog.severe("Fields:"); -+// for (Field field : fields) -+// { -+// String mods = String.format("%16s", Integer.toBinaryString(field.getModifiers())).replace(' ', '0'); -+// FMLLog.severe(" %s %s: %s", mods, field.getName(), field.getType().getName()); -+// } -+// return null; -+// } -+// -+// valuesField.setAccessible(true); -+// try -+// { -+// Enum[] previousValues = (Enum[])(Enum[])valuesField.get(enumType); -+// Enum[] newValues = new Enum[previousValues.length]; -+// Enum newValue = null; -+// for (Enum enumValue : previousValues) -+// { -+// if (enumValue.ordinal() == ordinal) -+// { -+// newValue = makeEnum(enumType, enumName, ordinal, paramTypes, paramValues); -+// newValues[enumValue.ordinal()] = newValue; -+// } -+// else newValues[enumValue.ordinal()] = enumValue; -+// } -+// List values = new ArrayList(Arrays.asList(newValues)); -+// setFailsafeFieldValue(valuesField, null, values.toArray((Enum[])(Enum[])Array.newInstance(enumType, 0))); -+// return (T) newValue; -+// } -+// catch (Exception e) -+// { -+// e.printStackTrace(); -+// throw new RuntimeException(e.getMessage(), e); -+// } -+// } -+ // Cauldron end - } From f8d00df46afab2fc91be5978339941216d30fc25 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sat, 21 Feb 2026 11:02:37 +0700 Subject: [PATCH 07/21] Improve GitHub Actions code --- .github/workflows/staging-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/staging-build.yml b/.github/workflows/staging-build.yml index caa1686d..3fc2e796 100644 --- a/.github/workflows/staging-build.yml +++ b/.github/workflows/staging-build.yml @@ -4,6 +4,7 @@ on: push: branches: - staging + - all-patches permissions: contents: write @@ -54,7 +55,7 @@ jobs: with: tag_name: staging-${{ steps.short_sha.outputs.sha }} prerelease: true - name: Staging Build ${{ steps.short_sha.outputs.sha }} | dev-${{ steps.commit_count.outputs.count }} + name: RetroFOrge All-Patches Build ${{ steps.short_sha.outputs.sha }} | dev-${{ steps.commit_count.outputs.count }} body: | This is an automatic pre-release dev build. From 6a06ff27e30c33600c76c354e5821780d1c0eb90 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sat, 21 Feb 2026 11:08:59 +0700 Subject: [PATCH 08/21] Fix release message, add java9args.txt to artifacts --- .github/workflows/staging-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/staging-build.yml b/.github/workflows/staging-build.yml index 3fc2e796..6649e4a1 100644 --- a/.github/workflows/staging-build.yml +++ b/.github/workflows/staging-build.yml @@ -55,7 +55,7 @@ jobs: with: tag_name: staging-${{ steps.short_sha.outputs.sha }} prerelease: true - name: RetroFOrge All-Patches Build ${{ steps.short_sha.outputs.sha }} | dev-${{ steps.commit_count.outputs.count }} + name: RetroForge All-Patches Build ${{ steps.short_sha.outputs.sha }} | dev-${{ steps.commit_count.outputs.count }} body: | This is an automatic pre-release dev build. @@ -65,3 +65,4 @@ jobs: This is the most up-to-date build available. files: | build/distributions/* + java9args.txt From 47a6202076288bfa8d2a31c9de215c9e8c9bdfe1 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sun, 3 May 2026 07:34:24 +0700 Subject: [PATCH 09/21] Try add enums to lwjgl3ify early --- .../bootstrap/CrucibleCoremodHook.java | 4 + .../crucible/bootstrap/Lwjgl3ifyGlue.java | 20 +++-- .../bootstrap/Lwjgl3ifyIntegration.java | 82 +++++++++++++++++++ 3 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 src/main/java/io/github/crucible/bootstrap/Lwjgl3ifyIntegration.java diff --git a/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java b/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java index 85df6e8d..c80545c1 100644 --- a/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java +++ b/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java @@ -15,6 +15,10 @@ public static void coremodHandleLaunch(File mcDir, LaunchClassLoader classLoader } catch (ClassNotFoundException e) { throw new RuntimeException(e); } + + // Register extensible enums with lwjgl3ify if present + Lwjgl3ifyIntegration.registerExtensibleEnums(); + classLoader.registerTransformer("io.github.crucible.patches.RecurrentComplexTransformer"); classLoader.registerTransformer("io.github.crucible.patches.StreamsTransformer"); classLoader.registerTransformer("thermos.ThermosClassTransformer"); diff --git a/src/main/java/io/github/crucible/bootstrap/Lwjgl3ifyGlue.java b/src/main/java/io/github/crucible/bootstrap/Lwjgl3ifyGlue.java index ebc0af3c..4e24df6e 100644 --- a/src/main/java/io/github/crucible/bootstrap/Lwjgl3ifyGlue.java +++ b/src/main/java/io/github/crucible/bootstrap/Lwjgl3ifyGlue.java @@ -23,16 +23,23 @@ public class Lwjgl3ifyGlue { "net.minecraftforge.event.terraingen.InitMapGenEvent$EventType", "net.minecraftforge.event.terraingen.OreGenEvent$GenerateMinable$EventType", "net.minecraftforge.event.terraingen.DecorateBiomeEvent$Decorate$EventType", + "net.minecraftforge.common.BiomeDictionary$Type", // From GTNH crashes "vswe.stevesfactory.Localization", "vswe.stevesfactory.blocks.ClusterMethodRegistration", "vswe.stevesfactory.blocks.ConnectionBlockType", "vswe.stevesfactory.components.ComponentType", "vswe.stevesfactory.components.ConnectionSet", "vswe.stevesfactory.components.ConnectionOption", "ic2.core.init.InternalName", "gregtech.api.enums.Element", "gregtech.api.enums.OrePrefixes", - "net.minecraft.client.audio.MusicTicker$MusicType", - "buildcraft.api.transport.IPipeTile.PipeType", "thaumcraft.common.entities.golems.EnumGolemType", + "net.minecraft.client.audio.MusicTicker$MusicType", "org.bukkit.Material", + "buildcraft.api.transport.IPipeTile$PipeType", "thaumcraft.common.entities.golems.EnumGolemType", // Non-GTNH Mods Compat // The Lord of the Rings Mod: Legacy "net.minecraft.event.HoverEvent$Action", + // LotR Addons + "lotr.common.fac.LOTRFaction", "lotr.common.quest.LOTRMiniQuestFactory", "lotr.common.world.map.LOTRWaypoint", + "lotr.common.world.map.LOTRWaypoint$Region", "lotr.common.world.spawning.LOTRInvasions", + "lotr.common.LOTRShields", "lotr.common.LOTRDimension$DimensionRegion", + "lotr.common.item.LOTRItemBanner$BannerType", "lotr.common.LOTRAchievement$Category", + "lotr.client.gui.LOTRMapLabels", // Reika's mods "net.minecraft.client.audio.SoundCategory", "Reika.RotaryCraft.TileEntities.Processing.TileEntityFuelConverter$Conversions", @@ -42,18 +49,19 @@ public class Lwjgl3ifyGlue { "Reika.DragonAPI.ModInteract.Bees.BeeAlleleRegistry$Territory", "Reika.DragonAPI.ModInteract.Bees.BeeAlleleRegistry$Life", "Reika.DragonAPI.ModInteract.Bees.ButterflyAlleleRegistry$Fertility", - "Reika.DragonAPI.ModInteract.Bees.ButterflyAlleleRegistry$Life", + "Reika.DragonAPI.ModInteract.Bees.ButterflyAlleleRegistry$Life", "Reika.DragonAPI.ModRegistry.ModCropList", // Et Futurum Requiem "net.minecraft.world.WorldSettings$GameType", - // Minechem - "net.minecraftforge.common.BiomeDictionary$Type", + "ganymedes01.etfuturum.tileentities.TileEntityBanner$EnumBannerPattern", + // EnderIO Addons + "crazypants.enderio.ModObject", // Bukkit Enums - "org.bukkit.Material", "org.bukkit.block.Biome", "org.bukkit.World$Environment", "org.bukkit.WorldType", "org.bukkit.entity.EntityType", "org.bukkit.event.inventory.InventoryType", + // }; public static void checkJava() { diff --git a/src/main/java/io/github/crucible/bootstrap/Lwjgl3ifyIntegration.java b/src/main/java/io/github/crucible/bootstrap/Lwjgl3ifyIntegration.java new file mode 100644 index 00000000..270e7d31 --- /dev/null +++ b/src/main/java/io/github/crucible/bootstrap/Lwjgl3ifyIntegration.java @@ -0,0 +1,82 @@ +package io.github.crucible.bootstrap; + +import io.github.crucible.CrucibleConfigs; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Set; + +/** + * Integration with lwjgl3ify mod when present as optional dependency. + * This class uses reflection to avoid hard dependency on lwjgl3ify. + * + * IMPORTANT: This must be called BEFORE any enum classes are loaded, as the + * ExtensibleEnumTransformer checks EarlyConfig.EXTENSIBLE_ENUMS during class transformation. + */ +public class Lwjgl3ifyIntegration { + + private static boolean initialized = false; + private static boolean lwjgl3ifyPresent = false; + + /** + * Attempts to register extensible enums with lwjgl3ify if it's present. + * This method MUST be called early in the initialization process, before any enum classes are loaded. + * + * This directly adds enums to EarlyConfig.EXTENSIBLE_ENUMS set before the transformer runs. + */ + public static void registerExtensibleEnums() { + if (initialized) { + return; + } + initialized = true; + + try { + // Try to load lwjgl3ify's EarlyConfig class + // Note: EarlyConfig is excluded from LaunchClassLoader, so it loads in the system classloader + Class earlyConfigClass = Class.forName("me.eigenraven.lwjgl3ify.rfb.EarlyConfig"); + lwjgl3ifyPresent = true; + + System.out.println("[Crucible] lwjgl3ify detected, registering extensible enums..."); + + // Get the list of extensible enums from Crucible config + List extensibleEnums = CrucibleConfigs.configs.lwjgl3ify_extensibleEnums; + + if (extensibleEnums == null || extensibleEnums.isEmpty()) { + System.out.println("[Crucible] No extensible enums configured, skipping registration."); + return; + } + + // Get the EXTENSIBLE_ENUMS field directly + Field extensibleEnumsField = earlyConfigClass.getDeclaredField("EXTENSIBLE_ENUMS"); + extensibleEnumsField.setAccessible(true); + + @SuppressWarnings("unchecked") + Set lwjgl3ifyEnums = (Set) extensibleEnumsField.get(null); + + // Add all Crucible enums to lwjgl3ify's set + int registered = 0; + for (String enumClass : extensibleEnums) { + if (lwjgl3ifyEnums.add(enumClass)) { + registered++; + } + } + + System.out.println("[Crucible] Successfully registered " + registered + " extensible enums with lwjgl3ify."); + System.out.println("[Crucible] Total extensible enums: " + lwjgl3ifyEnums.size()); + + } catch (ClassNotFoundException e) { + // lwjgl3ify is not present, this is fine + System.out.println("[Crucible] lwjgl3ify not detected, extensible enum registration skipped."); + } catch (Exception e) { + System.err.println("[Crucible] Error while registering extensible enums with lwjgl3ify:"); + e.printStackTrace(); + } + } + + /** + * @return true if lwjgl3ify is present and initialized + */ + public static boolean isLwjgl3ifyPresent() { + return lwjgl3ifyPresent; + } +} From 88c1df1ff40c5565fdfc310a34b47aa0b5db7447 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Mon, 4 May 2026 07:33:49 +0700 Subject: [PATCH 10/21] Upgrade RFB and ASM --- build.gradle | 14 +++++++------- .../fml/relauncher/ServerLaunchWrapper.java.patch | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 50e54003..d0851a8d 100644 --- a/build.gradle +++ b/build.gradle @@ -86,17 +86,17 @@ configurations { dependencies { // New java stuff - libraries('com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.10') { + libraries('com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.16') { exclude group: 'org.apache.logging.log4j' } - libraries 'com.google.code.gson:gson:2.13.2' + libraries 'com.google.code.gson:gson:2.14.0' libraries 'org.apache.commons:commons-lang3:3.17.0' libraries 'org.apache.commons:commons-compress:1.27.1' - libraries 'org.ow2.asm:asm:9.8' - libraries 'org.ow2.asm:asm-commons:9.8' - libraries 'org.ow2.asm:asm-tree:9.8' - libraries 'org.ow2.asm:asm-analysis:9.8' - libraries 'org.ow2.asm:asm-util:9.8' + libraries 'org.ow2.asm:asm:9.9.1' + libraries 'org.ow2.asm:asm-commons:9.9.1' + libraries 'org.ow2.asm:asm-tree:9.9.1' + libraries 'org.ow2.asm:asm-analysis:9.9.1' + libraries 'org.ow2.asm:asm-util:9.9.1' libraries 'org.ow2.asm:asm-deprecated:7.1' libraries 'jakarta.xml.bind:jakarta.xml.bind-api:3.0.1' libraries "javax.servlet:javax.servlet-api:4.0.1" diff --git a/patches/cpw/mods/fml/relauncher/ServerLaunchWrapper.java.patch b/patches/cpw/mods/fml/relauncher/ServerLaunchWrapper.java.patch index ad258390..96a78066 100644 --- a/patches/cpw/mods/fml/relauncher/ServerLaunchWrapper.java.patch +++ b/patches/cpw/mods/fml/relauncher/ServerLaunchWrapper.java.patch @@ -24,7 +24,7 @@ try { - launchwrapper = Class.forName("net.minecraft.launchwrapper.Launch",true,getClass().getClassLoader()); -+ launchwrapper = Class.forName("com.gtnewhorizons.retrofuturabootstrap.Main",true,getClass().getClassLoader()); ++ launchwrapper = Class.forName("com.gtnewhorizons.retrofuturabootstrap.MainStartOnFirstThread",true,getClass().getClassLoader()); Class.forName("org.objectweb.asm.Type",true,getClass().getClassLoader()); } catch (Exception e) From c629c5e51a052c83e401a5cadab4da78ca8c03c4 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sat, 9 May 2026 13:28:39 +0700 Subject: [PATCH 11/21] Fix build --- .../forgegradle/tasks/DeterministicDecompileTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/tasks/DeterministicDecompileTask.java b/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/tasks/DeterministicDecompileTask.java index 31c105bc..1c8fbf08 100644 --- a/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/tasks/DeterministicDecompileTask.java +++ b/buildSrc/src/main/java/io/github/cruciblemc/forgegradle/tasks/DeterministicDecompileTask.java @@ -157,7 +157,7 @@ private void crucibleFixJar(File patchFiles, File jar) throws Throwable { } private void decompile(final File inJar, final File outJar, final File fernFlower) { - this.getProject().getProviders().javaexec(new Action<>() { + this.getProject().javaexec(new Action() { private static final long serialVersionUID = 4608694547855396167L; @Override From 11676379d4162879490c55644cfc72f27ba2490c Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Sat, 9 May 2026 13:32:02 +0700 Subject: [PATCH 12/21] Make workable with Java 8 without lwjgl3ify --- .../registry/ItemStackHolderRef.java.patch | 124 --------------- .../registry/ObjectHolderRef.java.patch | 150 ------------------ 2 files changed, 274 deletions(-) delete mode 100644 patches/cpw/mods/fml/common/registry/ItemStackHolderRef.java.patch delete mode 100644 patches/cpw/mods/fml/common/registry/ObjectHolderRef.java.patch diff --git a/patches/cpw/mods/fml/common/registry/ItemStackHolderRef.java.patch b/patches/cpw/mods/fml/common/registry/ItemStackHolderRef.java.patch deleted file mode 100644 index e8e25e7d..00000000 --- a/patches/cpw/mods/fml/common/registry/ItemStackHolderRef.java.patch +++ /dev/null @@ -1,124 +0,0 @@ ---- ../src-base/minecraft/cpw/mods/fml/common/registry/ItemStackHolderRef.java -+++ ../src-work/minecraft/cpw/mods/fml/common/registry/ItemStackHolderRef.java -@@ -1,19 +1,15 @@ - package cpw.mods.fml.common.registry; - --import java.lang.reflect.Field; --import java.lang.reflect.Method; --import java.lang.reflect.Modifier; -- --import net.minecraft.item.ItemStack; -- --import org.apache.logging.log4j.Level; -- - import com.google.common.base.Throwables; -- - import cpw.mods.fml.common.FMLLog; - import cpw.mods.fml.common.registry.GameRegistry.ItemStackHolder; -+import net.minecraft.item.ItemStack; -+import org.apache.logging.log4j.Level; - -+import java.lang.reflect.Field; -+import java.lang.reflect.Method; - -+ - /** - * Internal class used in tracking {@link ItemStackHolder} references - * -@@ -44,16 +40,19 @@ - { - try - { -- if (modifiersField == null) -- { -- Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory"); -- reflectionFactory = getReflectionFactory.invoke(null); -- newFieldAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class); -- fieldAccessorSet = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class); -- modifiersField = Field.class.getDeclaredField("modifiers"); -- modifiersField.setAccessible(true); -- } -- modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); -+ // Crucible start - implement lwjgl3ify patch directly -+// if (modifiersField == null) -+// { -+// Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory"); -+// reflectionFactory = getReflectionFactory.invoke(null); -+// newFieldAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class); -+// fieldAccessorSet = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class); -+// modifiersField = Field.class.getDeclaredField("modifiers"); -+// modifiersField.setAccessible(true); -+// } -+// modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); -+ f.setAccessible(true); -+ // Crucible end - } catch (Exception e) - { - throw Throwables.propagate(e); -@@ -62,23 +61,53 @@ - - public void apply() - { -+ // Crucible start - implement lwjgl3ify patch directly -+// ItemStack is; -+// try -+// { -+// is = GameRegistry.makeItemStack(itemName, meta, 1, serializednbt); -+// } catch (RuntimeException e) -+// { -+// FMLLog.getLogger().log(Level.ERROR, "Caught exception processing itemstack {},{},{} in annotation at {}.{}", itemName, meta, serializednbt,field.getClass().getName(),field.getName()); -+// throw e; -+// } -+// try -+// { -+// Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false); -+// fieldAccessorSet.invoke(fieldAccessor, null, is); -+// } -+// catch (Exception e) -+// { -+// FMLLog.getLogger().log(Level.WARN, "Unable to set {} with value {},{},{}", this.field, this.itemName, this.meta, this.serializednbt); -+// } - ItemStack is; -- try -- { -+ try { - is = GameRegistry.makeItemStack(itemName, meta, 1, serializednbt); -- } catch (RuntimeException e) -- { -- FMLLog.getLogger().log(Level.ERROR, "Caught exception processing itemstack {},{},{} in annotation at {}.{}", itemName, meta, serializednbt,field.getClass().getName(),field.getName()); -+ } catch (RuntimeException e) { -+ FMLLog.getLogger() -+ .log( -+ Level.ERROR, -+ "Caught exception processing itemstack {},{},{} in annotation at {}.{}", -+ itemName, -+ meta, -+ serializednbt, -+ field.getClass() -+ .getName(), -+ field.getName()); - throw e; - } -- try -- { -- Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false); -- fieldAccessorSet.invoke(fieldAccessor, null, is); -+ try { -+ field.set(null, is); -+ } catch (Throwable e) { -+ FMLLog.getLogger() -+ .log( -+ Level.WARN, -+ "Unable to set {} with value {},{},{}", -+ this.field, -+ this.itemName, -+ this.meta, -+ this.serializednbt); - } -- catch (Exception e) -- { -- FMLLog.getLogger().log(Level.WARN, "Unable to set {} with value {},{},{}", this.field, this.itemName, this.meta, this.serializednbt); -- } -+ // Crucible end - } - } diff --git a/patches/cpw/mods/fml/common/registry/ObjectHolderRef.java.patch b/patches/cpw/mods/fml/common/registry/ObjectHolderRef.java.patch deleted file mode 100644 index a4907437..00000000 --- a/patches/cpw/mods/fml/common/registry/ObjectHolderRef.java.patch +++ /dev/null @@ -1,150 +0,0 @@ ---- ../src-base/minecraft/cpw/mods/fml/common/registry/ObjectHolderRef.java -+++ ../src-work/minecraft/cpw/mods/fml/common/registry/ObjectHolderRef.java -@@ -1,17 +1,18 @@ - package cpw.mods.fml.common.registry; - --import java.lang.reflect.Field; --import java.lang.reflect.Method; --import java.lang.reflect.Modifier; --import org.apache.logging.log4j.Level; - import com.google.common.base.Throwables; - import cpw.mods.fml.common.FMLLog; - import cpw.mods.fml.common.registry.GameRegistry.ObjectHolder; - import net.minecraft.block.Block; - import net.minecraft.init.Blocks; - import net.minecraft.item.Item; -+import net.minecraft.util.RegistryNamespaced; -+import org.apache.logging.log4j.Level; - -+import java.lang.reflect.Field; -+import java.lang.reflect.Method; - -+ - /** - * Internal class used in tracking {@link ObjectHolder} references - * -@@ -74,16 +75,19 @@ - { - try - { -- if (modifiersField == null) -- { -- Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory"); -- reflectionFactory = getReflectionFactory.invoke(null); -- newFieldAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class); -- fieldAccessorSet = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class); -- modifiersField = Field.class.getDeclaredField("modifiers"); -- modifiersField.setAccessible(true); -- } -- modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); -+ // Crucible start - implement lwjgl3ify patch directly -+// if (modifiersField == null) -+// { -+// Method getReflectionFactory = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("getReflectionFactory"); -+// reflectionFactory = getReflectionFactory.invoke(null); -+// newFieldAccessor = Class.forName("sun.reflect.ReflectionFactory").getDeclaredMethod("newFieldAccessor", Field.class, boolean.class); -+// fieldAccessorSet = Class.forName("sun.reflect.FieldAccessor").getDeclaredMethod("set", Object.class, Object.class); -+// modifiersField = Field.class.getDeclaredField("modifiers"); -+// modifiersField.setAccessible(true); -+// } -+// modifiersField.setInt(f, f.getModifiers() & ~Modifier.FINAL); -+ f.setAccessible(true); -+ // Crucible end - } catch (Exception e) - { - throw Throwables.propagate(e); -@@ -94,39 +98,72 @@ - { - return isBlock || isItem; - } -+ - public void apply() - { -+ // Crucible start - implement lwjgl3ify patch directly -+// Object thing; -+// if (isBlock) -+// { -+// thing = GameData.getBlockRegistry().getObject(injectedObject); -+// if (thing == Blocks.air) -+// { -+// thing = null; -+// } -+// } -+// else if (isItem) -+// { -+// thing = GameData.getItemRegistry().getObject(injectedObject); -+// } -+// else -+// { -+// thing = null; -+// } -+// -+// if (thing == null) -+// { -+// FMLLog.getLogger().log(Level.DEBUG, "Unable to lookup {} for {}. This means the object wasn't registered. It's likely just mod options.", injectedObject, field); -+// return; -+// } -+// try -+// { -+// Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false); -+// fieldAccessorSet.invoke(fieldAccessor, null, thing); -+// } -+// catch (Exception e) -+// { -+// FMLLog.log(Level.WARN, e, "Unable to set %s with value %s (%s)", this.field, thing, this.injectedObject); -+// } - Object thing; -- if (isBlock) -- { -- thing = GameData.getBlockRegistry().getObject(injectedObject); -- if (thing == Blocks.air) -- { -+ RegistryNamespaced registry; -+ if (isBlock) { -+ registry = GameData.getBlockRegistry(); -+ thing = registry.getObject(injectedObject); -+ if (thing == Blocks.air) { - thing = null; - } -- } -- else if (isItem) -- { -- thing = GameData.getItemRegistry().getObject(injectedObject); -- } -- else -- { -+ } else if (isItem) { -+ registry = GameData.getItemRegistry(); -+ thing = registry.getObject(injectedObject); -+ } else { - thing = null; - } - -- if (thing == null) -- { -- FMLLog.getLogger().log(Level.DEBUG, "Unable to lookup {} for {}. This means the object wasn't registered. It's likely just mod options.", injectedObject, field); -+ if (thing == null) { -+ FMLLog.getLogger() -+ .log( -+ Level.DEBUG, -+ "Unable to lookup {} for {}. This means the object wasn't registered. It's likely just mod options.", -+ injectedObject, -+ field); - return; - } -- try -- { -- Object fieldAccessor = newFieldAccessor.invoke(reflectionFactory, field, false); -- fieldAccessorSet.invoke(fieldAccessor, null, thing); -- } -- catch (Exception e) -- { -+ try { -+ field.set(null, thing); -+ FMLLog.finer("Set field " + field.toString() + " to " + thing); -+ } catch (Throwable e) { - FMLLog.log(Level.WARN, e, "Unable to set %s with value %s (%s)", this.field, thing, this.injectedObject); - } -+ // Crucible end - } - } From ca242bb905f9f106f4127843e3910b2af571cf4c Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Tue, 9 Jun 2026 04:50:03 +0700 Subject: [PATCH 13/21] Fix manifest attributes --- build.gradle.kts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 088d6155..d667e92f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.bundling.Zip import org.gradle.api.publish.maven.MavenPublication +import io.github.cruciblemc.forgegradle.tasks.DelayedJar import java.io.ByteArrayOutputStream plugins { @@ -179,6 +180,34 @@ tasks.named("jar").configure { } } +tasks.named("packageServer").configure { + doFirst { + manifest { + attributes( + mapOf( + "Thermos-Git-Branch" to gitInfo("branch"), + "Thermos-Git-Hash" to gitInfo("fullHash"), + "Thermos-Group" to project.group, + "Thermos-Channel" to project.name, + "Thermos-Version" to project.version, + "Thermos-Legacy" to true, + "Implementation-Vendor" to "CrucibleMC Team", + "Implementation-Title" to project.name, + "Implementation-Version" to project.version, + "Specification-Vendor" to "Bukkit Team", + "Specification-Title" to "Bukkit", + "Specification-Version" to "1.7.10-R0.1-SNAPSHOT", + "Forge-Version" to "10.13.4.1614", + "TweakClass" to "cpw.mods.fml.common.launcher.FMLTweaker", + "Main-Class" to "cpw.mods.fml.relauncher.ServerLaunchWrapper", + "Class-Path" to generateClasspath(), + "Crucible-Libs" to generateMavenLibs() + ) + ) + } + } +} + tasks.register("packageLibraries") { archiveFileName.set("libraries.zip") destinationDirectory.set(layout.buildDirectory.dir("distributions")) From fb8f3ee3539ed6f2d491b019b40e4e00f1ab856b Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Tue, 9 Jun 2026 05:12:22 +0700 Subject: [PATCH 14/21] Fix build scripts --- build.gradle.kts | 20 +++++++++---------- .../crucible/bootstrap/LibraryManager.java | 9 +++++++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index d667e92f..0282fa24 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -90,16 +90,17 @@ val libraries by configurations.creating { configurations["implementation"].extendsFrom(libraries) dependencies { - "libraries"("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.11") { + "libraries"("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.1.0") { exclude(group = "org.apache.logging.log4j") } - "libraries"("org.apache.commons:commons-lang3:3.12.0") - "libraries"("org.apache.commons:commons-compress:1.21") - "libraries"("org.ow2.asm:asm:9.8") - "libraries"("org.ow2.asm:asm-commons:9.8") - "libraries"("org.ow2.asm:asm-tree:9.8") - "libraries"("org.ow2.asm:asm-analysis:9.8") - "libraries"("org.ow2.asm:asm-util:9.8") + "libraries"("com.google.code.gson:gson:2.14.0") + "libraries"("org.apache.commons:commons-lang3:3.17.0") + "libraries"("org.apache.commons:commons-compress:1.27.1") + "libraries"("org.ow2.asm:asm:9.9.1") + "libraries"("org.ow2.asm:asm-commons:9.9.1") + "libraries"("org.ow2.asm:asm-tree:9.9.1") + "libraries"("org.ow2.asm:asm-analysis:9.9.1") + "libraries"("org.ow2.asm:asm-util:9.9.1") "libraries"("org.ow2.asm:asm-deprecated:7.1") "libraries"("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1") "libraries"("javax.servlet:javax.servlet-api:4.0.1") @@ -111,7 +112,7 @@ dependencies { "libraries"("commons-cli:commons-cli:1.3@jar") "libraries"("org.slf4j:slf4j-simple:1.6.2@jar") "libraries"("org.eclipse.jetty:jetty-servlet:9.0.3.v20130506@jar") - "libraries"("commons-io:commons-io:2.4@jar") + "libraries"("commons-io:commons-io:2.18.0") "libraries"("net.sf.opencsv:opencsv:2.0@jar") "libraries"("com.beust:jcommander:1.30@jar") "libraries"("io.github.cruciblemc:launchwrapper:1.13@jar") @@ -145,7 +146,6 @@ dependencies { "libraries"("com.koloboke:koloboke-impl-jdk8:1.0.0") "libraries"("java3d:vecmath:1.3.1") "libraries"("net.minecraft:server:1.7.10") - "libraries"(project(":eclipse:cauldron")) } tasks.register("packageJavadoc") { diff --git a/src/main/java/io/github/crucible/bootstrap/LibraryManager.java b/src/main/java/io/github/crucible/bootstrap/LibraryManager.java index 3458b525..d70406eb 100644 --- a/src/main/java/io/github/crucible/bootstrap/LibraryManager.java +++ b/src/main/java/io/github/crucible/bootstrap/LibraryManager.java @@ -22,7 +22,8 @@ import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; @@ -38,7 +39,7 @@ public static void downloadMavenLibraries(Path baseDir, String[] repos, String.. reposUri[i] = URI.create(repos[i]); } - ForkJoinPool pool = new ForkJoinPool((Runtime.getRuntime().availableProcessors() * 2) + 1); + ExecutorService pool = Executors.newFixedThreadPool(Math.min(libraries.length, 16)); List> tasks = new ArrayList<>(libraries.length); for (String library : libraries) { tasks.add(makeMavenDownloadTask(baseDir, reposUri, library)); @@ -59,6 +60,8 @@ public static void downloadMavenLibraries(Path baseDir, String[] repos, String.. } } + pool.shutdown(); + if (failed) { throw new RuntimeException("Failed to download one or more essential files for the server, check your logs for more information"); } @@ -126,6 +129,8 @@ public Boolean call() throws Exception { URI uriPath = repo.resolve(jarRelativeName).normalize(); file = uriPath.toURL(); HttpURLConnection connection = (HttpURLConnection) file.openConnection(); + connection.setConnectTimeout(10000); + connection.setReadTimeout(10000); connection.setRequestMethod("GET"); connection.connect(); if (connection.getResponseCode() >= 200 || connection.getResponseCode() <= 399) { From 48e59e834295e279d019852cf4a0c3ed9f4df941 Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Fri, 12 Jun 2026 02:36:20 +0700 Subject: [PATCH 15/21] Fix EndlessIDs compatibility --- src/main/java/thermos/Thermos.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/thermos/Thermos.java diff --git a/src/main/java/thermos/Thermos.java b/src/main/java/thermos/Thermos.java new file mode 100644 index 00000000..ccd5dfcc --- /dev/null +++ b/src/main/java/thermos/Thermos.java @@ -0,0 +1,8 @@ +package thermos; + +// EndlessID check this class for thermos compatibility +// https://github.com/GTMEGA/EndlessIDs/blob/a780cde306e2d049d52eca07c444d95ce87f8399/src/main/java/com/falsepattern/endlessids/mixin/plugin/Mixin.java#L629 +public class Thermos { + private Thermos() { + } +} From 93f8bcc1080fbc6b5c4790db7c1d7d3963982a1a Mon Sep 17 00:00:00 2001 From: Gravita <12893402+gravit0@users.noreply.github.com> Date: Fri, 12 Jun 2026 03:46:01 +0700 Subject: [PATCH 16/21] Make lwjgl3ify extensible enum integration with rfb-plugin --- .../crucible/bootstrap/CrucibleCoremodHook.java | 3 --- .../crucible/bootstrap/rfb/CrucibleRfbPlugin.java | 13 +++++++++++++ .../META-INF/rfb-plugin/crucible.properties | 9 +++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 src/main/java/io/github/crucible/bootstrap/rfb/CrucibleRfbPlugin.java create mode 100644 src/main/resources/META-INF/rfb-plugin/crucible.properties diff --git a/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java b/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java index c80545c1..55827275 100644 --- a/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java +++ b/src/main/java/io/github/crucible/bootstrap/CrucibleCoremodHook.java @@ -16,9 +16,6 @@ public static void coremodHandleLaunch(File mcDir, LaunchClassLoader classLoader throw new RuntimeException(e); } - // Register extensible enums with lwjgl3ify if present - Lwjgl3ifyIntegration.registerExtensibleEnums(); - classLoader.registerTransformer("io.github.crucible.patches.RecurrentComplexTransformer"); classLoader.registerTransformer("io.github.crucible.patches.StreamsTransformer"); classLoader.registerTransformer("thermos.ThermosClassTransformer"); diff --git a/src/main/java/io/github/crucible/bootstrap/rfb/CrucibleRfbPlugin.java b/src/main/java/io/github/crucible/bootstrap/rfb/CrucibleRfbPlugin.java new file mode 100644 index 00000000..1976dc89 --- /dev/null +++ b/src/main/java/io/github/crucible/bootstrap/rfb/CrucibleRfbPlugin.java @@ -0,0 +1,13 @@ +package io.github.crucible.bootstrap.rfb; + +import com.gtnewhorizons.retrofuturabootstrap.api.PluginContext; +import com.gtnewhorizons.retrofuturabootstrap.api.RfbPlugin; +import io.github.crucible.bootstrap.Lwjgl3ifyIntegration; +import org.jetbrains.annotations.NotNull; + +public class CrucibleRfbPlugin implements RfbPlugin { + @Override + public void onConstruction(@NotNull PluginContext ctx) { + Lwjgl3ifyIntegration.registerExtensibleEnums(); + } +} diff --git a/src/main/resources/META-INF/rfb-plugin/crucible.properties b/src/main/resources/META-INF/rfb-plugin/crucible.properties new file mode 100644 index 00000000..42c4a3ec --- /dev/null +++ b/src/main/resources/META-INF/rfb-plugin/crucible.properties @@ -0,0 +1,9 @@ +name=Crucible +version=0.0.1 +additionalVersions= +className=io.github.crucible.bootstrap.rfb.CrucibleRfbPlugin +transformerExclusions=io.github.crucible.bootstrap.rfb +versionConstraints= +loadBefore= +loadAfter= +loadRequires= From f38e67abd037cb29f773327f0ab51088c1843ff7 Mon Sep 17 00:00:00 2001 From: Petrus Pradella Date: Sun, 21 Jun 2026 02:56:17 -0300 Subject: [PATCH 17/21] Revert 'all-patches' from staging-build.yml but keep java9args.txt --- .github/workflows/staging-build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/staging-build.yml b/.github/workflows/staging-build.yml index 6649e4a1..a0acbe99 100644 --- a/.github/workflows/staging-build.yml +++ b/.github/workflows/staging-build.yml @@ -4,7 +4,6 @@ on: push: branches: - staging - - all-patches permissions: contents: write @@ -55,7 +54,7 @@ jobs: with: tag_name: staging-${{ steps.short_sha.outputs.sha }} prerelease: true - name: RetroForge All-Patches Build ${{ steps.short_sha.outputs.sha }} | dev-${{ steps.commit_count.outputs.count }} + name: Staging Build ${{ steps.short_sha.outputs.sha }} | dev-${{ steps.commit_count.outputs.count }} body: | This is an automatic pre-release dev build. From b8915d8ce4e79b94c1c08b177d2450f64fe19edc Mon Sep 17 00:00:00 2001 From: Petrus Pradella Date: Sun, 21 Jun 2026 03:47:37 -0300 Subject: [PATCH 18/21] Few updates that were not matching RetroForge fork --- buildSrc/build.gradle.kts | 6 +- gradle/wrapper/gradle-wrapper.properties | 4 +- .../common/discovery/JarDiscoverer.java.patch | 2 +- .../network/NetHandlerPlayServer.java.patch | 2 +- .../dedicated/DedicatedServer.java.patch | 66 +---- .../org/bukkit/plugin/PluginLogger.java.patch | 49 ++-- .../plugin/java/PluginClassLoader.java.patch | 195 +++++++++----- .../io/github/crucible/JulLogManager.java | 247 ++++++++++++++++++ .../crucible/api/CruciblePluginTransform.java | 34 +++ .../util/TerminalConsoleWriterThread.java | 2 +- src/main/resources/log4j2.xml | 4 +- 11 files changed, 462 insertions(+), 149 deletions(-) create mode 100644 src/main/java/io/github/crucible/JulLogManager.java create mode 100644 src/main/java/io/github/crucible/api/CruciblePluginTransform.java diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 8f3c8af6..5305ac66 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -16,8 +16,8 @@ repositories { dependencies { // TODO? figure a way to use the runtime dependencies of FG? - implementation("org.ow2.asm:asm:9.6") - implementation("org.ow2.asm:asm-tree:9.6") + implementation("org.ow2.asm:asm:9.8") + implementation("org.ow2.asm:asm-tree:9.8") implementation("com.google.guava:guava:31.1-jre") implementation("com.opencsv:opencsv:5.7.0") implementation("com.cloudbees:diff4j:1.3") @@ -33,7 +33,7 @@ dependencies { implementation("de.oceanlabs.mcp:mcinjector:3.2-SNAPSHOT") implementation("net.minecraftforge:Srg2Source:4.2.7") - api("com.anatawa12.forge:ForgeGradle:1.2-1.1.0") + api("com.anatawa12.forge:ForgeGradle:1.2-1.1.1") testImplementation(platform("org.junit:junit-bom:5.9.1")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index da1db5f0..d4081da4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/patches/cpw/mods/fml/common/discovery/JarDiscoverer.java.patch b/patches/cpw/mods/fml/common/discovery/JarDiscoverer.java.patch index cf0ba2ae..b20b03d7 100644 --- a/patches/cpw/mods/fml/common/discovery/JarDiscoverer.java.patch +++ b/patches/cpw/mods/fml/common/discovery/JarDiscoverer.java.patch @@ -5,7 +5,7 @@ for (ZipEntry ze : Collections.list(jar.entries())) { - if (ze.getName()!=null && ze.getName().startsWith("__MACOSX")) -+ if (ze.getName().startsWith("__MACOSX") || ze.getName().startsWith("META-INF/versions") || ze.getName().contains("org/openjdk/nashorn") || ze.getName().contains("jakarta/servlet/")) ++ if (ze.getName().startsWith("__MACOSX") || ze.getName().contains("module-info.class") || ze.getName().startsWith("META-INF/versions") || ze.getName().contains("org/openjdk/nashorn") || ze.getName().contains("jakarta/servlet/")) { continue; } diff --git a/patches/net/minecraft/network/NetHandlerPlayServer.java.patch b/patches/net/minecraft/network/NetHandlerPlayServer.java.patch index bafcdcd8..3422e49f 100644 --- a/patches/net/minecraft/network/NetHandlerPlayServer.java.patch +++ b/patches/net/minecraft/network/NetHandlerPlayServer.java.patch @@ -976,7 +976,7 @@ + // Spigot Start + if (org.spigotmc.SpigotConfig.logCommands) + { -+ this.logger.info(event.getPlayer().getName() + " issued server command: " + event.getMessage()); // CraftBukkit ++ this.logger.info("{} issued server command: {}", event.getPlayer().getName(), event.getMessage()); // CraftBukkit + } + + // Spigot end diff --git a/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch b/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch index fb00694a..9bc44167 100644 --- a/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch +++ b/patches/net/minecraft/server/dedicated/DedicatedServer.java.patch @@ -77,7 +77,7 @@ { private static final String __OBFID = "CL_00001787"; { -@@ -82,31 +100,102 @@ +@@ -82,31 +100,54 @@ }; } @@ -111,7 +111,7 @@ - DedicatedServer.this.addPendingCommand(s4, DedicatedServer.this); + if (useJline) + { -+ s = bufferedreader.readLine(">", null); ++ s = bufferedreader.readLine("", null); + } + else + { @@ -135,59 +135,11 @@ }; thread.setDaemon(true); thread.start(); -+ // CraftBukkit start - TODO: handle command-line logging arguments -+ java.util.logging.Logger global = java.util.logging.Logger.getLogger(""); -+ global.setUseParentHandlers(false); -+ -+ for (java.util.logging.Handler handler : global.getHandlers()) -+ { -+ global.removeHandler(handler); -+ } -+ -+ global.addHandler(new ForwardLogHandler()); -+ -+ final org.apache.logging.log4j.core.Logger logger = ((org.apache.logging.log4j.core.Logger) LogManager.getRootLogger()); -+ -+ for (org.apache.logging.log4j.core.Appender appender : logger.getAppenders().values()) -+ { -+ if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) -+ { -+ logger.removeAppender(appender); -+ } -+ } -+ -+ // Crucible start - go nuclear on all other loggers -+ try { -+ LoggerContext ctx = (LoggerContext) FMLRelaunchLog.log.ctx; -+ Field loggersField = LoggerContext.class.getDeclaredField("loggers"); -+ loggersField.setAccessible(true); -+ @SuppressWarnings("unchecked") -+ ConcurrentMap loggers = -+ (ConcurrentMap) loggersField.get(ctx); -+ for (org.apache.logging.log4j.core.Logger loggerToHack : loggers.values()) { -+ for (org.apache.logging.log4j.core.Appender appender : loggerToHack.getAppenders().values()) -+ { -+ if (appender instanceof org.apache.logging.log4j.core.appender.ConsoleAppender) -+ { -+ loggerToHack.removeAppender(appender); -+ } -+ } -+ } -+ } catch (Throwable e) { -+ System.out.println("[Crucible] Unable to hack other loggers, expect broken and chaotic logs."); -+ e.printStackTrace(); -+ } -+ // Crucible end -+ -+ CrucibleServerMainHook.restoreStreams(); // Crucible - Restores the original streams so System.out does not log to forge logger -+ new Thread(new TerminalConsoleWriterThread(System.out, this.reader)).start(); -+ System.setOut(new PrintStream(new CrucibleTracingLoggerOutputStream(LogManager.getLogger("STDOUT"), Level.INFO), true)); -+ System.setErr(new PrintStream(new CrucibleTracingLoggerOutputStream(LogManager.getLogger("STDERR"), Level.WARN), true)); + // CraftBukkit end field_155771_h.info("Starting minecraft server version 1.7.10"); if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) -@@ -117,7 +206,7 @@ +@@ -117,7 +158,7 @@ FMLCommonHandler.instance().onServerStart(this); field_155771_h.info("Loading properties"); @@ -196,7 +148,7 @@ this.field_154332_n = new ServerEula(new File("eula.txt")); if (!this.field_154332_n.func_154346_a()) -@@ -172,6 +261,18 @@ +@@ -172,6 +213,18 @@ this.setServerPort(this.settings.getIntProperty("server-port", 25565)); } @@ -215,7 +167,7 @@ field_155771_h.info("Generating keypair"); this.setKeyPair(CryptManager.createNewKeyPair()); field_155771_h.info("Starting Minecraft server on " + (this.getServerHostname().length() == 0 ? "*" : this.getServerHostname()) + ":" + this.getServerPort()); -@@ -180,7 +281,7 @@ +@@ -180,7 +233,7 @@ { this.func_147137_ag().addLanEndpoint(inetaddress, this.getServerPort()); } @@ -224,7 +176,7 @@ { field_155771_h.warn("**** FAILED TO BIND TO PORT!"); field_155771_h.warn("The exception was: {}", new Object[] {ioexception.toString()}); -@@ -196,10 +297,17 @@ +@@ -196,10 +249,17 @@ field_155771_h.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); } @@ -244,7 +196,7 @@ if (!PreYggdrasilConverter.func_152714_a(this.settings)) { -@@ -208,7 +316,8 @@ +@@ -208,7 +268,8 @@ else { FMLCommonHandler.instance().onServerStarted(); @@ -254,7 +206,7 @@ long j = System.nanoTime(); if (this.getFolderName() == null) -@@ -274,11 +383,30 @@ +@@ -274,11 +335,30 @@ this.theRConThreadMain.startThread(); } @@ -285,7 +237,7 @@ public boolean canStructuresSpawn() { return this.canSpawnStructures; -@@ -364,11 +492,19 @@ +@@ -364,11 +444,19 @@ public void executePendingCommands() { diff --git a/patches/org/bukkit/plugin/PluginLogger.java.patch b/patches/org/bukkit/plugin/PluginLogger.java.patch index d19b24a9..7d5d34e1 100644 --- a/patches/org/bukkit/plugin/PluginLogger.java.patch +++ b/patches/org/bukkit/plugin/PluginLogger.java.patch @@ -1,25 +1,42 @@ --- ../src-base/minecraft/org/bukkit/plugin/PluginLogger.java +++ ../src-work/minecraft/org/bukkit/plugin/PluginLogger.java -@@ -22,14 +22,20 @@ +@@ -1,5 +1,7 @@ + package org.bukkit.plugin; + ++import io.github.crucible.JulLogManager; ++ + import java.util.logging.Level; + import java.util.logging.LogRecord; + import java.util.logging.Logger; +@@ -11,8 +13,7 @@ + * + * @see Logger + */ +-public class PluginLogger extends Logger { +- private String pluginName; ++public class PluginLogger extends JulLogManager.JulToLog4jLogger { + + /** + * Creates a new PluginLogger that extracts the name from a plugin. +@@ -20,17 +21,12 @@ + * @param context A reference to the plugin + */ public PluginLogger(Plugin context) { - super(context.getClass().getCanonicalName(), null); - String prefix = context.getDescription().getPrefix(); +- super(context.getClass().getCanonicalName(), null); +- String prefix = context.getDescription().getPrefix(); - pluginName = prefix != null ? new StringBuilder().append("[").append(prefix).append("] ").toString() : "[" + context.getDescription().getName() + "] "; -+ // Crucible start - give plugins a proper prefix for log4j -+ //pluginName = prefix != null ? new StringBuilder().append("[").append(prefix).append("] ").toString() : "[" + context.getDescription().getName() + "] "; -+ pluginName = prefix != null ? prefix : context.getDescription().getName(); -+ // Crucible end - setParent(context.getServer().getLogger()); +- setParent(context.getServer().getLogger()); ++ super(getLoggerName(context)); setLevel(Level.ALL); } - @Override - public void log(LogRecord logRecord) { +- @Override +- public void log(LogRecord logRecord) { - logRecord.setMessage(pluginName + logRecord.getMessage()); -+ // Crucible start - fix log4j prefix -+ //logRecord.setMessage(pluginName + logRecord.getMessage()); -+ logRecord.setLoggerName(pluginName); -+ // Crucible end - super.log(logRecord); +- super.log(logRecord); ++ private static String getLoggerName(Plugin context) { ++ String prefix = context.getDescription().getPrefix(); ++ return prefix != null ? prefix : context.getDescription().getName(); } - +- + } diff --git a/patches/org/bukkit/plugin/java/PluginClassLoader.java.patch b/patches/org/bukkit/plugin/java/PluginClassLoader.java.patch index 55da34a0..8eb3ad9e 100644 --- a/patches/org/bukkit/plugin/java/PluginClassLoader.java.patch +++ b/patches/org/bukkit/plugin/java/PluginClassLoader.java.patch @@ -1,20 +1,28 @@ --- ../src-base/minecraft/org/bukkit/plugin/java/PluginClassLoader.java +++ ../src-work/minecraft/org/bukkit/plugin/java/PluginClassLoader.java -@@ -1,5 +1,23 @@ +@@ -1,5 +1,31 @@ package org.bukkit.plugin.java; +// Cauldron start ++import com.gtnewhorizons.retrofuturabootstrap.Main; ++import com.gtnewhorizons.retrofuturabootstrap.URLClassLoaderBase; ++import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer; ++import io.github.crucible.api.CruciblePluginTransform; +import net.md_5.specialsource.provider.ClassLoaderProvider; +import net.md_5.specialsource.transformer.MavenShade; +//import org.bouncycastle.util.io.Streams; +import net.md_5.specialsource.*; +import net.md_5.specialsource.repo.*; ++import net.minecraft.launchwrapper.LaunchClassLoader; +import net.minecraft.server.MinecraftServer; +import net.minecraftforge.cauldron.CauldronUtils; + +import org.bukkit.craftbukkit.v1_7_R4.CraftServer; +import org.bukkit.plugin.PluginDescriptionFile; +import java.io.*; ++import java.lang.invoke.MethodHandle; ++import java.lang.invoke.MethodHandles; ++import java.lang.reflect.Method; +import java.net.JarURLConnection; +import java.security.CodeSigner; +import java.security.CodeSource; @@ -24,7 +32,11 @@ import java.io.File; import java.net.MalformedURLException; import java.net.URL; -@@ -10,21 +28,46 @@ +@@ -7,24 +33,69 @@ + import java.util.HashMap; + import java.util.Map; + import java.util.Set; ++import java.util.jar.Manifest; import org.apache.commons.lang.Validate; import org.bukkit.plugin.InvalidPluginException; @@ -35,6 +47,22 @@ */ -final class PluginClassLoader extends URLClassLoader { +public class PluginClassLoader extends URLClassLoader { ++ private static final LaunchClassLoader launchClassLoader = (LaunchClassLoader) PluginClassLoader.class.getClassLoader(); ++ private static final MethodHandle runTransformersMH; ++ ++ static { ++ MethodHandle runTransformersMHTmp; ++ try { ++ Method method = LaunchClassLoader.class.getDeclaredMethod("runTransformers", String.class, String.class, byte[].class); ++ method.setAccessible(true); ++ runTransformersMHTmp = MethodHandles.lookup().unreflect(method); ++ } catch (NoSuchMethodException | IllegalAccessException e) { ++ e.printStackTrace(); ++ runTransformersMHTmp = null; ++ } ++ runTransformersMH = runTransformersMHTmp; ++ } ++ + public JavaPlugin getPlugin() { return plugin; } // Spigot private final JavaPluginLoader loader; - private final Map> classes = new HashMap>(); @@ -51,6 +79,9 @@ + private RemapperProcessor remapperProcessor; // secondary; for inheritance & remapping reflection + private boolean debug; // classloader debugging + private int remapFlags = -1; ++ private boolean runRfbTransformers; ++ private boolean runLwTransformers; ++ private boolean runPluginTransformers; + private static ConcurrentMap jarMappings = new ConcurrentHashMap(); + private static final int F_GLOBAL_INHERIT = 1 << 1; @@ -75,7 +106,7 @@ PluginClassLoader(final JavaPluginLoader loader, final ClassLoader parent, final PluginDescriptionFile description, final File dataFolder, final File file) throws InvalidPluginException, MalformedURLException { super(new URL[] {file.toURI().toURL()}, parent); Validate.notNull(loader, "Loader cannot be null"); -@@ -34,6 +77,116 @@ +@@ -34,6 +105,121 @@ this.dataFolder = dataFolder; this.file = file; @@ -104,6 +135,9 @@ + boolean reflectClass = MinecraftServer.cauldronConfig.getBoolean("plugin-settings.default.remap-reflect-class", true); + boolean allowFuture = MinecraftServer.cauldronConfig.getBoolean("plugin-settings.default.remap-allow-future", false); + boolean remapGuava = MinecraftServer.cauldronConfig.getBoolean("plugin-settings.default.remap-guava", true); ++ runRfbTransformers = MinecraftServer.cauldronConfig.getBoolean("plugin-settings.default.rfb-transformers", true); ++ runLwTransformers = MinecraftServer.cauldronConfig.getBoolean("plugin-settings.default.lw-transformers", false); ++ runPluginTransformers = MinecraftServer.cauldronConfig.getBoolean("plugin-settings.default.plugin-transformers", true); + + // plugin-specific overrides + useCustomClassLoader = MinecraftServer.cauldronConfig.getBoolean("plugin-settings."+pluginName+".custom-class-loader", useCustomClassLoader, false); @@ -126,73 +160,75 @@ + reflectClass = MinecraftServer.cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-reflect-class", reflectClass, false); + allowFuture = MinecraftServer.cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-allow-future", allowFuture, false); + remapGuava = MinecraftServer.cauldronConfig.getBoolean("plugin-settings."+pluginName+".remap-guava", remapGuava, false); ++ runRfbTransformers = MinecraftServer.cauldronConfig.getBoolean("plugin-settings."+pluginName+".rfb-transformers", runRfbTransformers, false); ++ runLwTransformers = MinecraftServer.cauldronConfig.getBoolean("plugin-settings."+pluginName+".lw-transformers", runLwTransformers, false); ++ runPluginTransformers = MinecraftServer.cauldronConfig.getBoolean("plugin-settings."+pluginName+".plugin-transformers", runPluginTransformers, false); + + if (debug) { + System.out.println("PluginClassLoader debugging enabled for "+pluginName); + } + -+ if (!useCustomClassLoader) { -+ remapper = null; -+ return; -+ } -+ -+ int flags = 0; -+ if (remapNMS1710) flags |= F_REMAP_NMS1710; -+ if (remapNMS179) flags |= F_REMAP_NMS179; -+ if (remapNMS172) flags |= F_REMAP_NMS172; -+ if (remapNMS164) flags |= F_REMAP_NMS164; -+ if (remapNMS152) flags |= F_REMAP_NMS152; -+ if (!remapNMSPre.equals("false")) { -+ if (remapNMSPre.equals("1.7.10")) flags |= 0x17100000; -+ else if (remapNMSPre.equals("1.7.9")) flags |= 0x01790000; -+ else if (remapNMSPre.equals("1.7.2")) flags |= 0x01720000; -+ else if (remapNMSPre.equals("1.6.4")) flags |= 0x01640000; -+ else if (remapNMSPre.equals("1.5.2")) flags |= 0x01520000; -+ else { -+ System.out.println("Unsupported nms-remap-pre version '"+remapNMSPre+"', disabling"); ++ if (useCustomClassLoader) { ++ int flags = 0; ++ if (remapNMS1710) flags |= F_REMAP_NMS1710; ++ if (remapNMS179) flags |= F_REMAP_NMS179; ++ if (remapNMS172) flags |= F_REMAP_NMS172; ++ if (remapNMS164) flags |= F_REMAP_NMS164; ++ if (remapNMS152) flags |= F_REMAP_NMS152; ++ if (!remapNMSPre.equals("false")) { ++ if (remapNMSPre.equals("1.7.10")) flags |= 0x17100000; ++ else if (remapNMSPre.equals("1.7.9")) flags |= 0x01790000; ++ else if (remapNMSPre.equals("1.7.2")) flags |= 0x01720000; ++ else if (remapNMSPre.equals("1.6.4")) flags |= 0x01640000; ++ else if (remapNMSPre.equals("1.5.2")) flags |= 0x01520000; ++ else { ++ System.out.println("Unsupported nms-remap-pre version '"+remapNMSPre+"', disabling"); ++ } + } -+ } -+ if (remapOBC1710) flags |= F_REMAP_OBC1710; -+ if (remapOBC179) flags |= F_REMAP_OBC179; -+ if (remapOBC172) flags |= F_REMAP_OBC172; -+ if (remapOBC164) flags |= F_REMAP_OBC164; -+ if (remapOBC152) flags |= F_REMAP_OBC152; -+ if (remapOBCPre) flags |= F_REMAP_OBCPRE; -+ if (remapGuava) flags |= F_REMAP_GUAVA; -+ if (globalInherit) flags |= F_GLOBAL_INHERIT; -+ -+ remapFlags = flags; // used in findClass0 -+ JarMapping jarMapping = getJarMapping(flags); -+ -+ // Load inheritance map -+ if ((flags & F_GLOBAL_INHERIT) != 0) { -+ if (debug) { -+ System.out.println("Enabling global inheritance remapping"); -+ //ClassLoaderProvider.verbose = debug; // TODO: changed in https://github.com/md-5/SpecialSource/commit/132584eda4f0860c9d14f4c142e684a027a128b8#L3L48 ++ if (remapOBC1710) flags |= F_REMAP_OBC1710; ++ if (remapOBC179) flags |= F_REMAP_OBC179; ++ if (remapOBC172) flags |= F_REMAP_OBC172; ++ if (remapOBC164) flags |= F_REMAP_OBC164; ++ if (remapOBC152) flags |= F_REMAP_OBC152; ++ if (remapOBCPre) flags |= F_REMAP_OBCPRE; ++ if (remapGuava) flags |= F_REMAP_GUAVA; ++ if (globalInherit) flags |= F_GLOBAL_INHERIT; ++ ++ remapFlags = flags; // used in findClass0 ++ JarMapping jarMapping = getJarMapping(flags); ++ ++ // Load inheritance map ++ if ((flags & F_GLOBAL_INHERIT) != 0) { ++ if (debug) { ++ System.out.println("Enabling global inheritance remapping"); ++ //ClassLoaderProvider.verbose = debug; // TODO: changed in https://github.com/md-5/SpecialSource/commit/132584eda4f0860c9d14f4c142e684a027a128b8#L3L48 ++ } ++ jarMapping.setInheritanceMap(loader.getGlobalInheritanceMap()); ++ jarMapping.setFallbackInheritanceProvider(new ClassLoaderProvider(this)); + } -+ jarMapping.setInheritanceMap(loader.getGlobalInheritanceMap()); -+ jarMapping.setFallbackInheritanceProvider(new ClassLoaderProvider(this)); -+ } + -+ remapper = new thermos.ThermosRemapper(jarMapping); ++ remapper = new thermos.ThermosRemapper(jarMapping); + -+ if (pluginInherit || reflectFields || reflectClass) { -+ remapperProcessor = new RemapperProcessor( -+ pluginInherit ? loader.getGlobalInheritanceMap() : null, -+ (reflectFields || reflectClass) ? jarMapping : null); ++ if (pluginInherit || reflectFields || reflectClass) { ++ remapperProcessor = new RemapperProcessor( ++ pluginInherit ? loader.getGlobalInheritanceMap() : null, ++ (reflectFields || reflectClass) ? jarMapping : null); + -+ remapperProcessor.setRemapReflectField(reflectFields); -+ remapperProcessor.setRemapReflectClass(reflectClass); -+ remapperProcessor.debug = debug; -+ } else { -+ remapperProcessor = null; ++ remapperProcessor.setRemapReflectField(reflectFields); ++ remapperProcessor.setRemapReflectClass(reflectClass); ++ remapperProcessor.debug = debug; ++ } else { ++ remapperProcessor = null; ++ } + } ++ ++ + // Cauldron end + try { Class jarClass; try { -@@ -58,34 +211,291 @@ +@@ -58,34 +244,316 @@ } @Override @@ -378,12 +414,22 @@ + if (name.startsWith("net.minecraft.")) + { + JarMapping jarMapping = this.getJarMapping(remapFlags); // grab from SpecialSource -+ String remappedClass = jarMapping.classes.get(name.replaceAll("\\.", "\\/")); // get remapped pkgmcp class name -+ if (remappedClass == null){ ++ String remappedClass = jarMapping.classes.get(name.replace('.', '/')); // get remapped pkgmcp class name ++ if(remappedClass == null) { + throw new ClassNotFoundException(name); + } -+ Class clazz = ((net.minecraft.launchwrapper.LaunchClassLoader)MinecraftServer.getServer().getClass().getClassLoader()).findClass(remappedClass); -+ return clazz; ++ try { ++ Class clazz = ((net.minecraft.launchwrapper.LaunchClassLoader)MinecraftServer.getServer().getClass().getClassLoader()).findClass(remappedClass); ++ if(debug) { ++ System.out.printf("Found class %s (remapped to %s, found %s)\n", name, remappedClass, clazz.getName()); ++ } ++ return clazz; ++ } catch (Throwable e) { ++ if(debug) { ++ System.out.printf("Not found class %s (remapped %s)\n", name, remappedClass); ++ } ++ throw e; ++ } + } + if (name.startsWith("org.bukkit.")) { + if (debug) { @@ -402,7 +448,7 @@ + } + if (result == null) { -+ if (remapper == null) { ++ if (remapper == null && !runRfbTransformers && !runLwTransformers && !runPluginTransformers) { + result = super.findClass(name); + } else { + result = remappedFindClass(name); @@ -445,13 +491,31 @@ + bytecode = remapperProcessor.process(stream); + if (bytecode == null) stream = url.openStream(); + } ++ JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); // parses only ++ URL jarURL = jarURLConnection.getJarFileURL(); ++ CodeSource codeSource = new CodeSource(jarURL, new CodeSigner[0]); ++ Manifest manifest = jarURLConnection.getManifest(); + -+ /*if (bytecode == null) { -+ bytecode = Streams.readAll(stream); -+ }*/ ++ if (bytecode == null) { ++ bytecode = URLClassLoaderBase.readAllBytes(stream, null); ++ } + + // Remap the classes -+ byte[] remappedBytecode = remapper.remapClassFile(bytecode, RuntimeRepo.getInstance()); ++ byte[] remappedBytecode = bytecode; ++ if(remapper != null) { ++ remappedBytecode = remapper.remapClassFile(bytecode, RuntimeRepo.getInstance()); ++ } ++ if(runLwTransformers) { ++ remappedBytecode = (byte[]) runTransformersMH.invoke(launchClassLoader, name, name, remappedBytecode); ++ } ++ if(runPluginTransformers) { ++ remappedBytecode = CruciblePluginTransform.transform(name, name, remappedBytecode, plugin); ++ } ++ if(runRfbTransformers) { ++ remappedBytecode = launchClassLoader.runRfbTransformers( ++ Main.getRfbTransformers(), runLwTransformers ? RfbClassTransformer.Context.LCL_WITH_TRANSFORMS : RfbClassTransformer.Context.LCL_NO_TRANSFORMS, ++ manifest, name, remappedBytecode); ++ } + + if (debug) { + File file = new File("remapped-plugin-classes/"+name+".class"); @@ -465,15 +529,12 @@ + } + } + ++ + // Define (create) the class using the modified byte code + // The top-child class loader is used for this to prevent access violations + // Set the codesource to the jar, not within the jar, for compatibility with + // plugins that do new File(getClass().getProtectionDomain().getCodeSource().getLocation().toURI())) + // instead of using getResourceAsStream - see https://github.com/MinecraftPortCentral/Cauldron-Plus/issues/75 -+ JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection(); // parses only -+ URL jarURL = jarURLConnection.getJarFileURL(); -+ CodeSource codeSource = new CodeSource(jarURL, new CodeSigner[0]); -+ + result = this.defineClass(name, remappedBytecode, 0, remappedBytecode.length, codeSource); + if (result != null) { + // Resolve it - sets the class loader of the class diff --git a/src/main/java/io/github/crucible/JulLogManager.java b/src/main/java/io/github/crucible/JulLogManager.java new file mode 100644 index 00000000..8a61ac4f --- /dev/null +++ b/src/main/java/io/github/crucible/JulLogManager.java @@ -0,0 +1,247 @@ +package io.github.crucible; + +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; + +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +/** + * Backport and simplify log4j2-jul + */ +public class JulLogManager extends LogManager { + + @Override + public boolean addLogger(Logger logger) { + return false; + } + + @Override + public Logger getLogger(String name) { + try { + return new JulToLog4jLogger(name); + } catch (Throwable e) { + return new JulNoopLogger(name); + } + } + + public static class JulNoopLogger extends Logger { + + protected JulNoopLogger(String name) { + super(name, null); + } + } + + public static class JulToLog4jLogger extends Logger { + private final org.apache.logging.log4j.Logger logger; + + public JulToLog4jLogger(String name) { + super(name, null); + this.logger = org.apache.logging.log4j.LogManager.getLogger(name); + } + + @Override + public void log(LogRecord record) { + final org.apache.logging.log4j.Level level = convertLevel(record.getLevel()); + final Object[] parameters = record.getParameters(); + final MessageFactory messageFactory = logger.getMessageFactory(); + final Message message = parameters == null + ? messageFactory.newMessage(record.getMessage()) + : messageFactory.newMessage(record.getMessage(), parameters); + final Throwable thrown = record.getThrown(); + logger.log(level, null, message, thrown); + } + + @Override + public boolean isLoggable(Level level) { + return logger.isEnabled(convertLevel(level)); + } + + @Override + public String getName() { + return super.getName(); + } + + @Override + public Level getLevel() { + return super.getLevel(); + } + + @Override + public void setLevel(Level newLevel) throws SecurityException { + super.setLevel(newLevel); + } + + public org.apache.logging.log4j.Level convertLevel(Level level) { + if(level == Level.ALL) { + return org.apache.logging.log4j.Level.ALL; + } + if(level == Level.FINEST || level == Level.FINER) { + return org.apache.logging.log4j.Level.TRACE; + } + if(level == Level.FINE) { + return org.apache.logging.log4j.Level.DEBUG; + } + if(level == Level.INFO) { + return org.apache.logging.log4j.Level.INFO; + } + if(level == Level.WARNING) { + return org.apache.logging.log4j.Level.WARN; + } + if(level == Level.SEVERE) { + return org.apache.logging.log4j.Level.ERROR; + } + return org.apache.logging.log4j.Level.TRACE; + } + + @Override + public void setParent(final Logger parent) { + logger.warn("Logger.setParent() not supported by jul-to-log4j LogManager. Logs may be broken"); + } + + @Override + public Logger getParent() { + final org.apache.logging.log4j.core.Logger parent = ((org.apache.logging.log4j.core.Logger)logger).getParent(); + return parent == null ? null : Logger.getLogger(parent.getName()); + } + + @Override + public void log(final Level level, final String msg) { + logger.log(convertLevel(level), msg); + } + + @Override + public void log(final Level level, final String msg, final Object param1) { + logger.log(convertLevel(level), msg, param1); + } + + @Override + public void log(final Level level, final String msg, final Object[] params) { + logger.log(convertLevel(level), msg, params); + } + + @Override + public void log(final Level level, final String msg, final Throwable thrown) { + logger.log(convertLevel(level), msg, thrown); + } + + @Override + public void logp(final Level level, final String sourceClass, final String sourceMethod, final String msg) { + log(level, msg); + } + + @Override + public void logp( + final Level level, + final String sourceClass, + final String sourceMethod, + final String msg, + final Object param1) { + log(level, msg, param1); + } + + @Override + public void logp( + final Level level, + final String sourceClass, + final String sourceMethod, + final String msg, + final Object[] params) { + log(level, msg, params); + } + + @Override + public void logp( + final Level level, + final String sourceClass, + final String sourceMethod, + final String msg, + final Throwable thrown) { + log(level, msg, thrown); + } + + @Override + public void logrb( + final Level level, + final String sourceClass, + final String sourceMethod, + final String bundleName, + final String msg) { + log(level, msg); + } + + @Override + public void logrb( + final Level level, + final String sourceClass, + final String sourceMethod, + final String bundleName, + final String msg, + final Object param1) { + log(level, msg, param1); + } + + @Override + public void logrb( + final Level level, + final String sourceClass, + final String sourceMethod, + final String bundleName, + final String msg, + final Object[] params) { + log(level, msg, params); + } + + @Override + public void logrb( + final Level level, + final String sourceClass, + final String sourceMethod, + final String bundleName, + final String msg, + final Throwable thrown) { + log(level, msg, thrown); + } + + @Override + public void throwing(final String sourceClass, final String sourceMethod, final Throwable thrown) { + logger.throwing(thrown); + } + + @Override + public void severe(final String msg) { + logger.log(org.apache.logging.log4j.Level.ERROR, null, msg); + } + + @Override + public void warning(final String msg) { + logger.log(org.apache.logging.log4j.Level.WARN, null, msg); + } + + @Override + public void info(final String msg) { + logger.log(org.apache.logging.log4j.Level.INFO, null, msg); + } + + @Override + public void config(final String msg) { + logger.log(org.apache.logging.log4j.Level.INFO, null, msg); + } + + @Override + public void fine(final String msg) { + logger.log(org.apache.logging.log4j.Level.DEBUG, null, msg); + } + + @Override + public void finer(final String msg) { + logger.log(org.apache.logging.log4j.Level.TRACE, null, msg); + } + + @Override + public void finest(final String msg) { + logger.log(org.apache.logging.log4j.Level.TRACE, null, msg); + } + } +} diff --git a/src/main/java/io/github/crucible/api/CruciblePluginTransform.java b/src/main/java/io/github/crucible/api/CruciblePluginTransform.java new file mode 100644 index 00000000..488e7702 --- /dev/null +++ b/src/main/java/io/github/crucible/api/CruciblePluginTransform.java @@ -0,0 +1,34 @@ +package io.github.crucible.api; + +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.List; + +public class CruciblePluginTransform { + private static final List transformers = new ArrayList<>(); + + public static byte[] transform(String name, String transformedName, byte[] basicClass, Plugin plugin) { + byte[] result = basicClass; + for(IPluginClassTransformer transformer : transformers) { + byte[] bytes = transformer.transform(name, transformedName, result, plugin); + if(bytes != null) { + result = bytes; + } + } + return result; + } + + public static void register(IPluginClassTransformer transformer) { + transformers.add(transformer); + } + + public static void unregister(IPluginClassTransformer transformer) { + transformers.remove(transformer); + } + + @FunctionalInterface + public interface IPluginClassTransformer { + byte[] transform(String name, String transformedName, byte[] basicClass, Plugin plugin); + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/v1_7_R4/util/TerminalConsoleWriterThread.java b/src/main/java/org/bukkit/craftbukkit/v1_7_R4/util/TerminalConsoleWriterThread.java index bacb52ae..e1e3422a 100644 --- a/src/main/java/org/bukkit/craftbukkit/v1_7_R4/util/TerminalConsoleWriterThread.java +++ b/src/main/java/org/bukkit/craftbukkit/v1_7_R4/util/TerminalConsoleWriterThread.java @@ -26,7 +26,7 @@ public TerminalConsoleWriterThread(OutputStream output, ConsoleReader reader) { @SuppressWarnings("unchecked") Map> instance = (Map>) queues.get(null); instance.get("TerminalConsole").clear(); - } catch (ReflectiveOperationException e) { + } catch (ReflectiveOperationException | NullPointerException e) { e.printStackTrace(); } // Crucible end diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index ea87a0fa..6e7049b8 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -43,14 +43,14 @@ - + - + From dd87dacc1c6d4d0b2569e4290143c6ca9db91b1b Mon Sep 17 00:00:00 2001 From: Petrus Pradella Date: Sun, 21 Jun 2026 03:51:18 -0300 Subject: [PATCH 19/21] run gradlew wrapper again --- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 43764 bytes gradlew | 287 ++++++++++++++++++------------ gradlew.bat | 62 +++---- 3 files changed, 203 insertions(+), 146 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch literal 43764 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,92 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 9618d8d9..db3a6ac2 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,10 +27,14 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -37,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -51,48 +57,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From ce5a301ea3d8438e2c5bea228d3b8565195b7424 Mon Sep 17 00:00:00 2001 From: Petrus Pradella Date: Mon, 22 Jun 2026 17:32:00 -0300 Subject: [PATCH 20/21] FIX NPE on relocations at java8 by: Define the package before defining the class so Class.getPackage() In fact the relocation was not the problem, the NPE was '-' --- .../plugin/java/PluginClassLoader.java.patch | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/patches/org/bukkit/plugin/java/PluginClassLoader.java.patch b/patches/org/bukkit/plugin/java/PluginClassLoader.java.patch index 8eb3ad9e..273f00be 100644 --- a/patches/org/bukkit/plugin/java/PluginClassLoader.java.patch +++ b/patches/org/bukkit/plugin/java/PluginClassLoader.java.patch @@ -228,7 +228,7 @@ try { Class jarClass; try { -@@ -58,34 +244,316 @@ +@@ -58,34 +244,333 @@ } @Override @@ -530,6 +530,23 @@ + } + + ++ // Define the package before defining the class so Class.getPackage() ++ // resolves on Java 8 (it returns null for packages the loader never ++ // defined). URLClassLoader does this for the normal findClass path, ++ // but this remap path calls defineClass directly and must do it itself. ++ int packageDot = name.lastIndexOf('.'); ++ if (packageDot != -1) { ++ String packageName = name.substring(0, packageDot); ++ try { ++ if (manifest != null) { ++ definePackage(packageName, manifest, jarURL); ++ } else { ++ definePackage(packageName, null, null, null, null, null, null, null); ++ } ++ } catch (IllegalArgumentException ignored) { ++ // Already defined by another class in this package or thread. ++ } ++ } + // Define (create) the class using the modified byte code + // The top-child class loader is used for this to prevent access violations + // Set the codesource to the jar, not within the jar, for compatibility with From e27dae1d4ca9010972188b87ea953fde67c2f14d Mon Sep 17 00:00:00 2001 From: Petrus Pradella Date: Mon, 22 Jun 2026 17:39:53 -0300 Subject: [PATCH 21/21] Attempt to fix Github Action --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0282fa24..98d7154d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -249,7 +249,7 @@ fun gitInfo(key: String): String { mapOf( "hash" to runGit("log", "--format=%h", "-n", "1"), "fullHash" to runGit("log", "--format=%H", "-n", "1"), - "branch" to runGit("symbolic-ref", "--short", "HEAD"), + "branch" to runGit("rev-parse", "--abbrev-ref", "HEAD"), "message" to runGit("log", "--format=%B", "-n", "1") ) } else {