diff --git a/gradle.properties b/gradle.properties index b0e4f8418..48cb45754 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ name=OneConfig mod_id=oneconfig version_major=1 version_minor=0 -version_patch=0-alpha.49 +version_patch=0-alpha.50 polyfrost.defaults.loom=3 diff --git a/versions/src/main/java/org/polyfrost/oneconfig/internal/legacy/OneConfigTweaker.java b/versions/src/main/java/org/polyfrost/oneconfig/internal/legacy/OneConfigTweaker.java index 7965ff16c..fd00ecd62 100644 --- a/versions/src/main/java/org/polyfrost/oneconfig/internal/legacy/OneConfigTweaker.java +++ b/versions/src/main/java/org/polyfrost/oneconfig/internal/legacy/OneConfigTweaker.java @@ -34,12 +34,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.polyfrost.oneconfig.api.platform.v1.Platform; -import org.polyfrost.oneconfig.utils.v1.MHUtils; import org.spongepowered.asm.launch.MixinBootstrap; import org.spongepowered.asm.mixin.Mixins; import java.io.File; -import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.net.URI; import java.net.URL; import java.nio.file.Files; @@ -49,20 +49,22 @@ import java.util.List; import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; import java.util.jar.Attributes; import java.util.jar.JarFile; /** * Mixin-related loading code adapted from EssentialGG's EssentialLoader under GPL-3.0 * here + *

+ * The most important part of this code is `OneConfigSourceFile` in the manifest. If a mod creates a custom tweaker + * but still wants the Mixin tweaker to be injected, they can add this attribute to their jar manifest to signal + * to OneConfig that it should inject the Mixin tweaker regardless of the tweaker class. + *

*/ @SuppressWarnings("unused") public class OneConfigTweaker implements ITweaker { private static final Logger LOGGER = LogManager.getLogger("OneConfig/Tweaker"); private static final String MIXIN_TWEAKER = "org.spongepowered.asm.launch.MixinTweaker"; - private static MethodHandle loadCoreMod; - private static Consumer addContainer; public OneConfigTweaker() { final List sourceFiles = getSourceFiles(); @@ -86,7 +88,7 @@ public OneConfigTweaker() { } @SuppressWarnings("unchecked") - private static void setupSourceFile(SourceFile sourceFile) throws Throwable { + private void setupSourceFile(SourceFile sourceFile) throws Exception { String path = sourceFile.path.toString(); // Forge will by default ignore a mod file if it contains a tweaker // So we need to remove ourselves from that exclusion list @@ -101,50 +103,75 @@ private static void setupSourceFile(SourceFile sourceFile) throws Throwable { // Mixin takes care of this as well, so we mustn't if it will. String coreMod = sourceFile.coreMod; if (coreMod != null && !sourceFile.mixin) { - ITweaker tweaker = loadCoreMod(Launch.classLoader, coreMod, sourceFile.path.toFile()); + Method loadCoreMod = CoreModManager.class.getDeclaredMethod("loadCoreMod", LaunchClassLoader.class, String.class, File.class); + loadCoreMod.setAccessible(true); + ITweaker tweaker = (ITweaker) loadCoreMod.invoke(null, Launch.classLoader, coreMod, sourceFile.path.toFile()); ((List) Launch.blackboard.get("Tweaks")).add(tweaker); } - // Mixin will only look at jar files which declare the MixinTweaker as their tweaker class, so we need - // to manually add our source files for inspection. - if (sourceFile.mixin) addContainer(sourceFile.path.toUri()); - } + // If they declared our tweaker but also want to use mixin, then we'll inject the mixin tweaker + // for them. + if (sourceFile.mixin) { + // Mixin will only look at jar files which declare the MixinTweaker as their tweaker class, so we need + // to manually add our source files for inspection. + try { + injectMixinTweaker(); - private static ITweaker loadCoreMod(LaunchClassLoader loader, String cls, File src) throws Throwable { - MethodHandle mh = loadCoreMod == null ? loadCoreMod = createLoadCoreMod() : loadCoreMod; - return (ITweaker) mh.invoke(loader, src, src); + Class MixinBootstrap = Class.forName("org.spongepowered.asm.launch.MixinBootstrap"); + Class MixinPlatformManager = Class.forName("org.spongepowered.asm.launch.platform.MixinPlatformManager"); + Object platformManager = MixinBootstrap.getDeclaredMethod("getPlatform").invoke(null); + Method addContainer; + Object arg; + try { + // Mixin 0.7 + addContainer = MixinPlatformManager.getDeclaredMethod("addContainer", URI.class); + arg = sourceFile.path.toUri(); + } catch (NoSuchMethodException ignored) { + // Mixin 0.8 + Class IContainerHandle = Class.forName("org.spongepowered.asm.launch.platform.container.IContainerHandle"); + Class ContainerHandleURI = Class.forName("org.spongepowered.asm.launch.platform.container.ContainerHandleURI"); + addContainer = MixinPlatformManager.getDeclaredMethod("addContainer", IContainerHandle); + arg = ContainerHandleURI.getDeclaredConstructor(URI.class).newInstance(sourceFile.path.toUri()); + } + addContainer.invoke(platformManager, arg); + } catch (Exception e) { + LOGGER.error("failed to inject mixin tweaker for {}", path, e); + } + } } - private static MethodHandle createLoadCoreMod() { - return MHUtils.getStaticMethodHandle(CoreModManager.class, "loadCoreMod", ITweaker.class, LaunchClassLoader.class, String.class, File.class).getOrThrow(); - } + private void injectMixinTweaker() throws ClassNotFoundException, IllegalAccessException, InstantiationException { + @SuppressWarnings("unchecked") + List tweakClasses = (List) Launch.blackboard.get("TweakClasses"); - private static void addContainer(URI uri) { - Consumer c = addContainer == null ? addContainer = createAddContainer() : addContainer; - c.accept(uri); - } + // If the MixinTweaker is already queued (because of another mod), then there's nothing we need to to + if (tweakClasses.contains(MIXIN_TWEAKER)) { + // Except we do need to initialize the MixinTweaker immediately so we can add containers + // for our mods. + // This is idempotent, so we can call it without adding to the tweaks list (and we must not add to + // it because the queued tweaker will already get added and there is nothing we can do about that). + initMixinTweaker(); + return; + } - @SuppressWarnings("unchecked") - private static Consumer createAddContainer() { - try { - Class MixinBootstrap = Class.forName("org.spongepowered.asm.launch.MixinBootstrap"); - Class MixinPlatformManager = Class.forName("org.spongepowered.asm.launch.platform.MixinPlatformManager"); - Object platformManager = MHUtils.invokeStatic(MixinBootstrap, "getPlatform"); - try { - // Mixin 0.7 - return MHUtils.getConsumerHandle(platformManager, "addContainer", URI.class).getOrThrow(); - } catch (Throwable ignored) { - // Mixin 0.8 - Class IContainerHandle = Class.forName("org.spongepowered.asm.launch.platform.container.IContainerHandle"); - Class ContainerHandleURI = Class.forName("org.spongepowered.asm.launch.platform.container.ContainerHandleURI"); - // as we can't refer to IContainerHandle, we have to use unchecked casts here. - Consumer addWrappedContainer = (Consumer) MHUtils.getConsumerHandle(MixinPlatformManager, "addContainer", IContainerHandle).getOrThrow(); - MethodHandle wrapper = MHUtils.getConstructorHandle(ContainerHandleURI, URI.class).getOrThrow(); - return it -> addWrappedContainer.accept(MHUtils.invokeCatching(wrapper, it).getOrThrow()); - } - } catch (Throwable t) { - throw new RuntimeException("Failed to crack addContainer method. mixin will not work!", t); + // If it is already booted, we're also good to go + if (Launch.blackboard.get("mixin.initialised") != null) { + return; } + + System.out.println("Injecting MixinTweaker from OneConfigTweaker"); + + // Otherwise, we need to take things into our own hands because the normal way to chainload a tweaker + // (by adding it to the TweakClasses list during injectIntoClassLoader) is too late for Mixin. + // Instead we instantiate the MixinTweaker on our own and add it to the current Tweaks list immediately. + @SuppressWarnings("unchecked") + List tweaks = (List) Launch.blackboard.get("Tweaks"); + tweaks.add(initMixinTweaker()); + } + + private ITweaker initMixinTweaker() throws ClassNotFoundException, IllegalAccessException, InstantiationException { + Launch.classLoader.addClassLoaderExclusion(MIXIN_TWEAKER.substring(0, MIXIN_TWEAKER.lastIndexOf('.'))); + return (ITweaker) Class.forName(MIXIN_TWEAKER, true, Launch.classLoader).newInstance(); } private static List getSourceFiles() { @@ -162,15 +189,17 @@ private static List getSourceFiles() { String tweakClass = null; String coreMod = null; boolean mixin = false; + boolean manualSourceFile = false; try (JarFile jar = new JarFile(file.toFile())) { if (jar.getManifest() != null) { Attributes attributes = jar.getManifest().getMainAttributes(); tweakClass = attributes.getValue("TweakClass"); coreMod = attributes.getValue("FMLCorePlugin"); mixin = attributes.getValue("MixinConfigs") != null; + manualSourceFile = attributes.getValue("OneConfigSourceFile") != null; // This is for when a mod uses a custom tweaker but still wants the Mixin tweaker to be injected } } - if (Objects.equals(tweakClass, "cc.polyfrost.oneconfigwrapper.OneConfigWrapper") || Objects.equals(tweakClass, "cc.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker")) { + if (Objects.equals(tweakClass, "org.polyfrost.oneconfig.loader.stage0.LaunchWrapperTweaker") || manualSourceFile) { sourceFiles.add(new SourceFile(file, coreMod, mixin)); } } catch (Exception e) { @@ -180,40 +209,6 @@ private static List getSourceFiles() { return sourceFiles; } - private static void injectMixinTweaker() throws ClassNotFoundException { - @SuppressWarnings("unchecked") - List tweakClasses = (List) Launch.blackboard.get("TweakClasses"); - - // If the MixinTweaker is already queued (because of another mod), then there's nothing we need to do - if (tweakClasses.contains(MIXIN_TWEAKER)) { - // Except we do need to initialize the MixinTweaker immediately so we can add containers - // for our mods. - // This is idempotent, so we can call it without adding to the tweaks list (and we must not add to - // it because the queued tweaker will already get added and there is nothing we can do about that). - initMixinTweaker(); - return; - } - - // If it is already booted, we're also good to go - if (Launch.blackboard.get("mixin.initialised") != null) { - return; - } - - LOGGER.info("Injecting MixinTweaker from OneConfigTweaker"); - - // Otherwise, we need to take things into our own hands because the normal way to chainload a tweaker - // (by adding it to the TweakClasses list during injectIntoClassLoader) is too late for Mixin. - // Instead we instantiate the MixinTweaker on our own and add it to the current Tweaks list immediately. - @SuppressWarnings("unchecked") - List tweaks = (List) Launch.blackboard.get("Tweaks"); - tweaks.add(initMixinTweaker()); - } - - private static ITweaker initMixinTweaker() throws ClassNotFoundException { - Launch.classLoader.addClassLoaderExclusion(MIXIN_TWEAKER.substring(0, MIXIN_TWEAKER.lastIndexOf('.'))); - return (ITweaker) MHUtils.instantiate(Class.forName(MIXIN_TWEAKER, true, Launch.classLoader), false).getOrThrow(); - } - /** * Taken from LWJGLTwoPointFive under The Unlicense * https://github.com/DJtheRedstoner/LWJGLTwoPointFive/blob/master/LICENSE/ @@ -221,9 +216,11 @@ private static ITweaker initMixinTweaker() throws ClassNotFoundException { @SuppressWarnings("unchecked") private static void removeLWJGLException() { try { - Set exceptions = (Set) MHUtils.getField(Launch.classLoader, "classLoaderExceptions").getOrThrow(); + Field f_exceptions = LaunchClassLoader.class.getDeclaredField("classLoaderExceptions"); + f_exceptions.setAccessible(true); + Set exceptions = (Set) f_exceptions.get(Launch.classLoader); exceptions.remove("org.lwjgl."); - } catch (Throwable e) { + } catch (Exception e) { throw new RuntimeException(e); } }