From 297cb01f220a617dd08096467978b2fccbc27695 Mon Sep 17 00:00:00 2001 From: nea Date: Wed, 1 Nov 2023 18:50:11 +0100 Subject: Add documentation --- agent/build.gradle.kts | 39 ++++++++ .../nea/modernjava/agent/Pack200Retransformer.java | 52 ++++++++++ .../nea/modernjava/agent/RelaunchEntryPoint.java | 70 +++++++++++++ build.gradle.kts | 15 ++- settings.gradle.kts | 2 +- .../moe/nea/modernjava/launch/FCPEntryPoint.java | 18 +++- .../modernjava/launch/FCPMixinAwareTweaker.java | 28 ------ .../moe/nea/modernjava/launch/FCPRelauncher.java | 102 ------------------- .../nea/modernjava/launch/WellKnownBlackboard.java | 17 ---- .../nea/modernjava/launch/live/FCPFixTweaker.java | 32 ++++++ .../modernjava/launch/relaunch/FCPRelauncher.java | 110 +++++++++++++++++++++ .../launch/util/ClassLoaderManipulations.java | 10 +- .../launch/util/ObjectHolderRefCompanion.java | 44 +++++++++ .../nea/modernjava/launch/util/PropertyNames.java | 16 +++ .../modernjava/launch/util/ReflectionUtils.java | 33 ------- .../nea/modernjava/launch/util/TextIoUtils.java | 22 +++++ .../launch/util/WellKnownBlackboard.java | 27 +++++ .../fml/nea/moe/modernjava/IAMFML.java | 9 ++ .../launch/transform/TransObjectHolderRef.kt | 18 +++- target/build.gradle.kts | 44 --------- .../modernjava/target/Pack200Retransformer.java | 46 --------- .../nea/modernjava/target/RelaunchEntryPoint.java | 56 ----------- 22 files changed, 469 insertions(+), 341 deletions(-) create mode 100644 agent/build.gradle.kts create mode 100644 agent/src/main/java/moe/nea/modernjava/agent/Pack200Retransformer.java create mode 100644 agent/src/main/java/moe/nea/modernjava/agent/RelaunchEntryPoint.java delete mode 100644 src/main/java/moe/nea/modernjava/launch/FCPMixinAwareTweaker.java delete mode 100644 src/main/java/moe/nea/modernjava/launch/FCPRelauncher.java delete mode 100644 src/main/java/moe/nea/modernjava/launch/WellKnownBlackboard.java create mode 100644 src/main/java/moe/nea/modernjava/launch/live/FCPFixTweaker.java create mode 100644 src/main/java/moe/nea/modernjava/launch/relaunch/FCPRelauncher.java create mode 100644 src/main/java/moe/nea/modernjava/launch/util/ObjectHolderRefCompanion.java create mode 100644 src/main/java/moe/nea/modernjava/launch/util/PropertyNames.java delete mode 100644 src/main/java/moe/nea/modernjava/launch/util/ReflectionUtils.java create mode 100644 src/main/java/moe/nea/modernjava/launch/util/TextIoUtils.java create mode 100644 src/main/java/moe/nea/modernjava/launch/util/WellKnownBlackboard.java delete mode 100644 target/build.gradle.kts delete mode 100644 target/src/main/java/moe/nea/modernjava/target/Pack200Retransformer.java delete mode 100644 target/src/main/java/moe/nea/modernjava/target/RelaunchEntryPoint.java diff --git a/agent/build.gradle.kts b/agent/build.gradle.kts new file mode 100644 index 0000000..983f917 --- /dev/null +++ b/agent/build.gradle.kts @@ -0,0 +1,39 @@ +plugins { + java + id("com.github.johnrengelman.shadow") version "7.1.2" +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(11)) +} + + +val shadowOnly: Configuration by configurations.creating { + attributes { + this.attribute(org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 11) + } +} + +dependencies { + shadowOnly(implementation("org.ow2.asm:asm:9.4")!!) + shadowOnly(implementation("org.ow2.asm:asm-commons:9.4")!!) + shadowOnly("dev.architectury:architectury-pack200:0.1.3") +} +tasks.withType(Jar::class) { + manifest { + attributes.apply { + this["Premain-Class"] = "moe.nea.modernjava.agent.Pack200Retransformer" + } + } +} + +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") +} +tasks.assemble.get().dependsOn(tasks.shadowJar) \ No newline at end of file diff --git a/agent/src/main/java/moe/nea/modernjava/agent/Pack200Retransformer.java b/agent/src/main/java/moe/nea/modernjava/agent/Pack200Retransformer.java new file mode 100644 index 0000000..67480bf --- /dev/null +++ b/agent/src/main/java/moe/nea/modernjava/agent/Pack200Retransformer.java @@ -0,0 +1,52 @@ +package moe.nea.modernjava.agent; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.commons.Remapper; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; +import java.util.Arrays; +import java.util.List; + +/** + * Transforms all classes such that they use dev.architectury.pack200.java instead of java.util.jar for Pack200 packing + * and unpacking. We cannot simply transform the jar on the classpath, since defining a package in the java directory + * is impossible in most versions of java, even from the boot class path. In theory this could be a custom class loader, + * 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(); + } +} diff --git a/agent/src/main/java/moe/nea/modernjava/agent/RelaunchEntryPoint.java b/agent/src/main/java/moe/nea/modernjava/agent/RelaunchEntryPoint.java new file mode 100644 index 0000000..e39a677 --- /dev/null +++ b/agent/src/main/java/moe/nea/modernjava/agent/RelaunchEntryPoint.java @@ -0,0 +1,70 @@ +package moe.nea.modernjava.agent; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Arrays; + +/** + * The main class for the new java runtime. + */ +public class RelaunchEntryPoint { + /** + * Logs things with a prefix to indicate it's coming from the new java runtime. + */ + public static void log(String text) { + System.out.println("[MODERNJAVA] " + text); + + } + + /** + * Wrap {@link #setupLaunch(String[])} in a crash handler for logging. + */ + public static void main(String[] args) { + log("This is modern java relaunch talking!"); + try { + setupLaunch(args); + } catch (Throwable t) { + log("Encountered exception during relaunching"); + t.printStackTrace(); + } + } + + private static void setupLaunch(String[] args) throws Exception { + + // Check if old class loader can load java modules. + log("Original SQLException: " + Thread.currentThread().getContextClassLoader().loadClass("java.sql.SQLException")); + + log("Setting up fake launch class loader"); + ClassLoader urlClassLoader = new URLClassLoader(retrieveClassPath(), Thread.currentThread().getContextClassLoader()); + + log("Created fake url class loader"); + Thread.currentThread().setContextClassLoader(urlClassLoader); + log("Handing off to Launch"); + + // Check if new class loader can load java modules. + log("LaunchClass ClassLoader SQLException: " + urlClassLoader.loadClass("java.sql.SQLException")); + + // Load launch wrapper and check launch class loader + Class launchClass = urlClassLoader.loadClass("net.minecraft.launchwrapper.Launch"); + log("LaunchClass: " + launchClass); + log("LaunchClass ClassLoader: " + launchClass.getClassLoader()); + + // Hand off to launch wrapper + Method main = launchClass.getMethod("main", String[].class); + main.invoke(null, new Object[]{args}); + } + + private static URL[] retrieveClassPath() throws MalformedURLException { + String property = System.getProperty("modernjava.relaunchclasspath"); + String[] split = property.split(File.pathSeparator); + URL[] urls = new URL[split.length]; + for (int i = 0; i < split.length; i++) { + urls[i] = new File(split[i]).toURI().toURL(); + } + log("Retrieved classpath: " + Arrays.toString(urls)); + return urls; + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 9e73297..154215c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,12 +30,16 @@ loom { } } -repositories { - mavenCentral() - maven("https://jitpack.io") { - content { - includeGroupByRegex("(com|io)\\.github\\..+") +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/") } } @@ -91,6 +95,7 @@ tasks.jar { tasks.shadowJar { archiveClassifier.set("all-dev") + relocate("dev.falsehonesty.asmhelper", "moe.nea.modernjava.dep.asmhelper") configurations = listOf(shadowImpl, shadowOnly) } diff --git a/settings.gradle.kts b/settings.gradle.kts index febc074..09aaae8 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,6 +18,6 @@ pluginManagement { } } -include("target") +include("agent") rootProject.name = "ModernJavaLauncher" diff --git a/src/main/java/moe/nea/modernjava/launch/FCPEntryPoint.java b/src/main/java/moe/nea/modernjava/launch/FCPEntryPoint.java index 3c32931..f7431ad 100644 --- a/src/main/java/moe/nea/modernjava/launch/FCPEntryPoint.java +++ b/src/main/java/moe/nea/modernjava/launch/FCPEntryPoint.java @@ -1,16 +1,25 @@ package moe.nea.modernjava.launch; +import moe.nea.modernjava.launch.live.FCPFixTweaker; import moe.nea.modernjava.launch.util.ClassLoaderManipulations; +import moe.nea.modernjava.launch.util.WellKnownBlackboard; import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; import java.net.URI; import java.util.List; import java.util.Map; +import static moe.nea.modernjava.launch.util.PropertyNames.HAS_RELAUNCHED; +/** + * Global entrypoint for both the FML part of the relaunched (live) and the relaunching (relaunch) runs. Execution + * begins during static initialization to be as early as possible. There aren't any security implications, but especially + * on Windows it can be problematic if two processes try to open up the same file, so we try to avoid these conflicts. + * Also, it's just a faster launch. + */ @IFMLLoadingPlugin.Name("ModernJavaRelauncher") public class FCPEntryPoint implements IFMLLoadingPlugin { - static URI fileUri; + public static URI fileUri; static { try { @@ -18,9 +27,10 @@ public class FCPEntryPoint implements IFMLLoadingPlugin { } catch (Exception e) { throw new RuntimeException(e); } - if (System.getProperty("modernjava.hasrelaunched") == null) { + if (System.getProperty(HAS_RELAUNCHED) == null) { try { - Class.forName("moe.nea.modernjava.launch.FCPRelauncher").getMethod("relaunch").invoke(null); + Class.forName("moe.nea.modernjava.launch.relaunch.FCPRelauncher") + .getMethod("relaunch").invoke(null); } catch (Throwable t) { System.out.println("Failed to relaunch"); t.printStackTrace(); @@ -32,7 +42,7 @@ public class FCPEntryPoint implements IFMLLoadingPlugin { throw new RuntimeException(e); } List tweakClasses = WellKnownBlackboard.tweakerNames(); - tweakClasses.add(FCPMixinAwareTweaker.class.getName()); + tweakClasses.add(FCPFixTweaker.class.getName()); } } diff --git a/src/main/java/moe/nea/modernjava/launch/FCPMixinAwareTweaker.java b/src/main/java/moe/nea/modernjava/launch/FCPMixinAwareTweaker.java deleted file mode 100644 index 585e943..0000000 --- a/src/main/java/moe/nea/modernjava/launch/FCPMixinAwareTweaker.java +++ /dev/null @@ -1,28 +0,0 @@ -package moe.nea.modernjava.launch; - -import net.minecraft.launchwrapper.ITweaker; -import net.minecraft.launchwrapper.LaunchClassLoader; - -import java.io.File; -import java.util.List; - -public class FCPMixinAwareTweaker implements ITweaker { - @Override - public void acceptOptions(List args, File gameDir, File assetsDir, String profile) { - } - - @Override - public void injectIntoClassLoader(LaunchClassLoader classLoader) { - classLoader.registerTransformer("moe.nea.modernjava.launch.transform.TransObjectHolderRef"); - } - - @Override - public String getLaunchTarget() { - return null; - } - - @Override - public String[] getLaunchArguments() { - return new String[0]; - } -} diff --git a/src/main/java/moe/nea/modernjava/launch/FCPRelauncher.java b/src/main/java/moe/nea/modernjava/launch/FCPRelauncher.java deleted file mode 100644 index ec27694..0000000 --- a/src/main/java/moe/nea/modernjava/launch/FCPRelauncher.java +++ /dev/null @@ -1,102 +0,0 @@ -package moe.nea.modernjava.launch; - -import net.minecraft.launchwrapper.Launch; -import net.minecraftforge.fml.common.launcher.FMLTweaker; -import net.minecraftforge.fml.nea.moe.modernjava.IAMFML; - -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -public class FCPRelauncher { - public static void relaunch() { - - 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); - - String modernJavaPath = "/home/nea/.sdkman/candidates/java/16.0.2-tem/bin/java"; - - - String thisJarFile = FCPEntryPoint.class.getProtectionDomain().getCodeSource().getLocation().getFile(); - thisJarFile = "/home/nea/src/ModernJavaLauncher/target/build/libs/target.jar"; - System.out.println("Found modern java jar at: " + thisJarFile); - - System.out.println("Located modern minecraft at: " + modernJavaPath); - - 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); - fullCommandLine.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); - fullCommandLine.add("-Dmodernjava.hasrelaunched=true"); - fullCommandLine.add("-Dmodernjava.relaunchclasspath=" + thisJarFile + File.pathSeparator + ManagementFactory.getRuntimeMXBean().getClassPath()); - fullCommandLine.add("--illegal-access=permit"); - for (String open : moduleOpens) { - fullCommandLine.add("--add-opens"); - fullCommandLine.add(open); - } - fullCommandLine.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005"); - fullCommandLine.add("-javaagent:" + thisJarFile); - fullCommandLine.add("--add-modules=ALL-MODULE-PATH,ALL-SYSTEM,ALL-DEFAULT,java.sql"); - fullCommandLine.add("-Xbootclasspath/a:" + thisJarFile); - fullCommandLine.add("moe.nea.modernjava.target.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 { - try { - new FileOutputStream(FileDescriptor.out).flush(); - new FileOutputStream(FileDescriptor.err).flush(); - } catch (IOException ignored) { - } - } - } 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); - } - -} diff --git a/src/main/java/moe/nea/modernjava/launch/WellKnownBlackboard.java b/src/main/java/moe/nea/modernjava/launch/WellKnownBlackboard.java deleted file mode 100644 index ee60b2a..0000000 --- a/src/main/java/moe/nea/modernjava/launch/WellKnownBlackboard.java +++ /dev/null @@ -1,17 +0,0 @@ -package moe.nea.modernjava.launch; - -import net.minecraft.launchwrapper.Launch; - -import java.util.List; -import java.util.Map; - -@SuppressWarnings("unchecked") -public class WellKnownBlackboard { - public static List tweakerNames() { - return (List) Launch.blackboard.get("TweakClasses"); - } - - public static Map launchArgs() { - return (Map) Launch.blackboard.get("launchArgs"); - } -} diff --git a/src/main/java/moe/nea/modernjava/launch/live/FCPFixTweaker.java b/src/main/java/moe/nea/modernjava/launch/live/FCPFixTweaker.java new file mode 100644 index 0000000..a5bf648 --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/live/FCPFixTweaker.java @@ -0,0 +1,32 @@ +package moe.nea.modernjava.launch.live; + +import moe.nea.modernjava.launch.transform.TransObjectHolderRef; +import net.minecraft.launchwrapper.ITweaker; +import net.minecraft.launchwrapper.LaunchClassLoader; + +import java.io.File; +import java.util.List; + +/** + * Tweaker class to inject {@link TransObjectHolderRef} + */ +public class FCPFixTweaker implements ITweaker { + @Override + public void acceptOptions(List args, File gameDir, File assetsDir, String profile) { + } + + @Override + public void injectIntoClassLoader(LaunchClassLoader classLoader) { + classLoader.registerTransformer("moe.nea.modernjava.launch.transform.TransObjectHolderRef"); + } + + @Override + public String getLaunchTarget() { + return null; + } + + @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 new file mode 100644 index 0000000..04fb0ea --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/relaunch/FCPRelauncher.java @@ -0,0 +1,110 @@ +package moe.nea.modernjava.launch.relaunch; + +import moe.nea.modernjava.launch.util.PropertyNames; +import moe.nea.modernjava.launch.util.TextIoUtils; +import moe.nea.modernjava.launch.util.WellKnownBlackboard; +import net.minecraftforge.fml.common.launcher.FMLTweaker; +import net.minecraftforge.fml.nea.moe.modernjava.IAMFML; + +import java.io.File; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +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() { + return new File("/home/nea/.sdkman/candidates/java/16.0.2-tem/bin/java") + } + + public static void relaunch() { + + List originalArgs = getOriginalArguments(); + + File modernJavaPath = findJavaLauncher(); + + + File agentFile; + agentFile = new File("/home/nea/src/ModernJavaLauncher/target/build/libs/target.jar"); + + System.out.println("Located modern minecraft at: " + modernJavaPath); + + 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); + fullCommandLine.add("--add-modules=ALL-MODULE-PATH,ALL-SYSTEM,ALL-DEFAULT,java.sql"); + fullCommandLine.add("-Xbootclasspath/a:" + agentFile); + fullCommandLine.add("moe.nea.modernjava.target.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); + } + +} diff --git a/src/main/java/moe/nea/modernjava/launch/util/ClassLoaderManipulations.java b/src/main/java/moe/nea/modernjava/launch/util/ClassLoaderManipulations.java index 09f2368..6cc21e9 100644 --- a/src/main/java/moe/nea/modernjava/launch/util/ClassLoaderManipulations.java +++ b/src/main/java/moe/nea/modernjava/launch/util/ClassLoaderManipulations.java @@ -1,6 +1,7 @@ package moe.nea.modernjava.launch.util; import net.minecraft.launchwrapper.Launch; +import net.minecraft.launchwrapper.LaunchClassLoader; import java.io.File; import java.lang.reflect.Method; @@ -8,7 +9,10 @@ import java.net.MalformedURLException; import java.net.URL; public class ClassLoaderManipulations { - + /** + * Adds a File to the parent class loader of the launch class loader. Necessary if you want to/have to use + * {@link LaunchClassLoader#addClassLoaderExclusion(String)}. + */ public static void addToParentClassLoader(File file) { try { addToParentClassLoader(file.toURI().toURL()); @@ -17,6 +21,10 @@ public class ClassLoaderManipulations { } } + /** + * Adds a URL to the parent class loader of the launch class loader. Necessary if you want to/have to use + * {@link LaunchClassLoader#addClassLoaderExclusion(String)}. + */ public static void addToParentClassLoader(URL file) { try { Launch.classLoader.addURL(file); diff --git a/src/main/java/moe/nea/modernjava/launch/util/ObjectHolderRefCompanion.java b/src/main/java/moe/nea/modernjava/launch/util/ObjectHolderRefCompanion.java new file mode 100644 index 0000000..15f9450 --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/util/ObjectHolderRefCompanion.java @@ -0,0 +1,44 @@ +package moe.nea.modernjava.launch.util; + +import moe.nea.modernjava.launch.transform.TransObjectHolderRef; +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + +/** + * A companion to my transformations from {@link TransObjectHolderRef} to avoid + * having to write all of this out in bytecode. + */ +public class ObjectHolderRefCompanion { + 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(); + } + } + + /** + * 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); + } + } +} diff --git a/src/main/java/moe/nea/modernjava/launch/util/PropertyNames.java b/src/main/java/moe/nea/modernjava/launch/util/PropertyNames.java new file mode 100644 index 0000000..8540dde --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/util/PropertyNames.java @@ -0,0 +1,16 @@ +package moe.nea.modernjava.launch.util; + +public class PropertyNames { + /** + * Property set to indicate whether the java process was launched on the new java version. + */ + public static final String HAS_RELAUNCHED = "modernjava.hasrelaunched"; + /** + * Classpath to load after reloading. + */ + public static final String RELAUNCH_CLASSPATH = "modernjava.relaunchclasspath"; + /** + * Starts a debugger on the given port if present. + */ + public static final String DEBUG_PORT = "modernjava.debugport"; +} diff --git a/src/main/java/moe/nea/modernjava/launch/util/ReflectionUtils.java b/src/main/java/moe/nea/modernjava/launch/util/ReflectionUtils.java deleted file mode 100644 index abc2364..0000000 --- a/src/main/java/moe/nea/modernjava/launch/util/ReflectionUtils.java +++ /dev/null @@ -1,33 +0,0 @@ -package moe.nea.modernjava.launch.util; - -import sun.misc.Unsafe; - -import java.lang.reflect.Field; - -public class ReflectionUtils { - 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(); - } - } - - public static void makeFieldWritable(Field f) { - String s = "Doing nothing. We will use unsafe to set the value instead, if possible"; - } - - 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/java/moe/nea/modernjava/launch/util/TextIoUtils.java b/src/main/java/moe/nea/modernjava/launch/util/TextIoUtils.java new file mode 100644 index 0000000..b7f82c0 --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/util/TextIoUtils.java @@ -0,0 +1,22 @@ +package moe.nea.modernjava.launch.util; + +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; + +public class TextIoUtils { + /** + * Flush out the standard IO, without closing it. Works even after {@link System#setOut(PrintStream)} or similar has been called + */ + public static void flushStdIO() { + try { + // These streams should not be closed. We just want to flush them out + //noinspection resource + new FileOutputStream(FileDescriptor.out).flush(); + //noinspection resource + new FileOutputStream(FileDescriptor.err).flush(); + } catch (IOException ignored) { + } + } +} diff --git a/src/main/java/moe/nea/modernjava/launch/util/WellKnownBlackboard.java b/src/main/java/moe/nea/modernjava/launch/util/WellKnownBlackboard.java new file mode 100644 index 0000000..967f531 --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/util/WellKnownBlackboard.java @@ -0,0 +1,27 @@ +package moe.nea.modernjava.launch.util; + +import net.minecraft.launchwrapper.Launch; + +import java.util.List; +import java.util.Map; + +/** + * Contains references to the {@link Launch#blackboard black board} in one central spot to avoid misspelling. + */ +@SuppressWarnings("unchecked") +public class WellKnownBlackboard { + /** + * A list of tweaker class names yet to be executed. This does not include tweaker class names present in the current + * round of tweaking. + */ + public static List tweakerNames() { + return (List) Launch.blackboard.get("TweakClasses"); + } + + /** + * A map of arguments in the form of --prefixedKey to value. + */ + public static Map launchArgs() { + return (Map) Launch.blackboard.get("launchArgs"); + } +} diff --git a/src/main/java/net/minecraftforge/fml/nea/moe/modernjava/IAMFML.java b/src/main/java/net/minecraftforge/fml/nea/moe/modernjava/IAMFML.java index 99ee7b0..d4d2844 100644 --- a/src/main/java/net/minecraftforge/fml/nea/moe/modernjava/IAMFML.java +++ b/src/main/java/net/minecraftforge/fml/nea/moe/modernjava/IAMFML.java @@ -1,7 +1,16 @@ package net.minecraftforge.fml.nea.moe.modernjava; +import net.minecraftforge.fml.relauncher.FMLSecurityManager; + +/** + * Class in a package starting with {@code net.minecraftforge.fml} in order to easily bypass the {@link FMLSecurityManager} + * which disallows calling {@link System#exit(int)}. + */ public class IAMFML { + /** + * Calls {@link System#exit(int)} + */ public static void shutdown(int code) { System.exit(code); } diff --git a/src/main/kotlin/moe/nea/modernjava/launch/transform/TransObjectHolderRef.kt b/src/main/kotlin/moe/nea/modernjava/launch/transform/TransObjectHolderRef.kt index 2fb4d04..2687dfc 100644 --- a/src/main/kotlin/moe/nea/modernjava/launch/transform/TransObjectHolderRef.kt +++ b/src/main/kotlin/moe/nea/modernjava/launch/transform/TransObjectHolderRef.kt @@ -5,14 +5,22 @@ 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" @@ -22,13 +30,16 @@ class TransObjectHolderRef : BaseClassTransformer() { aload(0/* arg0 */) invoke( InvokeType.STATIC, - "moe/nea/modernjava/launch/util/ReflectionUtils", + "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 @@ -56,7 +67,7 @@ class TransObjectHolderRef : BaseClassTransformer() { getField("net/minecraftforge/fml/common/registry/ObjectHolderRef", "field", "Ljava/lang/reflect/Field;") aload(1 /*thing*/) invokeStatic( - "moe/nea/modernjava/launch/util/ReflectionUtils", + "moe/nea/modernjava/launch/util/ObjectHolderRefCompanion", "doFieldWrite", "(Ljava/lang/reflect/Field;Ljava/lang/Object;)V" ) @@ -86,8 +97,7 @@ class TransObjectHolderRef : BaseClassTransformer() { val classLoader: LaunchClassLoader = Launch.classLoader classLoader.addTransformerExclusion("kotlin.") - classLoader.addTransformerExclusion("dev.falsehonesty.asmhelper.") - classLoader.addTransformerExclusion("org.objenesis.") + classLoader.addTransformerExclusion("moe.nea.modernjava.dep.asmhelper.") classLoader.addTransformerExclusion(this.javaClass.name) setup(classLoader) diff --git a/target/build.gradle.kts b/target/build.gradle.kts deleted file mode 100644 index 7ffdde9..0000000 --- a/target/build.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -plugins { - java - id("com.github.johnrengelman.shadow") version "7.1.2" -} - -java { - toolchain.languageVersion.set(JavaLanguageVersion.of(11)) -} - - -val shadowOnly: Configuration by configurations.creating { - attributes { - this.attribute(org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 11) - } -} -repositories { - mavenCentral() - maven("https://maven.architectury.dev/") - maven("https://repository.ow2.org/nexus/content/repositories/releases/") -} - -dependencies { - shadowOnly(implementation("org.ow2.asm:asm:9.4")!!) - shadowOnly(implementation("org.ow2.asm:asm-commons:9.4")!!) - shadowOnly("dev.architectury:architectury-pack200:0.1.3") -} -tasks.withType(Jar::class) { - manifest { - attributes.apply { - this["Premain-Class"] = "moe.nea.modernjava.target.Pack200Retransformer" - } - } -} - -tasks.shadowJar { - archiveClassifier.set("") - configurations = listOf(shadowOnly) - relocate("org.objectweb.asm", "moe.nea.modernjava.asm") - // relocate("dev.architectury.pack200.java", "java.util.jar") -} -tasks.jar { - archiveClassifier.set("thin") -} -tasks.assemble.get().dependsOn(tasks.shadowJar) \ No newline at end of file diff --git a/target/src/main/java/moe/nea/modernjava/target/Pack200Retransformer.java b/target/src/main/java/moe/nea/modernjava/target/Pack200Retransformer.java deleted file mode 100644 index 385e7ec..0000000 --- a/target/src/main/java/moe/nea/modernjava/target/Pack200Retransformer.java +++ /dev/null @@ -1,46 +0,0 @@ -package moe.nea.modernjava.target; - -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.commons.ClassRemapper; -import org.objectweb.asm.commons.Remapper; - -import java.lang.instrument.ClassFileTransformer; -import java.lang.instrument.IllegalClassFormatException; -import java.lang.instrument.Instrumentation; -import java.security.ProtectionDomain; -import java.util.Arrays; -import java.util.List; - -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(); - } -} diff --git a/target/src/main/java/moe/nea/modernjava/target/RelaunchEntryPoint.java b/target/src/main/java/moe/nea/modernjava/target/RelaunchEntryPoint.java deleted file mode 100644 index f30de3f..0000000 --- a/target/src/main/java/moe/nea/modernjava/target/RelaunchEntryPoint.java +++ /dev/null @@ -1,56 +0,0 @@ -package moe.nea.modernjava.target; - -import java.io.File; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Arrays; - -public class RelaunchEntryPoint { - public static void log(String text) { - System.out.println("[MODERNJAVA] " + text); - - } - - public static void main(String[] args) { - log("This is modern java relaunch talking!"); - try { - setupLaunch(args); - } catch (Throwable t) { - log("Encountered exception during relaunching"); - t.printStackTrace(); - } - } - - private static void setupLaunch(String[] args) throws Exception { - log("Original SQLException: " + Thread.currentThread().getContextClassLoader().loadClass("java.sql.SQLException")); - log("Setting up fake launch class loader"); - ClassLoader urlClassLoader = new URLClassLoader(retrieveClassPath(), Thread.currentThread().getContextClassLoader()); - log("Created fake url class loader"); - Thread.currentThread().setContextClassLoader(urlClassLoader); - log("Handing off to Launch"); - Class launchClass = urlClassLoader.loadClass("net.minecraft.launchwrapper.Launch"); - log("LaunchClass: " + launchClass); - log("LaunchClass ClassLoader: " + launchClass.getClassLoader()); - - log("LaunchClass ClassLoader SQLException: " + urlClassLoader.loadClass("java.sql.SQLException")); - - - Method main = launchClass.getMethod("main", String[].class); - main.invoke(null, new Object[]{args}); - } - - private static URL[] retrieveClassPath() throws MalformedURLException { - String property = System.getProperty("modernjava.relaunchclasspath"); - String[] split = property.split(File.pathSeparator); - URL[] urls = new URL[split.length]; - for (int i = 0; i < split.length; i++) { - urls[i] = new File(split[i]).toURI().toURL(); - } - log("Retrieved classpath: " + Arrays.toString(urls)); - return urls; - } - - -} -- cgit