/* * 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.platform; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; //import org.spongepowered.asm.launch.MixinBootstrap.Delegate; import org.spongepowered.asm.launch.MixinTweaker; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.MixinEnvironment.CompatibilityLevel; import org.spongepowered.asm.mixin.MixinEnvironment.Phase; import org.spongepowered.asm.mixin.Mixins; //import com.google.common.collect.ImmutableList; import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.LaunchClassLoader; /** * Handler for platform-specific behaviour required in different mixin * environments. */ public class MixinPlatformManager { private static final String MFATT_TWEAKER = "TweakClass"; private static final String DEFAULT_MAIN_CLASS = "net.minecraft.client.main.Main"; /** * Make with the logging already */ private static final Logger logger = LogManager.getLogger("mixin"); /** * Bootstrap delegate */ // private final Delegate delegate; /** * Tweak containers */ private final Map<URI, MixinContainer> containers = new LinkedHashMap<URI, MixinContainer>(); /** * Container for this tweaker */ private MixinContainer primaryContainer; /** * Tracks whether {@link #acceptOptions} was called yet, if true, causes new * agents to be <tt>prepare</tt>d immediately */ private boolean prepared = false; /** * Tracks whether {@link #injectIntoClassLoader} was called yet */ private boolean injected; public MixinPlatformManager() { //Delegate delegate) { // this.delegate = delegate; MixinPlatformManager.logger.debug("Initialising Mixin Platform Manager"); // Add agents for the tweak container URI uri = null; try { uri = this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI(); if (uri != null) { MixinPlatformManager.logger.debug("Mixin platform: primary container is {}", uri); this.primaryContainer = this.addContainer(uri); } } catch (URISyntaxException ex) { ex.printStackTrace(); } // Do an early scan to ensure preinit mixins are discovered this.scanClasspath(); } /** * Get the phase provider classes from the primary container */ public Collection<String> getPhaseProviderClasses() { Collection<String> phaseProviders = this.primaryContainer.getPhaseProviders(); if (phaseProviders != null) { return Collections.<String>unmodifiableCollection(phaseProviders); } return Collections.<String>emptyList(); } /** * Add a new URI to this platform and return the new container (or an * existing container if the URI was previously registered) * * @param uri URI to add * @return container for specified URI */ public final MixinContainer addContainer(URI uri) { MixinContainer existingContainer = this.containers.get(uri); if (existingContainer != null) { return existingContainer; } MixinPlatformManager.logger.debug("Adding mixin platform agents for container {}", uri); MixinContainer container = new MixinContainer(this, uri); this.containers.put(uri, container); if (this.prepared) { container.prepare(); } return container; } /** * Prepare all containers in this platform * * @param args command-line arguments from tweaker */ public final void prepare(List<String> args) { this.prepared = true; for (MixinContainer container : this.containers.values()) { container.prepare(); } if (args != null) { this.parseArgs(args); } else { String argv = System.getProperty("sun.java.command"); if (argv != null) { this.parseArgs(Arrays.asList(argv.split(" "))); } } } /** * Read and parse command-line arguments * * @param args command-line arguments */ private void parseArgs(List<String> args) { boolean captureNext = false; for (String arg : args) { if (captureNext) { this.addConfig(arg); } captureNext = "--mixin".equals(arg); } } /** * Initialise the primary container and dispatch injectIntoClassLoader to * all containers * * @param classLoader classloader to inject into */ public final void injectIntoClassLoader(LaunchClassLoader classLoader) { if (this.injected) { return; } this.injected = true; if (this.primaryContainer != null) { this.primaryContainer.initPrimaryContainer(); } this.scanClasspath(); MixinPlatformManager.logger.debug("injectIntoClassLoader running with {} agents", this.containers.size()); for (MixinContainer container : this.containers.values()) { try { container.injectIntoClassLoader(classLoader); } catch (Exception ex) { ex.printStackTrace(); } } } /** * Scan the classpath for mixin containers (containers which declare the * mixin tweaker in their manifest) and add agents for them */ private void scanClasspath() { for (URL url : Launch.classLoader.getSources()) { try { URI uri = url.toURI(); if (this.containers.containsKey(uri)) { continue; } MixinPlatformManager.logger.debug("Scanning {} for mixin tweaker", uri); if (!"file".equals(uri.getScheme()) || !new File(uri).exists()) { continue; } MainAttributes attributes = MainAttributes.of(uri); String tweaker = attributes.get(MixinPlatformManager.MFATT_TWEAKER); if (MixinTweaker.class.getName().equals(tweaker)) { MixinPlatformManager.logger.debug("{} contains a mixin tweaker, adding agents", uri); this.addContainer(uri); } } catch (Exception ex) { ex.printStackTrace(); } } } /** * Queries all containers for launch target, returns null if no containers * specify a launch target */ public String getLaunchTarget() { for (MixinContainer container : this.containers.values()) { String mainClass = container.getLaunchTarget(); if (mainClass != null) { return mainClass; } } return MixinPlatformManager.DEFAULT_MAIN_CLASS; } /** * Set the desired compatibility level as a string, used by agents to set * compatibility level from jar manifest * * @param level compatibility level as a string */ @SuppressWarnings("deprecation") final void setCompatibilityLevel(String level) { try { CompatibilityLevel value = CompatibilityLevel.valueOf(level.toUpperCase()); MixinPlatformManager.logger.debug("Setting mixin compatibility level: {}", value); MixinEnvironment.setCompatibilityLevel(value); } catch (IllegalArgumentException ex) { MixinPlatformManager.logger.warn("Invalid compatibility level specified: {}", level); } } /** * Add a config from a jar manifest source or the command line. Supports * config declarations in the form <tt>filename.json</tt> or alternatively * <tt>filename.json@PHASE</tt> where <tt>PHASE</tt> is a * case-sensitive string token representing an environment phase. * * @param config config resource name, does not require a leading / */ @SuppressWarnings("deprecation") final void addConfig(String config) { if (config.endsWith(".json")) { MixinPlatformManager.logger.debug("Registering mixin config: {}", config); Mixins.addConfiguration(config); } else if (config.contains(".json@")) { int pos = config.indexOf(".json@"); String phaseName = config.substring(pos + 6); config = config.substring(0, pos + 5); Phase phase = Phase.forName(phaseName); if (phase != null) { MixinPlatformManager.logger.warn("Setting config phase via manifest is deprecated: {}. Specify target in config instead", config); MixinPlatformManager.logger.debug("Registering mixin config: {}", config); MixinEnvironment.getEnvironment(phase).addConfiguration(config); } } } /** * Add a token provider class from a jar manifest source. Supports either * bare class names in the form <tt>blah.package.ClassName</tt> or * alternatively <tt>blah.package.ClassName@PHASE</tt> where * <tt>PHASE</tt> is a case-sensitive string token representing an * environment phase name. * * @param provider provider class name, optionally suffixed with @PHASE */ final void addTokenProvider(String provider) { if (provider.contains("@")) { String[] parts = provider.split("@", 2); Phase phase = Phase.forName(parts[1]); if (phase != null) { MixinPlatformManager.logger.debug("Registering token provider class: {}", parts[0]); MixinEnvironment.getEnvironment(phase).registerTokenProviderClass(parts[0]); } return; } MixinEnvironment.getDefaultEnvironment().registerTokenProviderClass(provider); } }