From 5593488744dc5aa994b5ab2806ed56d7bc627368 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Thu, 20 Jun 2024 02:48:38 +0200 Subject: Modernize some stuff idk --- agent/build.gradle.kts | 6 +- .../java/moe/nea/modernjava/agent/LenientType.java | 34 +++ .../nea/modernjava/agent/Pack200Retransformer.java | 90 ++++--- build.gradle.kts | 134 +++++----- install.sh | 4 +- .../nea/modernjava/launch/live/FCPFixTweaker.java | 38 +-- .../modernjava/launch/relaunch/FCPRelauncher.java | 273 ++++++++++++--------- .../modernjava/launch/relaunch/JavaScanner.java | 248 +++++++++---------- .../nea/modernjava/launch/transform/BasePatch.java | 90 +++++++ .../launch/transform/PatchObjectHolderRef.java | 100 ++++++++ .../launch/util/ObjectHolderRefCompanion.java | 60 ++--- .../launch/transform/TransObjectHolderRef.kt | 118 --------- 12 files changed, 693 insertions(+), 502 deletions(-) create mode 100644 agent/src/main/java/moe/nea/modernjava/agent/LenientType.java create mode 100644 src/main/java/moe/nea/modernjava/launch/transform/BasePatch.java create mode 100644 src/main/java/moe/nea/modernjava/launch/transform/PatchObjectHolderRef.java delete mode 100644 src/main/kotlin/moe/nea/modernjava/launch/transform/TransObjectHolderRef.kt diff --git a/agent/build.gradle.kts b/agent/build.gradle.kts index c85be22..3be50bc 100644 --- a/agent/build.gradle.kts +++ b/agent/build.gradle.kts @@ -12,8 +12,8 @@ val shadowOnly: Configuration by configurations.creating { } dependencies { - shadowOnly(implementation("org.ow2.asm:asm:9.4")!!) - shadowOnly(implementation("org.ow2.asm:asm-commons:9.4")!!) + implementation("org.ow2.asm:asm:9.7") + implementation("org.ow2.asm:asm-commons:9.7") shadowOnly("dev.architectury:architectury-pack200:0.1.3") shadowOnly("org.apache.commons:commons-lang3:3.13.0") } @@ -28,8 +28,6 @@ tasks.withType(Jar::class) { tasks.shadowJar { archiveClassifier.set("") configurations = listOf(shadowOnly) - relocate("org.objectweb.asm", "moe.nea.modernjava.agent.dep.asm") - // relocate("dev.architectury.pack200.java", "java.util.jar") } tasks.jar { archiveClassifier.set("thin") diff --git a/agent/src/main/java/moe/nea/modernjava/agent/LenientType.java b/agent/src/main/java/moe/nea/modernjava/agent/LenientType.java new file mode 100644 index 0000000..88462f3 --- /dev/null +++ b/agent/src/main/java/moe/nea/modernjava/agent/LenientType.java @@ -0,0 +1,34 @@ +package moe.nea.modernjava.agent; + +import org.objectweb.asm.Type; + +public class LenientType { + /** + * {@link Type#getType(String)}, but implementing the old lenient behaviour. + * This deviates from the old behaviour in that it defaults to creating an object, + * instead of a method, but this is generally the desired behaviour. + */ + public static Type getType(String typeDescriptor) { + char c = 0; + if (!typeDescriptor.isEmpty()) { + c = typeDescriptor.charAt(0); + } + switch (c) { + case 'V': + case 'Z': + case 'C': + case 'B': + case 'S': + case 'I': + case 'F': + case 'J': + case 'D': + case '[': + case 'L': + case '(': + return Type.getType(typeDescriptor); + default: + return Type.getObjectType(typeDescriptor); + } + } +} diff --git a/agent/src/main/java/moe/nea/modernjava/agent/Pack200Retransformer.java b/agent/src/main/java/moe/nea/modernjava/agent/Pack200Retransformer.java index 67480bf..4d31329 100644 --- a/agent/src/main/java/moe/nea/modernjava/agent/Pack200Retransformer.java +++ b/agent/src/main/java/moe/nea/modernjava/agent/Pack200Retransformer.java @@ -3,6 +3,9 @@ package moe.nea.modernjava.agent; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.commons.ClassRemapper; import org.objectweb.asm.commons.Remapper; @@ -20,33 +23,62 @@ import java.util.List; * but the java agent is far more reliable. */ public class Pack200Retransformer implements ClassFileTransformer { - // relocate("dev.architectury.pack200.java", "java.util.jar") - List classes = Arrays.asList("AdaptiveCoding", "Attribute", "BandStructure", "ClassReader", "ClassWriter", "Code", "Coding", "CodingChooser", "CodingMethod", "ConstantPool", "Constants", "Driver", "DriverResource", "DriverResource_ja", "DriverResource_zh_CN", "FixedList", "Fixups", "Histogram", "Instruction", "NativeUnpack", "Pack200", "Pack200Adapter", "Pack200Plugin", "Package", "PackageReader", "PackageWriter", "PackerImpl", "PopulationCoding", "PropMap", "TLGlobals", "UnpackerImpl", "Utils"); - String architecturyPackage = "dev/architectury/pack200/java/"; - String javaPackage = "java/util/jar/"; - - public static void premain(String agentArgs, Instrumentation inst) { - inst.addTransformer(new Pack200Retransformer()); - } - - @Override - public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { - ClassReader reader = new ClassReader(classfileBuffer); - ClassWriter writer = new ClassWriter(reader, 0); - Remapper remapper = new Remapper() { - @Override - public String map(String internalName) { - if (internalName.startsWith(javaPackage)) { - for (String aClass : classes) { - if (internalName.equals(javaPackage + aClass) || (internalName.startsWith(javaPackage + aClass + "$"))) - return internalName.replace(javaPackage, architecturyPackage); - } - } - return internalName; - } - }; - ClassVisitor visitor = new ClassRemapper(writer, remapper); - reader.accept(visitor, 0); - return writer.toByteArray(); - } + // relocate("dev.architectury.pack200.java", "java.util.jar") + List classes = Arrays.asList("AdaptiveCoding", "Attribute", "BandStructure", "ClassReader", "ClassWriter", "Code", "Coding", "CodingChooser", "CodingMethod", "ConstantPool", "Constants", "Driver", "DriverResource", "DriverResource_ja", "DriverResource_zh_CN", "FixedList", "Fixups", "Histogram", "Instruction", "NativeUnpack", "Pack200", "Pack200Adapter", "Pack200Plugin", "Package", "PackageReader", "PackageWriter", "PackerImpl", "PopulationCoding", "PropMap", "TLGlobals", "UnpackerImpl", "Utils"); + String architecturyPackage = "dev/architectury/pack200/java/"; + String javaPackage = "java/util/jar/"; + + public static void premain(String agentArgs, Instrumentation inst) { + inst.addTransformer(new Pack200Retransformer()); + } + + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { + if (className.startsWith("moe/nea/modernjava/agent")) return classfileBuffer; + ClassReader reader = new ClassReader(classfileBuffer); + ClassWriter writer = new ClassWriter(reader, 0); + ClassVisitor visitor = writer; + visitor = getRemapVisitor(visitor); + visitor = getTypeVisitor(className, visitor); + reader.accept(visitor, 0); + return writer.toByteArray(); + } + + private ClassVisitor getRemapVisitor(ClassVisitor parent) { + Remapper remapper = new Remapper() { + @Override + public String map(String internalName) { + if (internalName.startsWith(javaPackage)) { + for (String aClass : classes) { + if (internalName.equals(javaPackage + aClass) || (internalName.startsWith(javaPackage + aClass + "$"))) + return internalName.replace(javaPackage, architecturyPackage); + } + } + return internalName; + } + }; + return new ClassRemapper(parent, remapper); + } + + private ClassVisitor getTypeVisitor(String className, ClassVisitor parent) { + if (className.startsWith("org/objectweb/asm/")) return parent; + return new ClassVisitor(Opcodes.ASM9, parent) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, descriptor, signature, exceptions)) { + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + boolean isType = owner.equals("org/objectweb$/asm/Type".replace("$","")); + boolean isName = "getType".equals(name); + boolean isDesc = "(Ljava/lang/String;)Lorg/objectweb/asm/Type;".equals(descriptor); + if (isType && isName && isDesc) { + owner = Type.getInternalName(LenientType.class); + } + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + + } + }; + } + }; + } } diff --git a/build.gradle.kts b/build.gradle.kts index 46deb21..41ea502 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,10 +1,10 @@ plugins { - idea - java - id("gg.essential.loom") version "0.10.0.+" - id("dev.architectury.architectury-pack200") version "0.1.3" - id("com.github.johnrengelman.shadow") version "7.1.2" - kotlin("jvm") version "1.9.10" + idea + java + id("gg.essential.loom") version "0.10.0.+" + id("dev.architectury.architectury-pack200") version "0.1.3" + id("com.github.johnrengelman.shadow") version "7.1.2" + kotlin("jvm") version "1.9.10" } group = "com.example.archloomtemplate" @@ -12,102 +12,114 @@ version = "1.0.0" // Toolchains: java { - toolchain.languageVersion.set(JavaLanguageVersion.of(8)) + toolchain.languageVersion.set(JavaLanguageVersion.of(8)) } val fcpEntryPoint = "moe.nea.modernjava.launch.FCPEntryPoint" // Minecraft configuration: loom { - log4jConfigs.from(file("log4j2.xml")) - launchConfigs { - "client" { - property("fml.coreMods.load", fcpEntryPoint) - } - } - forge { - pack200Provider.set(dev.architectury.pack200.java.Pack200Adapter()) - } + log4jConfigs.from(file("log4j2.xml")) + launchConfigs { + "client" { + property("fml.coreMods.load", fcpEntryPoint) + } + } + forge { + pack200Provider.set(dev.architectury.pack200.java.Pack200Adapter()) + } } allprojects { - repositories { - mavenCentral() - maven("https://jitpack.io") { - content { - includeGroupByRegex("(com|io)\\.github\\..+") - } - } - maven("https://maven.architectury.dev/") - maven("https://repository.ow2.org/nexus/content/repositories/releases/") - } + repositories { + mavenCentral() + maven("https://jitpack.io") { + content { + includeGroupByRegex("(com|io)\\.github\\..+") + } + } + maven("https://maven.architectury.dev/") + maven("https://repository.ow2.org/nexus/content/repositories/releases/") + } } val shadowImpl: Configuration by configurations.creating { - configurations.implementation.get().extendsFrom(this) + configurations.implementation.get().extendsFrom(this) } val agentDeps: Configuration by configurations.creating +val reentryDeps: Configuration by configurations.creating val shadowOnly: Configuration by configurations.creating { - attributes { - this.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 16) - } + attributes { + this.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 16) + } } dependencies { - minecraft("com.mojang:minecraft:1.8.9") - mappings("de.oceanlabs.mcp:mcp_stable:22-1.8.9") - forge("net.minecraftforge:forge:1.8.9-11.15.1.2318-1.8.9") - - shadowOnly("javax.annotation:javax.annotation-api:1.3.2") - shadowImpl("com.github.Skytils:AsmHelper:91ecc2bd9c") - shadowImpl(enforcedPlatform(kotlin("bom"))) - shadowImpl(kotlin("stdlib")) - agentDeps(project(":agent",configuration = "agentShadow")) + minecraft("com.mojang:minecraft:1.8.9") + mappings("de.oceanlabs.mcp:mcp_stable:22-1.8.9") + forge("net.minecraftforge:forge:1.8.9-11.15.1.2318-1.8.9") + + shadowOnly("javax.annotation:javax.annotation-api:1.3.2") + shadowImpl(enforcedPlatform(kotlin("bom"))) + shadowImpl(kotlin("stdlib")) + agentDeps(project(":agent", configuration = "agentShadow")) + reentryDeps("org.ow2.asm:asm-util:9.7") + reentryDeps("org.ow2.asm:asm-tree:9.7") + reentryDeps("org.ow2.asm:asm:9.7") + reentryDeps("org.ow2.asm:asm-commons:9.7") + reentryDeps("org.ow2.asm:asm-analysis:9.7") } // Tasks: -val doubleWrappedJar by tasks.creating(Zip::class) { - archiveFileName.set("agent.jar") - destinationDirectory.set(project.layout.buildDirectory.dir("wrapper")) - from(agentDeps) - into("agent") +val doubleWrappedJarAgent by tasks.creating(Zip::class) { + archiveFileName.set("agent.jar") + destinationDirectory.set(project.layout.buildDirectory.dir("wrapper-agent")) + from(agentDeps) + into("agent") +} + +val doubleWrappedJarRT by tasks.creating(Zip::class) { + archiveFileName.set("rt-deps.jar") + destinationDirectory.set(project.layout.buildDirectory.dir("wrapper-deps")) + from(reentryDeps) + into("deps") } tasks.withType(JavaCompile::class) { - options.encoding = "UTF-8" + options.encoding = "UTF-8" } tasks.withType(Jar::class) { - archiveBaseName.set("ModernJavaLauncher") - manifest.attributes.run { - this["FMLCorePlugin"] = fcpEntryPoint - this["ModSide"] = "BOTH" - this["ModType"] = "FML" - this["FMLCorePluginContainsFMLMod"] = "true" - this["ForceLoadAsMod"] = "true" - } + archiveBaseName.set("ModernJavaLauncher") + manifest.attributes.run { + this["FMLCorePlugin"] = fcpEntryPoint + this["ModSide"] = "BOTH" + this["ModType"] = "FML" + this["FMLCorePluginContainsFMLMod"] = "true" + this["ForceLoadAsMod"] = "true" + } } val remapJar by tasks.named("remapJar") { - archiveClassifier.set("") - from(tasks.shadowJar) - input.set(tasks.shadowJar.get().archiveFile) + archiveClassifier.set("") + from(tasks.shadowJar) + input.set(tasks.shadowJar.get().archiveFile) } tasks.jar { - archiveClassifier.set("thin-dev") + archiveClassifier.set("thin-dev") } tasks.shadowJar { - archiveClassifier.set("all-dev") - relocate("dev.falsehonesty.asmhelper", "moe.nea.modernjava.dep.asmhelper") - configurations = listOf(shadowImpl, shadowOnly) - from(doubleWrappedJar) + archiveClassifier.set("all-dev") + configurations = listOf(shadowImpl, shadowOnly) + from(doubleWrappedJarRT) + from(doubleWrappedJarAgent) } tasks.assemble.get().dependsOn(tasks.remapJar) diff --git a/install.sh b/install.sh index 4415fcc..4e24dbd 100755 --- a/install.sh +++ b/install.sh @@ -2,9 +2,9 @@ set -euo pipefail -rm -f ~/.local/share/PrismLauncher/instances/1.8.9/.minecraft/mods/ModernJavaLauncher-* +rm -f ~/.local/share/PrismLauncher/instances/'1.8.9 Java 17'/.minecraft/mods/ModernJavaLauncher-* ./gradlew :agent:build :build -cp ./build/libs/ModernJavaLauncher-1.0.0.jar ~/.local/share/PrismLauncher/instances/1.8.9/.minecraft/mods +cp ./build/libs/ModernJavaLauncher-1.0.0.jar ~/.local/share/PrismLauncher/instances/'1.8.9 Java 17'/.minecraft/mods diff --git a/src/main/java/moe/nea/modernjava/launch/live/FCPFixTweaker.java b/src/main/java/moe/nea/modernjava/launch/live/FCPFixTweaker.java index 0fe2285..826e765 100644 --- a/src/main/java/moe/nea/modernjava/launch/live/FCPFixTweaker.java +++ b/src/main/java/moe/nea/modernjava/launch/live/FCPFixTweaker.java @@ -1,6 +1,6 @@ package moe.nea.modernjava.launch.live; -import moe.nea.modernjava.launch.transform.TransObjectHolderRef; +import moe.nea.modernjava.launch.transform.PatchObjectHolderRef; import net.minecraft.launchwrapper.ITweaker; import net.minecraft.launchwrapper.LaunchClassLoader; @@ -8,27 +8,27 @@ import java.io.File; import java.util.List; /** - * Tweaker class to inject {@link TransObjectHolderRef} + * Tweaker class to inject {@link PatchObjectHolderRef} */ public class FCPFixTweaker implements ITweaker { - @Override - public void acceptOptions(List args, File gameDir, File assetsDir, String profile) { - } + @Override + public void acceptOptions(List args, File gameDir, File assetsDir, String profile) { + } - @Override - public void injectIntoClassLoader(LaunchClassLoader classLoader) { - classLoader.addClassLoaderExclusion("moe.nea.modernjava."); - classLoader.addClassLoaderExclusion("kotlin."); - classLoader.registerTransformer("moe.nea.modernjava.launch.transform.TransObjectHolderRef"); - } + @Override + public void injectIntoClassLoader(LaunchClassLoader classLoader) { + classLoader.addClassLoaderExclusion("moe.nea.modernjava."); + classLoader.addClassLoaderExclusion("kotlin."); + classLoader.registerTransformer("moe.nea.modernjava.launch.transform.PatchObjectHolderRef"); + } - @Override - public String getLaunchTarget() { - return null; - } + @Override + public String getLaunchTarget() { + return null; + } - @Override - public String[] getLaunchArguments() { - return new String[0]; - } + @Override + public String[] getLaunchArguments() { + return new String[0]; + } } diff --git a/src/main/java/moe/nea/modernjava/launch/relaunch/FCPRelauncher.java b/src/main/java/moe/nea/modernjava/launch/relaunch/FCPRelauncher.java index 3e13806..70002b5 100644 --- a/src/main/java/moe/nea/modernjava/launch/relaunch/FCPRelauncher.java +++ b/src/main/java/moe/nea/modernjava/launch/relaunch/FCPRelauncher.java @@ -1,5 +1,6 @@ package moe.nea.modernjava.launch.relaunch; +import moe.nea.modernjava.launch.FCPEntryPoint; import moe.nea.modernjava.launch.util.PropertyNames; import moe.nea.modernjava.launch.util.TextIoUtils; import moe.nea.modernjava.launch.util.WellKnownBlackboard; @@ -14,126 +15,166 @@ import java.io.OutputStream; import java.lang.management.ManagementFactory; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; public class FCPRelauncher { - /** - * @return the original arguments, as passed to the main method. - */ - public static List getOriginalArguments() { - List originalArgs = new ArrayList<>(); - - // Provided by FML - // This is highly processed so there might be some arguments that become lost, but almost everything should be in here. - // Namely non -- arguments get lost. I don't know any of these arguments that the vanilla launcher uses, so it should be fine? - // Also, some tweakers are missing. But we can fix this. - Map launchArgs = WellKnownBlackboard.launchArgs(); - if ("UnknownFMLProfile".equals(launchArgs.get("--version"))) { - launchArgs.remove("--version"); - } - for (Map.Entry argument : launchArgs.entrySet()) { - originalArgs.add(argument.getKey()); - originalArgs.add(argument.getValue()); - } - - - originalArgs.add("--tweakClass"); - originalArgs.add(FMLTweaker.class.getName()); - System.out.println("Reconstructed original minecraft arguments: " + originalArgs); - return originalArgs; - } - - public static File findJavaLauncher() { - JavaScanner javaScanner = new JavaScanner(); - javaScanner.scanDefaultPaths(); - javaScanner.prettyPrint(); - JavaScanner.LocalJavaVersion candidate = javaScanner.findCandidate(); - if (candidate == null) { - System.err.println("Looks like we couldn't find a java candidate. Either install one, or if you have one" + - " and we just cannot find it, specify -D" + PropertyNames.JAVA_SCAN_PATH + "=." + - " We need a Java 16 JDK. Exiting now."); - IAMFML.shutdown(1); - throw new RuntimeException(); - } - System.out.println("Choosing Java Candidate:\n" + candidate.prettyPrint()); - return candidate.getJavaBinary(); - } - - public static File findAgentJar() { - try { - File file = File.createTempFile("mjr-agent", ".jar"); - try (InputStream is = FCPRelauncher.class.getResourceAsStream("/agent/agent.jar"); - OutputStream os = Files.newOutputStream(file.toPath())) { - assert is != null; - IOUtils.copy(is, os); - } - return file; - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static void relaunch() { - - List originalArgs = getOriginalArguments(); - - File modernJavaPath = findJavaLauncher(); - System.out.println("Located modern minecraft at: " + modernJavaPath); - - File agentFile = findAgentJar(); - System.out.println("Located agent jar at: " + agentFile); - - - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.inheritIO(); - processBuilder.directory(null); - - - List moduleOpens = new ArrayList<>(); - moduleOpens.add("java.base/java.util=ALL-UNNAMED"); - moduleOpens.add("java.base/jdk.internal.loader=ALL-UNNAMED"); - moduleOpens.add("java.base/java.lang.reflect=ALL-UNNAMED"); - - - List fullCommandLine = new ArrayList<>(); - fullCommandLine.add(modernJavaPath.getAbsolutePath()); - fullCommandLine.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); - fullCommandLine.add("-D" + PropertyNames.HAS_RELAUNCHED + "=true"); - fullCommandLine.add("-D" + PropertyNames.RELAUNCH_CLASSPATH + "=" + agentFile.getAbsolutePath() + File.pathSeparator + ManagementFactory.getRuntimeMXBean().getClassPath()); - fullCommandLine.add("--illegal-access=permit"); - for (String open : moduleOpens) { - fullCommandLine.add("--add-opens"); - fullCommandLine.add(open); - } - if (System.getProperty(PropertyNames.DEBUG_PORT) != null) - fullCommandLine.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:" + System.getProperty(PropertyNames.DEBUG_PORT)); - fullCommandLine.add("-javaagent:" + agentFile.getAbsolutePath()); - fullCommandLine.add("--add-modules=ALL-MODULE-PATH,ALL-SYSTEM,ALL-DEFAULT,java.sql"); - fullCommandLine.add("-Xbootclasspath/a:" + agentFile.getAbsolutePath()); - fullCommandLine.add("moe.nea.modernjava.agent.RelaunchEntryPoint"); - fullCommandLine.addAll(originalArgs); - - System.out.println("Full relaunch commandline: " + fullCommandLine); - - - processBuilder.command(fullCommandLine); - int exitCode; - try { - try { - Process process = processBuilder.start(); - exitCode = process.waitFor(); - } finally { - TextIoUtils.flushStdIO(); - } - } catch (IOException | InterruptedException e) { - throw new RuntimeException("Failed to relaunch with old java version", e); - } - - - System.out.println("Exiting outer relaunch layer"); - IAMFML.shutdown(exitCode); - } + /** + * @return the original arguments, as passed to the main method. + */ + public static List getOriginalArguments() { + List originalArgs = new ArrayList<>(); + + // Provided by FML + // This is highly processed so there might be some arguments that become lost, but almost everything should be in here. + // Namely non -- arguments get lost. I don't know any of these arguments that the vanilla launcher uses, so it should be fine? + // Also, some tweakers are missing. But we can fix this. + Map launchArgs = WellKnownBlackboard.launchArgs(); + if ("UnknownFMLProfile".equals(launchArgs.get("--version"))) { + launchArgs.remove("--version"); + } + for (Map.Entry argument : launchArgs.entrySet()) { + originalArgs.add(argument.getKey()); + originalArgs.add(argument.getValue()); + } + + + originalArgs.add("--tweakClass"); + originalArgs.add(FMLTweaker.class.getName()); + System.out.println("Reconstructed original minecraft arguments: " + originalArgs); + return originalArgs; + } + + public static File findJavaLauncher() { + JavaScanner javaScanner = new JavaScanner(); + javaScanner.scanDefaultPaths(); + javaScanner.prettyPrint(); + JavaScanner.LocalJavaVersion candidate = javaScanner.findCandidate(); + if (candidate == null) { + System.err.println("Looks like we couldn't find a java candidate. Either install one, or if you have one" + + " and we just cannot find it, specify -D" + PropertyNames.JAVA_SCAN_PATH + "=." + + " We need a Java 16 JDK. Exiting now."); + IAMFML.shutdown(1); + throw new RuntimeException(); + } + System.out.println("Choosing Java Candidate:\n" + candidate.prettyPrint()); + return candidate.getJavaBinary(); + } + + public static File copyResource(String name, String resourcePath) { + try { + File file = File.createTempFile(name, ".jar"); + try (InputStream is = FCPRelauncher.class.getResourceAsStream(resourcePath); + OutputStream os = Files.newOutputStream(file.toPath())) { + assert is != null; + IOUtils.copy(is, os); + } + return file; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static File findAgentJar() { + return copyResource("mjr-agent", "/agent/agent.jar"); + } + + public static void relaunch() { + + List originalArgs = getOriginalArguments(); + + File modernJavaPath = findJavaLauncher(); + System.out.println("Located modern minecraft at: " + modernJavaPath); + + File agentFile = findAgentJar(); + System.out.println("Located agent jar at: " + agentFile); + + + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.inheritIO(); + processBuilder.directory(null); + + + List moduleOpens = new ArrayList<>(); + moduleOpens.add("java.base/java.util=ALL-UNNAMED"); + moduleOpens.add("java.base/jdk.internal.loader=ALL-UNNAMED"); + moduleOpens.add("java.base/java.lang.reflect=ALL-UNNAMED"); + + + List fullCommandLine = new ArrayList<>(); + fullCommandLine.add(modernJavaPath.getAbsolutePath()); + fullCommandLine.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); + fullCommandLine.add("-D" + PropertyNames.HAS_RELAUNCHED + "=true"); + fullCommandLine.add("-D" + PropertyNames.RELAUNCH_CLASSPATH + "=" + createClassPath(agentFile)); + fullCommandLine.add("--illegal-access=permit"); + for (String open : moduleOpens) { + fullCommandLine.add("--add-opens"); + fullCommandLine.add(open); + } + if (System.getProperty(PropertyNames.DEBUG_PORT) != null) + fullCommandLine.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:" + System.getProperty(PropertyNames.DEBUG_PORT)); + fullCommandLine.add("-Xbootclasspath/a:" + agentFile.getAbsolutePath()); + fullCommandLine.add("-Xbootclasspath/a:" + extractExtraDependencies().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator))); + fullCommandLine.add("-javaagent:" + agentFile.getAbsolutePath()); + fullCommandLine.add("--add-modules=ALL-MODULE-PATH,ALL-SYSTEM,ALL-DEFAULT,java.sql"); + fullCommandLine.add("moe.nea.modernjava.agent.RelaunchEntryPoint"); + fullCommandLine.addAll(originalArgs); + + System.out.println("Full relaunch commandline: " + fullCommandLine); + + + processBuilder.command(fullCommandLine); + int exitCode; + try { + try { + Process process = processBuilder.start(); + exitCode = process.waitFor(); + } finally { + TextIoUtils.flushStdIO(); + } + } catch (IOException | InterruptedException e) { + throw new RuntimeException("Failed to relaunch with old java version", e); + } + + + System.out.println("Exiting outer relaunch layer"); + IAMFML.shutdown(exitCode); + } + + private static String createClassPath(File agentFile) { + List classPath = new ArrayList<>(); + classPath.addAll(Arrays.asList(ManagementFactory.getRuntimeMXBean().getClassPath().split(File.pathSeparator))); + classPath.removeIf(it -> it.matches("[/\\\\]asm-[^/\\\\]+-5\\.[^/\\\\]+\\.jar")); + classPath.add(0, agentFile.getAbsolutePath()); + return String.join(File.pathSeparator, classPath); + } + + public static List extractExtraDependencies() { + try (ZipFile zipFile = new ZipFile(getCurrentJarFile())) { + List extractedResources = new ArrayList<>(); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (!entry.getName().startsWith("deps/")) continue; + if (!entry.getName().endsWith(".jar")) continue; + String[] split = entry.getName().split("/"); + extractedResources.add(copyResource(split[split.length - 1].split("\\.")[0], "/" + entry.getName())); + } + return extractedResources; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static File getCurrentJarFile() { + return new File(FCPEntryPoint.fileUri); + } } diff --git a/src/main/java/moe/nea/modernjava/launch/relaunch/JavaScanner.java b/src/main/java/moe/nea/modernjava/launch/relaunch/JavaScanner.java index 0003097..e50e5d4 100644 --- a/src/main/java/moe/nea/modernjava/launch/relaunch/JavaScanner.java +++ b/src/main/java/moe/nea/modernjava/launch/relaunch/JavaScanner.java @@ -11,127 +11,129 @@ import java.util.List; public class JavaScanner { - public static final String JAVA_BINARY_PATH = "bin/java" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : ""); - public static final String JAVA_COMPILER_PATH = "bin/javac" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : ""); - - public boolean isJavaHome(File potential) { - if (new File(potential, JAVA_BINARY_PATH).exists()) { - return true; - } - return false; - } - - private List versionList = new ArrayList<>(); - - public void scanDirectory(File file) { - scanDirectory(file, 1); - } - - public void scanDirectory(File file, int depthLimit) { - if (depthLimit < 0) return; - if (isJavaHome(file)) { - versionList.add(new LocalJavaVersion(file)); - } else { - for (File listFile : file.listFiles()) { - if (listFile.isDirectory()) { - scanDirectory(listFile, depthLimit - 1); - } - } - } - } - - public void prettyPrint() { - String s = "Fun fact these are the found Java Runtime Environments:\n"; - for (LocalJavaVersion localJavaVersion : versionList) { - s += localJavaVersion.prettyPrint(); - } - System.out.println(s); - } - - public void scanDefaultPaths() { - File home = new File(System.getProperty("user.home")); - scanDirectory(new File(home, ".sdkman/candidates/java")); - scanDirectory(new File(home, ".jdks")); - scanDirectory(new File("/usr"), 0); - String[] paths = System.getProperty(PropertyNames.JAVA_SCAN_PATH, "").split(File.pathSeparator); - for (String path : paths) { - if (!path.isEmpty()) { - scanDirectory(new File(path).getParentFile(), 3); - } - } - } - - public LocalJavaVersion findCandidate() { - LocalJavaVersion bestFit = null; - for (LocalJavaVersion localJavaVersion : versionList) { - if (localJavaVersion.isJdk() && localJavaVersion.getMajorVersion() == 16) { - bestFit = localJavaVersion; - } - } - return bestFit; - } - - public static class LocalJavaVersion { - private final File javaHome; - private String versionString; - - public LocalJavaVersion(File javaHome) { - this.javaHome = javaHome; - } - - public File getJavaHome() { - return javaHome; - } - - public File getJavaBinary() { - return new File(javaHome, JAVA_BINARY_PATH); - } - - public boolean isJdk() { - return new File(javaHome, JAVA_COMPILER_PATH).exists(); - } - - public String getVersionString() { - if (versionString == null) { - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.command(getJavaBinary().getAbsolutePath(), "-version"); - processBuilder.redirectInput(ProcessBuilder.Redirect.PIPE); - processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE); - processBuilder.redirectError(ProcessBuilder.Redirect.PIPE); - try { - Process process = processBuilder.start(); - process.waitFor(); - versionString = IOUtils.toString(process.getInputStream()) + IOUtils.toString(process.getErrorStream()); - } catch (Exception e) { - e.printStackTrace(); - versionString = ""; - } - } - return versionString; - } - - public String prettyPrint() { - return javaHome.getAbsolutePath() + ":\n" - + "\tJava Binary: " + getJavaBinary().getAbsolutePath() + "\n" - + "\tMajor Version: " + getMajorVersion() + "\n" - + "\tFull Version: " + getVersion() + "\n" - + "\tIs Jdk: " + isJdk() + "\n" - ; - } - - public String getVersion() { - String v = getVersionString(); - String[] s = v.split("\""); - if (s.length < 2) return null; - return s[1]; - } - - public int getMajorVersion() { - try { - return Integer.parseInt(getVersion().split("\\.")[0]); - } catch (Exception e) { - return -1; - } - } - } + public static final String JAVA_BINARY_PATH = "bin/java" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : ""); + public static final String JAVA_COMPILER_PATH = "bin/javac" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : ""); + + public boolean isJavaHome(File potential) { + if (new File(potential, JAVA_BINARY_PATH).exists()) { + return true; + } + return false; + } + + private List versionList = new ArrayList<>(); + + public void scanDirectory(File file) { + scanDirectory(file, 1); + } + + public void scanDirectory(File file, int depthLimit) { + if (depthLimit < 0) return; + if (isJavaHome(file)) { + versionList.add(new LocalJavaVersion(file)); + } else { + File[] files = file.listFiles(); + if (files == null) return; + for (File listFile : files) { + if (listFile.isDirectory()) { + scanDirectory(listFile, depthLimit - 1); + } + } + } + } + + public void prettyPrint() { + String s = "Fun fact these are the found Java Runtime Environments:\n"; + for (LocalJavaVersion localJavaVersion : versionList) { + s += localJavaVersion.prettyPrint(); + } + System.out.println(s); + } + + public void scanDefaultPaths() { + File home = new File(System.getProperty("user.home")); + scanDirectory(new File(home, ".sdkman/candidates/java")); + scanDirectory(new File(home, ".jdks")); + scanDirectory(new File("/usr"), 0); + String[] paths = System.getProperty(PropertyNames.JAVA_SCAN_PATH, "").split(File.pathSeparator); + for (String path : paths) { + if (!path.isEmpty()) { + scanDirectory(new File(path).getParentFile(), 3); + } + } + } + + public LocalJavaVersion findCandidate() { + LocalJavaVersion bestFit = null; + for (LocalJavaVersion localJavaVersion : versionList) { + if (localJavaVersion.isJdk() && localJavaVersion.getMajorVersion() == 16) { + bestFit = localJavaVersion; + } + } + return bestFit; + } + + public static class LocalJavaVersion { + private final File javaHome; + private String versionString; + + public LocalJavaVersion(File javaHome) { + this.javaHome = javaHome; + } + + public File getJavaHome() { + return javaHome; + } + + public File getJavaBinary() { + return new File(javaHome, JAVA_BINARY_PATH); + } + + public boolean isJdk() { + return new File(javaHome, JAVA_COMPILER_PATH).exists(); + } + + public String getVersionString() { + if (versionString == null) { + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.command(getJavaBinary().getAbsolutePath(), "-version"); + processBuilder.redirectInput(ProcessBuilder.Redirect.PIPE); + processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE); + processBuilder.redirectError(ProcessBuilder.Redirect.PIPE); + try { + Process process = processBuilder.start(); + process.waitFor(); + versionString = IOUtils.toString(process.getInputStream()) + IOUtils.toString(process.getErrorStream()); + } catch (Exception e) { + e.printStackTrace(); + versionString = ""; + } + } + return versionString; + } + + public String prettyPrint() { + return javaHome.getAbsolutePath() + ":\n" + + "\tJava Binary: " + getJavaBinary().getAbsolutePath() + "\n" + + "\tMajor Version: " + getMajorVersion() + "\n" + + "\tFull Version: " + getVersion() + "\n" + + "\tIs Jdk: " + isJdk() + "\n" + ; + } + + public String getVersion() { + String v = getVersionString(); + String[] s = v.split("\""); + if (s.length < 2) return null; + return s[1]; + } + + public int getMajorVersion() { + try { + return Integer.parseInt(getVersion().split("\\.")[0]); + } catch (Exception e) { + return -1; + } + } + } } diff --git a/src/main/java/moe/nea/modernjava/launch/transform/BasePatch.java b/src/main/java/moe/nea/modernjava/launch/transform/BasePatch.java new file mode 100644 index 0000000..1ef494c --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/transform/BasePatch.java @@ -0,0 +1,90 @@ +package moe.nea.modernjava.launch.transform; + +import net.minecraft.launchwrapper.IClassTransformer; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.util.Printer; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceMethodVisitor; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public abstract class BasePatch implements IClassTransformer { + protected abstract String getTargetedName(); + + protected Type getTargetedType() { + return getClassType(getTargetedName()); + } + + protected abstract ClassNode transform(ClassNode classNode); + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (basicClass == null) return null; + if (!getTargetedName().equals(name)) return basicClass; + ClassNode node = new ClassNode(); + ClassReader reader = new ClassReader(basicClass); + reader.accept(node, 0); + ClassNode processedNode = transform(node); + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + processedNode.accept(writer); + return writer.toByteArray(); + } + + protected Iterable iterableInstructions(InsnList insns) { + return new Iterable() { + @NotNull + @Override + public Iterator iterator() { + return insns.iterator(); + } + }; + } + + private Printer printer = new Textifier(); + private TraceMethodVisitor traceMethodVisitor = new TraceMethodVisitor(printer); + + protected String debugInsn(AbstractInsnNode insnNode) { + insnNode.accept(traceMethodVisitor); + StringWriter sw = new StringWriter(); + printer.print(new PrintWriter(sw)); + printer.getText().clear(); + return sw.toString(); + } + + protected List debugInsnList(InsnList list) { + List strings = new ArrayList<>(); + for (AbstractInsnNode node : iterableInstructions(list)) { + strings.add(debugInsn(node)); + } + return strings; + } + + protected Type getClassType(String plainName) { + return Type.getObjectType(plainName.replace('.', '/')); + } + + protected static MethodNode findMethod(ClassNode node, String name, Type desc) { + System.out.println("Searching for " + name + " " + desc.getDescriptor()); + for (MethodNode method : node.methods) { + System.out.println(" - Candidate: " + method.name + " " + method.desc); + if (name.equals(method.name) && desc.getDescriptor().equals(method.desc)) { + System.out.println("Found method"); + return method; + } + } + System.out.println("Could not find method."); + return null; + } + +} diff --git a/src/main/java/moe/nea/modernjava/launch/transform/PatchObjectHolderRef.java b/src/main/java/moe/nea/modernjava/launch/transform/PatchObjectHolderRef.java new file mode 100644 index 0000000..baef356 --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/transform/PatchObjectHolderRef.java @@ -0,0 +1,100 @@ +package moe.nea.modernjava.launch.transform; + + +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; + +import java.lang.reflect.Field; + +public class PatchObjectHolderRef extends BasePatch { + @Override + protected String getTargetedName() { + return "net.minecraftforge.fml.common.registry.ObjectHolderRef"; + } + + @Override + protected ClassNode transform(ClassNode classNode) { + patchFindWriteable(classNode); + patchApply(classNode); + return classNode; + } + + private void patchApply(ClassNode classNode) { + MethodNode apply = findMethod( + classNode, "apply", + Type.getMethodType(Type.VOID_TYPE) + ); + assert apply != null; + + InsnList insns = apply.instructions; + + AbstractInsnNode start = null, end = null; + int c = 0; + for (AbstractInsnNode instruction : iterableInstructions(insns)) { + if (instruction instanceof FieldInsnNode && instruction.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode) instruction).name.equals("newFieldAccessor")) { + start = instruction; + } + if (instruction.getOpcode() == Opcodes.INVOKEVIRTUAL && start != null) { + c++; + if (c == 2) { + end = instruction.getNext(); + break; + } + } + } + AbstractInsnNode trueStart = start; + { + InsnList insertion = new InsnList(); + insertion.add(new VarInsnNode(Opcodes.ALOAD, 0)); + insertion.add(new FieldInsnNode(Opcodes.GETFIELD, "net/minecraftforge/fml/common/registry/ObjectHolderRef", "field", "Ljava/lang/reflect/Field;")); + insertion.add(new VarInsnNode(Opcodes.ALOAD, 1)); + insertion.add(new MethodInsnNode(Opcodes.INVOKESTATIC, + "moe/nea/modernjava/launch/util/ObjectHolderRefCompanion", + "doFieldWrite", + "(Ljava/lang/reflect/Field;Ljava/lang/Object;)V", + false)); + insertion.add(new InsnNode(Opcodes.RETURN)); + insns.insertBefore(trueStart, insertion); + } + apply.maxLocals = 0; + AbstractInsnNode toRemove = start; + while (true) { + AbstractInsnNode next = toRemove.getNext(); + insns.remove(toRemove); + if (end == toRemove) + break; + toRemove = next; + } + } + + Type companionType = getClassType("moe.nea.modernjava.launch.util.ObjectHolderRefCompanion"); + + private void patchFindWriteable(ClassNode classNode) { + MethodNode makeWriteable = findMethod( + classNode, "makeWritable", + Type.getMethodType(Type.VOID_TYPE, Type.getType(Field.class)) + ); + assert makeWriteable != null; + makeWriteable.tryCatchBlocks.clear(); + InsnList insns = makeWriteable.instructions; + insns.clear(); + insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); + insns.add(new MethodInsnNode( + Opcodes.INVOKESTATIC, + companionType.getInternalName(), + "makeFieldWritable", + Type.getMethodType(Type.VOID_TYPE, Type.getType(Field.class)).getDescriptor(), + false + )); + insns.add(new InsnNode(Opcodes.RETURN)); + } +} diff --git a/src/main/java/moe/nea/modernjava/launch/util/ObjectHolderRefCompanion.java b/src/main/java/moe/nea/modernjava/launch/util/ObjectHolderRefCompanion.java index 15f9450..3b31b4e 100644 --- a/src/main/java/moe/nea/modernjava/launch/util/ObjectHolderRefCompanion.java +++ b/src/main/java/moe/nea/modernjava/launch/util/ObjectHolderRefCompanion.java @@ -1,44 +1,44 @@ package moe.nea.modernjava.launch.util; -import moe.nea.modernjava.launch.transform.TransObjectHolderRef; +import moe.nea.modernjava.launch.transform.PatchObjectHolderRef; import sun.misc.Unsafe; import java.lang.reflect.Field; /** - * A companion to my transformations from {@link TransObjectHolderRef} to avoid + * A companion to my transformations from {@link PatchObjectHolderRef} to avoid * having to write all of this out in bytecode. */ public class ObjectHolderRefCompanion { - private static Unsafe unsafe; + private static Unsafe unsafe; - static { - try { - final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - unsafe = (Unsafe) unsafeField.get(null); - } catch (Exception ex) { - ex.printStackTrace(); - } - } + static { + try { + final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + unsafe = (Unsafe) unsafeField.get(null); + } catch (Exception ex) { + ex.printStackTrace(); + } + } - /** - * A noop to have a jump target for the reflection factories. - */ - public static void makeFieldWritable(Field f) { - String s = "Doing nothing. We will use unsafe to set the value instead, if possible"; - } + /** + * A noop to have a jump target for the reflection factories. + */ + public static void makeFieldWritable(Field f) { + String s = "Doing nothing. We will use unsafe to set the value instead, if possible"; + } - /** - * Write a value to a static final field. - */ - public static void doFieldWrite(Field field, Object object) throws IllegalAccessException { - if (unsafe == null) { - field.set(null, object); - } else { - Object o = unsafe.staticFieldBase(field); - long l = unsafe.staticFieldOffset(field); - unsafe.putObject(o, l, object); - } - } + /** + * Write a value to a static final field. + */ + public static void doFieldWrite(Field field, Object object) throws IllegalAccessException { + if (unsafe == null) { + field.set(null, object); + } else { + Object o = unsafe.staticFieldBase(field); + long l = unsafe.staticFieldOffset(field); + unsafe.putObject(o, l, object); + } + } } diff --git a/src/main/kotlin/moe/nea/modernjava/launch/transform/TransObjectHolderRef.kt b/src/main/kotlin/moe/nea/modernjava/launch/transform/TransObjectHolderRef.kt deleted file mode 100644 index 2687dfc..0000000 --- a/src/main/kotlin/moe/nea/modernjava/launch/transform/TransObjectHolderRef.kt +++ /dev/null @@ -1,118 +0,0 @@ -package moe.nea.modernjava.launch.transform - -import dev.falsehonesty.asmhelper.BaseClassTransformer -import dev.falsehonesty.asmhelper.dsl.instructions.InsnListBuilder -import dev.falsehonesty.asmhelper.dsl.instructions.InvokeType -import dev.falsehonesty.asmhelper.dsl.modify -import dev.falsehonesty.asmhelper.dsl.overwrite -import moe.nea.modernjava.launch.util.ObjectHolderRefCompanion -import net.minecraft.launchwrapper.Launch -import net.minecraft.launchwrapper.LaunchClassLoader -import org.objectweb.asm.Opcodes -import org.objectweb.asm.tree.AbstractInsnNode -import org.objectweb.asm.tree.FieldInsnNode - -/** - * Transform [net.minecraftforge.fml.common.registry.ObjectHolderRef] such that it does not make references to outdated - * Java Reflection Tools anymore - */ -class TransObjectHolderRef : BaseClassTransformer() { - override fun makeTransformers() { - /** - * Redirect the makeWritable call to [ObjectHolderRefCompanion] - */ - overwrite { - className = "net.minecraftforge.fml.common.registry.ObjectHolderRef" - methodName = "makeWritable" - methodDesc = "(Ljava/lang/reflect/Field;)V" - - insnList { - aload(0/* arg0 */) - invoke( - InvokeType.STATIC, - "moe/nea/modernjava/launch/util/ObjectHolderRefCompanion", - "makeFieldWritable", - "(Ljava/lang/reflect/Field;)V" - ) - methodReturn() - } - } - /** - * Redirect the reflection calls to write a value to a static field in apply to [ObjectHolderRefCompanion] - */ - modify("net/minecraftforge/fml/common/registry/ObjectHolderRef") { - var end: AbstractInsnNode? = null - var start: AbstractInsnNode? = null - var c = 0 - val m = findMethod("apply", "()V") - for (instruction in m.instructions) { - if (instruction is FieldInsnNode && - instruction.opcode == Opcodes.GETSTATIC && - instruction.name == "newFieldAccessor" - ) { - start = instruction - } - if (instruction.opcode == Opcodes.INVOKEVIRTUAL && start != null) { - c++ - if (c == 2) { - end = instruction.next - break - } - } - } - end!! - val trueStart = start!! - m.instructions.insertBefore(trueStart, InsnListBuilder(m).apply { - aload(0/* this */) - getField("net/minecraftforge/fml/common/registry/ObjectHolderRef", "field", "Ljava/lang/reflect/Field;") - aload(1 /*thing*/) - invokeStatic( - "moe/nea/modernjava/launch/util/ObjectHolderRefCompanion", - "doFieldWrite", - "(Ljava/lang/reflect/Field;Ljava/lang/Object;)V" - ) - methodReturn() - }.insnList) - m.maxLocals = 0 - var toRemove = start!! - while (true) { - val n = toRemove.next - m.instructions.remove(toRemove) - if (end == toRemove) { - break - } - toRemove = n - } - } - } - - val supCalledSetup = BaseClassTransformer::class.java.getDeclaredField("calledSetup").also { - it.isAccessible = true - } - - fun mySetup() { - myCalledSetup = true - supCalledSetup.set(this, true) - - val classLoader: LaunchClassLoader = Launch.classLoader - - classLoader.addTransformerExclusion("kotlin.") - classLoader.addTransformerExclusion("moe.nea.modernjava.dep.asmhelper.") - classLoader.addTransformerExclusion(this.javaClass.name) - - setup(classLoader) - - makeTransformers() - - } - - var myCalledSetup = false - override fun transform(name: String?, transformedName: String?, basicClass: ByteArray?): ByteArray? { - if (!myCalledSetup) { - mySetup() - } - - return super.transform(name, transformedName, basicClass) - } - -} \ No newline at end of file -- cgit