package net.minecraftforge.gradle;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import joptsimple.NonOptionArgumentSpec;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import net.minecraft.launchwrapper.ITweaker;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
public abstract class GradleStartCommon
{
protected static Logger LOGGER = LogManager.getLogger("GradleStart");
private static final String NO_CORE_SEARCH = "noCoreSearch";
private Map<String, String> argMap = Maps.newHashMap();
private List<String> extras = Lists.newArrayList();
protected abstract void setDefaultArguments(Map<String, String> argMap);
protected abstract void preLaunch(Map<String, String> argMap, List<String> extras);
protected abstract String getBounceClass();
protected abstract String getTweakClass();
protected void launch(String[] args) throws Throwable
{
// set defaults!
setDefaultArguments(argMap);
// parse stuff
parseArgs(args);
// now send it back for prelaunch
preLaunch(argMap, extras);
// because its the dev env.
System.setProperty("fml.ignoreInvalidMinecraftCertificates", "true"); // cant hurt. set it now.
// coremod searching.
if (argMap.get(NO_CORE_SEARCH) == null)
searchCoremods();
else
LOGGER.info("GradleStart coremod searching disabled!");
// now the actual launch args.
args = getArgs();
// clear it out
argMap = null;
extras = null;
// launch.
System.gc();
String bounce = getBounceClass(); // marginally faster. And we need the launch wrapper anyways.
if (bounce.endsWith("launchwrapper.Launch"))
Launch.main(args);
else
Class.forName(getBounceClass()).getDeclaredMethod("main", String[].class).invoke(null, new Object[] { args });
}
private String[] getArgs()
{
ArrayList<String> list = new ArrayList<String>(22);
for (Map.Entry<String, String> e : argMap.entrySet())
{
String val = e.getValue();
if (!Strings.isNullOrEmpty(val))
{
list.add("--" + e.getKey());
list.add(val);
}
}
// grab tweakClass
if (!Strings.isNullOrEmpty(getTweakClass()))
{
list.add("--tweakClass");
list.add(getTweakClass());
}
if (extras != null)
{
list.addAll(extras);
}
String[] out = list.toArray(new String[0]);
// final logging.
StringBuilder b = new StringBuilder();
b.append('[');
for (int x = 0; x < out.length; x++)
{
b.append(out[x]).append(", ");
if ("--accessToken".equalsIgnoreCase(out[x]))
{
b.append("{REDACTED}, ");
x++;
}
}
b.replace(b.length() - 2, b.length(), "");
b.append(']');
GradleStartCommon.LOGGER.info("Running with arguments: " + b.toString());
return out;
}
private void parseArgs(String[] args)
{
final OptionParser parser = new OptionParser();
parser.allowsUnrecognizedOptions();
for (String key : argMap.keySet())
{
parser.accepts(key).withRequiredArg().ofType(String.class);
}
// accept the noCoreSearch thing
parser.accepts(NO_CORE_SEARCH);
final NonOptionArgumentSpec<String> nonOption = parser.nonOptions();
final OptionSet options = parser.parse(args);
for (String key : argMap.keySet())
{
if (options.hasArgument(key))
{
String value = (String)options.valueOf(key);
argMap.put(key, value);
if (!"password".equalsIgnoreCase(key))
LOGGER.info(key + ": " + value);
}
}
if (options.has(NO_CORE_SEARCH))
argMap.put(NO_CORE_SEARCH, "");
extras = Lists.newArrayList(nonOption.values(options));
LOGGER.info("Extra: " + extras);
}
/* ----------- REFLECTION HELPER --------- */
private static final String MC_VERSION = "1.7.10";
private static final String FML_PACK_OLD = "cpw.mods";
private static final String FML_PACK_NEW = "net.minecraftforge";
@SuppressWarnings("rawtypes")
protected static Class getFmlClass(String classname) throws ClassNotFoundException
{
if (!classname.startsWith("fml")) // dummy check myself
throw new IllegalArgumentException("invalid FML classname");
if (MC_VERSION.startsWith("1.7"))
classname = FML_PACK_OLD + "." + classname;
else
classname = FML_PACK_NEW + "." + classname;
return Class.forName(classname);
}
/* ----------- COREMOD AND AT HACK --------- */
// coremod hack
private static final String COREMOD_VAR = "fml.coreMods.load";
private static final String COREMOD_MF = "FMLCorePlugin";
// AT hack
private static final String AT_LOADER_CLASS = "fml.common.asm.transformers.ModAccessTransformer";
private static final String AT_LOADER_METHOD = "addJar";
private static final Map<String, File> coreMap = Maps.newHashMap();
@SuppressWarnings("unchecked")
private void searchCoremods() throws Exception
{
// intialize AT hack Method
Method atRegistrar = null;
try{
atRegistrar = getFmlClass(AT_LOADER_CLASS).getDeclaredMethod(AT_LOADER_METHOD, JarFile.class);
}
catch(Throwable t) { }
for (URL url : ((URLClassLoader) GradleStartCommon.class.getClassLoader()).getURLs())
{
if (!url.getProtocol().startsWith("file")) // because file urls start with file://
continue; // this isnt a file
File coreMod = new File(url.getFile());
Manifest manifest = null;
if (!coreMod.exists())
continue;
if (coreMod.isDirectory())
{
File manifestMF = new File(coreMod, "META-INF/MANIFEST.MF");
if (manifestMF.exists())
{
FileInputStream stream = new FileInputStream(manifestMF);
manifest = new Manifest(stream);
stream.close();
}
}
else if (coreMod.getName().endsWith("jar")) // its a jar
{
JarFile jar = new JarFile(coreMod);
manifest = jar.getManifest();
if (atRegistrar != null && manifest != null) atRegistrar.invoke(null, jar);
jar.close();
}
// we got the manifest? use it.
if (manifest != null)
{
String clazz = manifest.getMainAttributes().getValue(COREMOD_MF);
if (!Strings.isNullOrEmpty(clazz))
{
LOGGER.info("Found and added coremod: "+clazz);
coreMap.put(clazz, coreMod);
}
}
}
// set property.
Set<String> coremodsSet = Sets.newHashSet();
if (!Strings.isNullOrEmpty(System.getProperty(COREMOD_VAR)))
coremodsSet.addAll(Splitter.on(',').splitToList(System.getProperty(COREMOD_VAR)));
coremodsSet.addAll(coreMap.keySet());
System.setProperty(COREMOD_VAR, Joiner.on(',').join(coremodsSet));
// ok.. tweaker hack now.
if (!Strings.isNullOrEmpty(getTweakClass()))
{
extras.add("--tweakClass");
extras.add(GradleStartTweaker.class.getName());
}
}
/* ----------- CUSTOM TWEAKER FOR COREMOD HACK --------- */
public static final class GradleStartTweaker implements ITweaker
{
@Override
public void acceptOptions(List<String> args, File gameDir, File assetsDir, String profile) { }
@SuppressWarnings("unchecked")
@Override
public void injectIntoClassLoader(LaunchClassLoader classLoader)
{
try
{
Field coreModList = getFmlClass("fml.relauncher.CoreModManager").getDeclaredField("loadPlugins");
coreModList.setAccessible(true);
// grab constructor.
Class<ITweaker> clazz = (Class<ITweaker>) getFmlClass("fml.relauncher.CoreModManager$FMLPluginWrapper");
Constructor<ITweaker> construct = (Constructor<ITweaker>) clazz.getConstructors()[0];
construct.setAccessible(true);
Field[] fields = clazz.getDeclaredFields();
Field pluginField = fields[1]; // 1
Field fileField = fields[3]; // 3
Field listField = fields[2]; // 2
Field.setAccessible(clazz.getConstructors(), true);
Field.setAccessible(fields, true);
List<ITweaker> oldList = (List<ITweaker>) coreModList.get(null);
for (int i = 0; i < oldList.size(); i++)
{
ITweaker tweaker = oldList.get(i);
if (clazz.isInstance(tweaker))
{
Object coreMod = pluginField.get(tweaker);
Object oldFile = fileField.get(tweaker);
File newFile = coreMap.get(coreMod.getClass().getCanonicalName());
LOGGER.info("Injecting location in coremod {}", coreMod.getClass().getCanonicalName());
if (newFile != null && oldFile == null)
{
// build new tweaker.
oldList.set(i, construct.newInstance(new Object[] {
(String)fields[0].get(tweaker), // name
coreMod, // coremod
newFile, // location
fields[4].getInt(tweaker), // sort index?
((List<String>)listField.get(tweaker)).toArray(new String[0])
}));
}
}
}
}
catch (Throwable t)
{
LOGGER.error("Something went wrong with the claslaoding. No biggy.");
t.printStackTrace();
}
}
@Override
public String getLaunchTarget()
{
// if it gets here... something went terribly wrong..
return null;
}
@Override
public String[] getLaunchArguments()
{
// if it gets here... something went terribly wrong.
return new String[0];
}
}
}