net.minecrell.quartz.launch.QuartzTweaker.java Source code

Java tutorial

Introduction

Here is the source code for net.minecrell.quartz.launch.QuartzTweaker.java

Source

/*
 * Quartz
 * Copyright (c) 2015, Minecrell <https://github.com/Minecrell>
 *
 * Based on Sponge and SpongeAPI, licensed under the MIT License (MIT).
 * Copyright (c) SpongePowered.org <http://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 net.minecrell.quartz.launch;

import static com.google.common.base.Preconditions.checkState;
import static org.apache.commons.lang3.ArrayUtils.toArray;

import com.google.common.base.Throwables;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import net.minecraft.launchwrapper.ITweaker;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecrell.quartz.launch.console.QuartzConsole;
import net.minecrell.quartz.launch.mappings.Mappings;
import net.minecrell.quartz.launch.mappings.MappingsLoader;
import net.minecrell.quartz.launch.mappings.MappingsParser;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.spongepowered.asm.launch.MixinBootstrap;
import org.spongepowered.asm.mixin.MixinEnvironment;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;

public final class QuartzTweaker implements ITweaker {

    public static final String MAIN = "net.minecraft.server.MinecraftServer";
    public static final String MAIN_PATH = "net/minecraft/server/MinecraftServer";
    public static final String MAIN_CLASS = MAIN_PATH + ".class";

    private static final Logger logger = LogManager.getLogger();

    private static Path serverJar;

    public static Path getServerJar() {
        return serverJar;
    }

    private boolean gui;

    @Override
    public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) {
        Path gamePath = gameDir != null ? gameDir.toPath() : Paths.get("");
        serverJar = gamePath.resolve("bin").resolve(QuartzMain.MINECRAFT_SERVER_LOCAL);
        checkState(Files.exists(serverJar), "Failed to load server JAR");

        boolean jline = true;

        if (args != null && !args.isEmpty()) {
            OptionParser parser = new OptionParser();
            parser.allowsUnrecognizedOptions();
            parser.accepts("gui");
            parser.accepts("nojline"); // TODO: Naming

            OptionSet options = parser.parse(args.toArray(new String[args.size()]));
            gui = options.has("gui");
            jline = !options.has("nojline");
        }

        QuartzLaunch.initialize(gamePath);

        // Initialize jline
        logger.debug("Initializing JLine...");
        try {
            QuartzConsole.initialize(jline);
        } catch (Throwable t) {
            logger.error("Failed to initialize fancy console", t);
            try {
                QuartzConsole.initializeFallback();
            } catch (IOException e) {
                throw new RuntimeException("Failed to initialize terminal", e);
            }
        }

        QuartzConsole.start();
    }

    @Override
    public void injectIntoClassLoader(LaunchClassLoader loader) {
        try {
            logger.info("Initializing Quartz...");

            byte[] mainClass = loader.getClassBytes(MAIN);

            // Check if we're running in development environment
            if (mainClass != null && MappingsParser.isMappingsClass(mainClass)) {
                // We need to replace the main class because it clashes with our mapping
                logger.debug("Enabling main class transformer...");
                loader.registerTransformer("net.minecrell.quartz.launch.transformers.MainClassTransformer");
            } else {
                loader.clearNegativeEntries(Collections.singleton(MAIN_CLASS));
            }

            // Load Minecraft Server
            loader.addURL(serverJar.toUri().toURL());

            // Would rather not load these through Launchwrapper as they use native dependencies
            loader.addClassLoaderExclusion("com.sun.");
            loader.addClassLoaderExclusion("oshi.");
            loader.addClassLoaderExclusion("io.netty.");

            // Console
            loader.addClassLoaderExclusion("jline.");
            loader.addClassLoaderExclusion("org.fusesource.");

            // Some libraries shouldn't get transformed, don't even give the chance for that
            loader.addTransformerExclusion("joptsimple.");

            // Minecraft Server libraries
            loader.addTransformerExclusion("com.google.gson.");
            loader.addTransformerExclusion("org.apache.commons.codec.");
            loader.addTransformerExclusion("org.apache.commons.io.");
            loader.addTransformerExclusion("org.apache.commons.lang3.");

            // SpongeAPI
            loader.addTransformerExclusion("com.flowpowered.math.");
            loader.addTransformerExclusion("org.slf4j.");

            // Guice
            loader.addTransformerExclusion("com.google.inject.");
            loader.addTransformerExclusion("org.aopalliance.");

            // configurate
            loader.addTransformerExclusion("ninja.leaping.configurate.");
            loader.addTransformerExclusion("com.googlecode.concurrentlinkedhashmap.");
            loader.addTransformerExclusion("com.typesafe.config.");

            // Mixins
            loader.addClassLoaderExclusion("org.spongepowered.tools.");
            loader.addTransformerExclusion("net.minecrell.quartz.mixin.");

            // The server GUI won't work if we don't exclude this: log4j2 wants to have this in the same classloader
            loader.addClassLoaderExclusion("com.mojang.util.QueueLogAppender");

            logger.debug("Initializing Mappings...");
            Mappings mappings = MappingsLoader.load(logger);

            logger.debug("Class mappings: {}", mappings.getClasses());
            logger.debug("Method mappings: {}", mappings.getMethods());
            logger.debug("Field mappings: {}", mappings.getFields());
            logger.debug("Class constructors: {}", mappings.getConstructors());
            logger.debug("Access mappings: {}", mappings.getAccessMappings());

            Launch.blackboard.put("quartz.mappings", mappings);
            loader.registerTransformer("net.minecrell.quartz.launch.transformers.DeobfuscationTransformer");

            if (!mappings.getConstructors().isEmpty()) {
                logger.debug("Enabling constructor transformer");
                loader.registerTransformer("net.minecrell.quartz.launch.transformers.ConstructorTransformer");
            }

            if (!mappings.getAccessMappings().isEmpty()) {
                logger.debug("Enabling access transformer...");
                loader.registerTransformer("net.minecrell.quartz.launch.transformers.AccessTransformer");
            }

            logger.debug("Initializing Mixin environment...");
            MixinBootstrap.init();
            MixinEnvironment env = MixinEnvironment.getCurrentEnvironment();
            env.addConfiguration("mixins.quartz.json");
            env.setSide(MixinEnvironment.Side.SERVER);
            loader.registerTransformer(MixinBootstrap.TRANSFORMER_CLASS);

            logger.info("Starting Minecraft server...");
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }
    }

    @Override
    public String getLaunchTarget() {
        return MAIN;
    }

    @Override
    public String[] getLaunchArguments() {
        return gui ? ArrayUtils.EMPTY_STRING_ARRAY : toArray("nogui");
    }

}