diff options
Diffstat (limited to 'src')
9 files changed, 410 insertions, 0 deletions
diff --git a/src/main/java/moe/nea/modernjava/launch/FCPEntryPoint.java b/src/main/java/moe/nea/modernjava/launch/FCPEntryPoint.java new file mode 100644 index 0000000..3c32931 --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/FCPEntryPoint.java @@ -0,0 +1,65 @@ +package moe.nea.modernjava.launch; + +import moe.nea.modernjava.launch.util.ClassLoaderManipulations; +import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; + +import java.net.URI; +import java.util.List; +import java.util.Map; + + +@IFMLLoadingPlugin.Name("ModernJavaRelauncher") +public class FCPEntryPoint implements IFMLLoadingPlugin { + static URI fileUri; + + static { + try { + fileUri = new URI(FCPEntryPoint.class.getProtectionDomain().getCodeSource().getLocation().toString().split("!")[0]); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (System.getProperty("modernjava.hasrelaunched") == null) { + try { + Class.forName("moe.nea.modernjava.launch.FCPRelauncher").getMethod("relaunch").invoke(null); + } catch (Throwable t) { + System.out.println("Failed to relaunch"); + t.printStackTrace(); + } + } else { + try { + ClassLoaderManipulations.addToParentClassLoader(fileUri.toURL()); + } catch (Exception e) { + throw new RuntimeException(e); + } + List<String> tweakClasses = WellKnownBlackboard.tweakerNames(); + tweakClasses.add(FCPMixinAwareTweaker.class.getName()); + } + } + + + @Override + public String[] getASMTransformerClass() { + return new String[]{ + }; + } + + @Override + public String getModContainerClass() { + return null; + } + + @Override + public String getSetupClass() { + return null; + } + + @Override + public void injectData(Map<String, Object> map) { + + } + + @Override + public String getAccessTransformerClass() { + return null; + } +} diff --git a/src/main/java/moe/nea/modernjava/launch/FCPMixinAwareTweaker.java b/src/main/java/moe/nea/modernjava/launch/FCPMixinAwareTweaker.java new file mode 100644 index 0000000..585e943 --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/FCPMixinAwareTweaker.java @@ -0,0 +1,28 @@ +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<String> 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 new file mode 100644 index 0000000..ec27694 --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/FCPRelauncher.java @@ -0,0 +1,102 @@ +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<String> 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<String, String> launchArgs = WellKnownBlackboard.launchArgs(); + if ("UnknownFMLProfile".equals(launchArgs.get("--version"))) { + launchArgs.remove("--version"); + } + for (Map.Entry<String, String> 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<String> 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<String> 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 new file mode 100644 index 0000000..ee60b2a --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/WellKnownBlackboard.java @@ -0,0 +1,17 @@ +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<String> tweakerNames() { + return (List<String>) Launch.blackboard.get("TweakClasses"); + } + + public static Map<String, String> launchArgs() { + return (Map<String, String>) Launch.blackboard.get("launchArgs"); + } +} diff --git a/src/main/java/moe/nea/modernjava/launch/util/ClassLoaderManipulations.java b/src/main/java/moe/nea/modernjava/launch/util/ClassLoaderManipulations.java new file mode 100644 index 0000000..09f2368 --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/util/ClassLoaderManipulations.java @@ -0,0 +1,31 @@ +package moe.nea.modernjava.launch.util; + +import net.minecraft.launchwrapper.Launch; + +import java.io.File; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; + +public class ClassLoaderManipulations { + + public static void addToParentClassLoader(File file) { + try { + addToParentClassLoader(file.toURI().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + public static void addToParentClassLoader(URL file) { + try { + Launch.classLoader.addURL(file); + ClassLoader parentClassLoader = Launch.classLoader.getClass().getClassLoader(); + Method addUrl = parentClassLoader.getClass().getDeclaredMethod("addURL", URL.class); + addUrl.setAccessible(true); + addUrl.invoke(parentClassLoader, file); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/moe/nea/modernjava/launch/util/ReflectionUtils.java b/src/main/java/moe/nea/modernjava/launch/util/ReflectionUtils.java new file mode 100644 index 0000000..abc2364 --- /dev/null +++ b/src/main/java/moe/nea/modernjava/launch/util/ReflectionUtils.java @@ -0,0 +1,33 @@ +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/net/minecraftforge/fml/nea/moe/modernjava/IAMFML.java b/src/main/java/net/minecraftforge/fml/nea/moe/modernjava/IAMFML.java new file mode 100644 index 0000000..99ee7b0 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/nea/moe/modernjava/IAMFML.java @@ -0,0 +1,8 @@ +package net.minecraftforge.fml.nea.moe.modernjava; + +public class IAMFML { + + 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 new file mode 100644 index 0000000..2fb4d04 --- /dev/null +++ b/src/main/kotlin/moe/nea/modernjava/launch/transform/TransObjectHolderRef.kt @@ -0,0 +1,108 @@ +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 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 + +class TransObjectHolderRef : BaseClassTransformer() { + override fun makeTransformers() { + 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/ReflectionUtils", + "makeFieldWritable", + "(Ljava/lang/reflect/Field;)V" + ) + methodReturn() + } + } + 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/ReflectionUtils", + "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("dev.falsehonesty.asmhelper.") + classLoader.addTransformerExclusion("org.objenesis.") + 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 diff --git a/src/main/resources/mcmod.info b/src/main/resources/mcmod.info new file mode 100644 index 0000000..7b1a26b --- /dev/null +++ b/src/main/resources/mcmod.info @@ -0,0 +1,18 @@ +[ + { + "modid": "examplemod", + "name": "Xample Mod", + "description": "A mod that is used as an example.", + "version": "1.0.0", + "mcversion": "1.8.9", + "url": "https://github.com/romangraef/Forge1.8.9Template/", + "updateUrl": "", + "authorList": [ + "You" + ], + "credits": "", + "logoFile": "", + "screenshots": [], + "dependencies": [] + } +] |