/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.asm.launch; import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.spongepowered.asm.launch.platform.MixinPlatformManager; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.MixinEnvironment.Phase; import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.LaunchClassLoader; /** * Bootstaps the mixin subsystem. This class acts as a bridge between the mixin * subsystem and the tweaker or coremod which is boostrapping it. Without this * class, a coremod may cause classload of MixinEnvironment in the * LaunchClassLoader before we have a chance to exclude it. By placing the main * bootstap logic here we avoid the need for consumers to add the classloader * exclusion themselves. * * <p>In development, where (because of the classloader environment at dev time) * it is safe to let a coremod initialise the mixin subsystem, we can perform * initialisation all in one go using the {@link #init} method and everything is * fine. However in production the tweaker must be used and the situation is a * little more delicate.</p> * * <p>In an ideal world, the mixin tweaker would initialise the environment in * its constructor and that would be the end of the story. However we also need * to register the additional tweaker for environment to detect the transition * from pre-init to default and we cannot do this within the tweaker constructor * witout triggering a ConcurrentModificationException in the tweaker list. To * work around this we register the secondary tweaker from within the mixin * tweaker's acceptOptions method instead.</p> */ public abstract class MixinBootstrap { /** * Subsystem version */ public static final String VERSION = "0.6.9"; // Consts private static final String LAUNCH_PACKAGE = "org.spongepowered.asm.launch."; private static final String MIXIN_PACKAGE = "org.spongepowered.asm.mixin."; private static final String MIXIN_UTIL_PACKAGE = "org.spongepowered.asm.util."; private static final String ASM_PACKAGE = "org.spongepowered.asm.lib."; private static final String TRANSFORMER_PROXY_CLASS = MixinBootstrap.MIXIN_PACKAGE + "transformer.MixinTransformer$Proxy"; /** * Log all the things */ private static final Logger logger = LogManager.getLogger("mixin"); // These are Klass local, with luck this shouldn't be a problem private static boolean initialised = false; private static boolean initState = true; // Static initialiser, add classloader exclusions as early as possible static { // The important ones Launch.classLoader.addClassLoaderExclusion(MixinBootstrap.ASM_PACKAGE); Launch.classLoader.addClassLoaderExclusion(MixinBootstrap.MIXIN_PACKAGE); Launch.classLoader.addClassLoaderExclusion(MixinBootstrap.MIXIN_UTIL_PACKAGE); // Only needed in dev, in production this would be handled by the tweaker Launch.classLoader.addClassLoaderExclusion(MixinBootstrap.LAUNCH_PACKAGE); } /** * Platform manager instance */ private static MixinPlatformManager platform; private MixinBootstrap() {} /** * Register a new proxy transformer */ public static void addProxy() { Launch.classLoader.registerTransformer(MixinBootstrap.TRANSFORMER_PROXY_CLASS); } /** * Get the platform manager */ public static MixinPlatformManager getPlatform() { if (MixinBootstrap.platform == null) { MixinBootstrap.platform = new MixinPlatformManager(); } return MixinBootstrap.platform; } /** * Initialise the mixin subsystem */ public static void init() { if (!MixinBootstrap.start()) { return; } MixinBootstrap.doInit(null); } /** * Phase 1 of mixin initialisation */ static boolean start() { if (MixinBootstrap.isSubsystemRegistered()) { if (!MixinBootstrap.checkSubsystemVersion()) { throw new MixinInitialisationError("Mixin subsystem version " + MixinBootstrap.getActiveSubsystemVersion() + " was already initialised. Cannot bootstrap version " + MixinBootstrap.VERSION); } return false; } MixinBootstrap.registerSubsystem(MixinBootstrap.VERSION); if (!MixinBootstrap.initialised) { MixinBootstrap.initialised = true; String command = System.getProperty("sun.java.command"); if (command != null && command.contains("GradleStart")) { System.setProperty("mixin.env.disableRefMap", "true"); } if (MixinBootstrap.findInStackTrace(Launch.class.getName(), "launch") > 132) { MixinBootstrap.logger.error("Initialising mixin subsystem after game pre-init phase! Some mixins may be skipped."); MixinEnvironment.init(Phase.DEFAULT); MixinBootstrap.getPlatform().prepare(null); MixinBootstrap.initState = false; } else { MixinEnvironment.init(Phase.PREINIT); } MixinBootstrap.addProxy(); } MixinBootstrap.getPlatform(); return true; } /** * Phase 2 of mixin initialisation, initialise the phases */ static void doInit(List<String> args) { if (!MixinBootstrap.initialised) { if (MixinBootstrap.isSubsystemRegistered()) { MixinBootstrap.logger.warn("Multiple Mixin containers present, init suppressed for " + MixinBootstrap.VERSION); return; } throw new IllegalStateException("MixinBootstrap.doInit() called before MixinBootstrap.start()"); } MixinBootstrap.getPlatform().getPhaseProviderClasses(); // for (String platformProviderClass : MixinBootstrap.getPlatform().getPhaseProviderClasses()) { // System.err.printf("Registering %s\n", platformProviderClass); // MixinEnvironment.registerPhaseProvider(platformProviderClass); // } if (MixinBootstrap.initState) { MixinBootstrap.getPlatform().prepare(args); if (MixinBootstrap.findInStackTrace(Launch.class.getName(), "launch") < 4) { MixinBootstrap.logger.warn("MixinBootstrap.doInit() called during a tweak constructor. Expect CoModificationException in 5.. 4.."); } List<String> tweakClasses = Blackboard.<List<String>>get(Blackboard.Keys.TWEAKCLASSES); if (tweakClasses != null) { tweakClasses.add(MixinEnvironment.class.getName() + "$EnvironmentStateTweaker"); } } } static void injectIntoClassLoader(LaunchClassLoader classLoader) { MixinBootstrap.getPlatform().injectIntoClassLoader(classLoader); } private static boolean isSubsystemRegistered() { return Blackboard.<Object>get(Blackboard.Keys.INIT) != null; } private static boolean checkSubsystemVersion() { return MixinBootstrap.VERSION.equals(MixinBootstrap.getActiveSubsystemVersion()); } private static Object getActiveSubsystemVersion() { Object version = Blackboard.get(Blackboard.Keys.INIT); return version != null ? version : ""; } private static void registerSubsystem(String version) { Blackboard.put(Blackboard.Keys.INIT, version); } private static int findInStackTrace(String className, String methodName) { Thread currentThread = Thread.currentThread(); if (!"main".equals(currentThread.getName())) { return 0; } StackTraceElement[] stackTrace = currentThread.getStackTrace(); for (StackTraceElement s : stackTrace) { if (className.equals(s.getClassName()) && methodName.equals(s.getMethodName())) { return s.getLineNumber(); } } return 0; } }