/*
* 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;
}
}