package com.austinv11.collectiveframework.minecraft.asm;
import com.austinv11.collectiveframework.dependencies.download.BinaryProvider;
import com.austinv11.collectiveframework.dependencies.download.FileType;
import com.austinv11.collectiveframework.dependencies.download.PlainTextProvider;
import com.austinv11.collectiveframework.minecraft.CollectiveFramework;
import com.austinv11.collectiveframework.utils.FileUtils;
import com.austinv11.collectiveframework.utils.StringUtils;
import com.austinv11.collectiveframework.utils.WebUtils;
import com.google.gson.Gson;
import cpw.mods.fml.common.FMLLog;
import cpw.mods.fml.relauncher.CoreModManager;
import cpw.mods.fml.relauncher.IFMLCallHook;
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
import net.minecraftforge.common.MinecraftForge;
import org.apache.logging.log4j.Level;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
//@IFMLLoadingPlugin.TransformerExclusions(value = {"com.austinv11.collectiveframework.asm"})
@IFMLLoadingPlugin.SortingIndex(Integer.MIN_VALUE) //Want mods installed as early as possible
public class CollectiveFrameworkEarlyTransformerPlugin implements IFMLLoadingPlugin, IFMLCallHook {
private static boolean didInject = false;
public CollectiveFrameworkEarlyTransformerPlugin() {
if (!didInject) {
try {
FMLLog.log("CollectiveFramework Early-Init", Level.INFO, "Injecting transformer...");
injectNewTransformer();
} catch (Exception e) {
FMLLog.log("CollectiveFramework Early-Init", Level.FATAL, "There was a problem injecting the CollectiveFramework ASM Transformer!");
e.printStackTrace();
}
}
}
private void injectNewTransformer() throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class e = Class.forName("cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper");
Constructor wrapperConstructor = e.getConstructor(new Class[]{String.class, IFMLLoadingPlugin.class, File.class, Integer.TYPE, String[].class});
Field loadPlugins = CoreModManager.class.getDeclaredField("loadPlugins");
wrapperConstructor.setAccessible(true);
loadPlugins.setAccessible(true);
((List)loadPlugins.get((Object)null)).add(wrapperConstructor.newInstance(new Object[]{"CollectiveFrameworkPlugin", new CollectiveFrameworkTransformerPlugin(), null, Integer.valueOf(Integer.MAX_VALUE), new String[0]}));
didInject = true;
}
@Override
public String[] getASMTransformerClass() {
return new String[0];
}
@Override
public String getModContainerClass() {
return "com.austinv11.collectiveframework.minecraft.asm.DummyContainer";
}
@Override
public String getSetupClass() {
return this.getClass().getName();
}
@Override
public void injectData(Map<String,Object> data) {
}
@Override
public String getAccessTransformerClass() {
return "com.austinv11.collectiveframework.minecraft.asm.CollectiveFrameworkAccessTransformer";
}
@Override
public Void call() throws Exception {
ModpackProvider provider = new ModpackProvider();
File xml = new File("modpack.xml");
if (xml.exists()) {
FMLLog.log("CollectiveFramework Early-Init", Level.INFO, "modpack.xml found! Installing modpack...");
FMLLog.log("CollectiveFramework Early-Init", Level.INFO, "Do NOT stop the client if it hangs!");
provider.installMods(provider.parseModpackXML(xml), provider.doOverwrite(xml));
FMLLog.log("CollectiveFramework Early-Init", Level.INFO, "Modpack installed! Restarting Minecraft is recommended (though not required)");
}
return null;
}
/**
* Pseudo-provider for modpack installation
*/
public static class ModpackProvider {
//Firefox UA Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0
private static String lastVersion;
private String version;
static {
File changelog = new File("Modpack.log");
if (!changelog.exists()) {
lastVersion = "NULL";
} else {
try {
List<String> log = FileUtils.readAll(changelog);
lastVersion = log.get(1);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Determines whether to overwrite based on a modpack.xml
* @param xmlFile The file to parse
* @return Whether to overwrite
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
*/
public boolean doOverwrite(File xmlFile) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(xmlFile);
doc.getDocumentElement().normalize();
return (doc.getDocumentElement().hasAttribute("overwrite") && Boolean.parseBoolean(doc.getDocumentElement().getAttribute("overwrite")))
|| (doc.getDocumentElement().hasAttribute("version") && !doc.getDocumentElement().hasAttribute("overwrite") && !getLastVersion().equals(doc.getDocumentElement().getAttribute("version")));
}
/**
* Gets the last used version of the modpack
* @return The version
*/
public String getLastVersion() {
return lastVersion;
}
/**
* Gets the current modpack version
* @return The version
*/
public String getVersion() {
return version;
}
/**
* Parses an xml file following the "Modpack Schema"
* @param xmlFile The file to parse
* @return The mods
* @throws ParserConfigurationException
* @throws IOException
* @throws SAXException
*/
public List<IModpackFile> parseModpackXML(File xmlFile) throws ParserConfigurationException, IOException, SAXException {
ArrayList<IModpackFile> mods = new ArrayList<IModpackFile>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(xmlFile);
doc.getDocumentElement().normalize();
if (doc.getDocumentElement().hasAttribute("version"))
version = doc.getDocumentElement().getAttribute("version");
else
version = "NULL";
//Curse mods
NodeList curseNodeList = doc.getElementsByTagName("cursemod");
CollectiveFramework.LOGGER.info(curseNodeList.getLength()+" Curse mod(s) found!");
for (int i = 0; i < curseNodeList.getLength(); i++) {
Node node = curseNodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getAttribute("id");
String slug = element.getElementsByTagName("slug").item(0).getTextContent();
String mcversion = element.getElementsByTagName("mcversion").getLength() > 0 ? element.getElementsByTagName("mcversion").item(0).getTextContent() : MinecraftForge.MC_VERSION;
ReleaseType releaseType = element.getElementsByTagName("releasetype").getLength() > 0 ? ReleaseType.fromString(element.getElementsByTagName("releasetype").item(0).getTextContent()) : ReleaseType.RELEASE;
int id = element.getElementsByTagName("id").getLength() > 0 ? Integer.valueOf(element.getElementsByTagName("id").item(0).getTextContent()) : -1;
String fileName = element.getElementsByTagName("name").getLength() > 0 ? element.getElementsByTagName("name").item(0).getTextContent() : "@REPLACE@";
mods.add(new CurseMod(name, slug, mcversion, releaseType, id, fileName));
}
}
//Adfly mods
NodeList adflyNodeList = doc.getElementsByTagName("adflymod");
CollectiveFramework.LOGGER.info(adflyNodeList.getLength()+" Adfly-type mod(s) found!");
for (int i = 0; i < adflyNodeList.getLength(); i++) {
Node node = adflyNodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getAttribute("id");
String link = element.getElementsByTagName("link").item(0).getTextContent();
String fileName = element.getElementsByTagName("name").item(0).getTextContent();
mods.add(new AdflyMod(name, link, fileName));
}
}
//Other mods
NodeList modNodeList = doc.getElementsByTagName("mod");
CollectiveFramework.LOGGER.info(modNodeList.getLength()+" Other mod(s) found!");
for (int i = 0; i < modNodeList.getLength(); i++) {
Node node = modNodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getAttribute("id");
String link = element.getElementsByTagName("link").item(0).getTextContent();
String fileName = element.getElementsByTagName("name").item(0).getTextContent();
mods.add(new OtherMod(name, link, fileName));
}
}
//Plaintext files
NodeList plainNodeList = doc.getElementsByTagName("plainfile");
CollectiveFramework.LOGGER.info(plainNodeList.getLength()+" Other plain file(s) found!");
for (int i = 0; i < plainNodeList.getLength(); i++) {
Node node = plainNodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getAttribute("id");
String link = element.getElementsByTagName("link").item(0).getTextContent();
String fileName = element.getElementsByTagName("path").item(0).getTextContent();
mods.add(new OtherFile(name, link, fileName, FileType.PLAIN_TEXT));
}
}
//Binary files
NodeList binaryNodeList = doc.getElementsByTagName("binaryfile");
CollectiveFramework.LOGGER.info(binaryNodeList.getLength()+" Other binary file(s) found!");
for (int i = 0; i < binaryNodeList.getLength(); i++) {
Node node = binaryNodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getAttribute("id");
String link = element.getElementsByTagName("link").item(0).getTextContent();
String fileName = element.getElementsByTagName("path").item(0).getTextContent();
mods.add(new OtherFile(name, link, fileName, FileType.BINARY));
}
}
//Zip files
NodeList zipNodeList = doc.getElementsByTagName("zipfile");
CollectiveFramework.LOGGER.info(zipNodeList.getLength()+" Zip file(s) found!");
for (int i = 0; i < zipNodeList.getLength(); i++) {
Node node = zipNodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element) node;
String name = element.getAttribute("id");
String link = element.getElementsByTagName("link").item(0).getTextContent();
String fileName = element.getElementsByTagName("path").item(0).getTextContent();
mods.add(new ZipFile(name, link, fileName));
}
}
return mods;
}
/**
* Installs mods from a {@link IModpackFile} list
* @param mods Mods to install
* @param overwrite Whether to override existing files under the same filename
*/
public void installMods(List<IModpackFile> mods, boolean overwrite) throws IOException {
for (IModpackFile mod : mods) {
CollectiveFramework.LOGGER.info("Installing "+mod.getType()+" '"+mod.getName()+"'...");
if (new File(formatPath(mod.getPath())).exists() && !overwrite)
CollectiveFramework.LOGGER.info("Skipping "+mod.getType()+" '"+mod.getName()+"', it already exists!");
boolean didDownload = mod.install();
if (didDownload)
CollectiveFramework.LOGGER.info("Installed "+mod.getType()+" '"+mod.getName()+"'!");
else
CollectiveFramework.LOGGER.warn("Failed to install "+mod.getType()+" '"+mod.getName()+"'!");
}
File changelog = new File("Modpack.log");
if (!changelog.exists()) {
changelog.createNewFile();
}
DateFormat format = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss");
FileUtils.safeWrite(changelog, format.format(new Date())+"\n"+getVersion());
}
protected static String formatPath(String path) {
if (path.contains("./") && path.startsWith("./"))
return path;
return "./"+path;
}
/**
* An interface to represent a file to be downloaded based on the modpack.xml
*/
public static interface IModpackFile {
public boolean install();
public String getType();
public String getPath();
public String getName();
}
/**
* An object representing a zip file
*/
public static class ZipFile implements IModpackFile {
public String name;
public String url;
public String fileName;
public ZipFile(String name, String url, String fileName) {
this.name = name;
this.url = url;
this.fileName = ModpackProvider.formatPath(fileName);
}
@Override
public boolean install() {
File temp = new File(getPath()+File.separator+"temp.zip");
boolean result = new BinaryProvider().downloadFile(url, temp.getPath());
if (!result)
return false;
try {
FileUtils.unzip(temp, new File(getPath()));
} catch (IOException e) {
e.printStackTrace();
}
temp.delete();
return true;
}
@Override
public String getType() {
return "zip";
}
@Override
public String getPath() {
return this.fileName;
}
@Override
public String getName() {
return name;
}
}
/**
* An object representing a non-mod file
*/
public static class OtherFile implements IModpackFile {
public String name;
public String url;
public String fileName;
public FileType type;
public OtherFile(String name, String url, String fileName, FileType type) {
this.name = name;
this.url = url;
this.fileName = ModpackProvider.formatPath(fileName);
this.type = type;
}
@Override
public boolean install() {
if (type == FileType.BINARY) {
return new BinaryProvider().downloadFile(url, getPath());
} else if (type == FileType.PLAIN_TEXT) {
return new PlainTextProvider().downloadFile(url, getPath());
}
return false;
}
@Override
public String getType() {
return "file";
}
@Override
public String getPath() {
return this.fileName;
}
@Override
public String getName() {
return name;
}
}
/**
* An object representing a mod not on curse
*/
public static class OtherMod implements IModpackFile {
public String name;
public String url;
public String fileName;
public OtherMod(String name, String url, String fileName) {
this.name = name;
this.url = url;
this.fileName = fileName;
}
@Override
public boolean install() {
return new BinaryProvider().downloadFile(url, getPath());
}
@Override
public String getType() {
return "mod";
}
@Override
public String getPath() {
return "./mods/"+this.fileName;
}
@Override
public String getName() {
return name;
}
}
/**
* An object representing a mod using adfly or similar
*/
public static class AdflyMod implements IModpackFile {
public String name;
public String url;
public String fileName;
public AdflyMod(String name, String url, String fileName) {
this.name = name;
this.url = url;
this.fileName = fileName;
}
@Override
public boolean install() {
CollectiveFramework.LOGGER.info("Redirecting you to "+url);
try {
Desktop.getDesktop().browse(new URI(url));
JFileChooser fileChooser = new JFileChooser();
fileChooser.setFileFilter(new FileNameExtensionFilter("Mod files", "jar", "zip"));
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
int result = fileChooser.showOpenDialog(null);
if (result == JFileChooser.APPROVE_OPTION) {
File selectedFile = fileChooser.getSelectedFile();
return selectedFile.renameTo(new File(getPath()));
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
@Override
public String getType() {
return "adfly mod";
}
@Override
public String getPath() {
return "./mods/"+this.fileName;
}
@Override
public String getName() {
return name;
}
}
/**
* An object representing a mod on curse
*/
public static class CurseMod implements IModpackFile {
public String name;
public String slug;
public String mcversion;
public ReleaseType releasetype;
public int id;
public String fileName;
public JSONCurseResponse curseResponse;
public String url;
public CurseMod(String name, String slug, String mcversion, ReleaseType releaseType, int id, String fileName) {
this.name = name;
this.slug = slug;
this.mcversion = mcversion;
this.releasetype = releaseType;
this.id = id;
this.fileName = fileName;
parseCurseJson();
if (id != -1) {
url = "http://minecraft.curseforge.com/mc-mods/"+slug+"/files/"+id+"/download";
} else {
for (JSONCurseResponse.JSONDownload download : curseResponse.versions.get(mcversion)) {
if (download.version.equalsIgnoreCase(mcversion)) {
url = "http://minecraft.curseforge.com/mc-mods/"+slug+"/files/"+download.id+"/download";
break;
}
}
}
if (fileName.equals("@REPLACE@")) {
fileName = curseResponse.files.get(id+"").name;
}
}
public void parseCurseJson() {
Gson gson = new Gson();
try {
curseResponse = gson.fromJson(StringUtils.stringFromList(WebUtils.readURL("http://widget.mcf.li/mc-mods/minecraft/"+slug+".json")), JSONCurseResponse.class);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public boolean install() {
return new BinaryProvider().downloadFile(this.url, getPath());
}
@Override
public String getType() {
return "curse mod";
}
@Override
public String getPath() {
return "./mods/"+this.fileName;
}
@Override
public String getName() {
return name;
}
}
/**
* Type of release the mod is classified as in curse
*/
public static enum ReleaseType {
RELEASE,BETA,ALPHA;
public static ReleaseType fromString(String s) {
if (s != null) {
if (s.equalsIgnoreCase("beta"))
return BETA;
if (s.equalsIgnoreCase("alpha"))
return ALPHA;
}
return RELEASE;
}
}
public static class JSONCurseResponse {
String name;
String game;
String category;
String url;
String thumbnail;
String[] authors;
JSONDownloads downloads;
int favorites;
int likes;
String updated_at;
String created_at;
String project_url;
String release_type;
String license;
JSONDownload download;
Map<String, JSONDownload[]> versions;
Map<String,JSONDownload> files;
public static class JSONDownloads {
int monthly;
int total;
}
public static class JSONDownload {
int id;
String url;
String name;
String type;
String version;
String downloads;
String created_at;
}
}
}
}