Merge branch 'fg2'

This commit is contained in:
cpw committed 2015-08-20 12:27:20 -07:00
commit b211aca5ce
474 files changed
+2011 -54094

No files matched your search

@@ -38,6 +38,6 @@ public class FMLForgePlugin implements IFMLLoadingPlugin
@Override
public String getAccessTransformerClass()
{
return "net.minecraftforge.transformers.ForgeAccessTransformer";
return null;
}
}
@@ -26,7 +26,10 @@ public class ForgeVersion
public static final int revisionVersion = 3;
//This number is incremented every time Jenkins builds Forge, and never reset. Should always be 0 in the repo code.
public static final int buildVersion = 0;
// This is the minecraft version we're building for - used in various places in Forge/FML code
public static final String mcVersion = "1.8";
// This is the MCP data version we're using
public static final String mcpVersion = "9.10";
private static Status status = PENDING;
private static String target = null;
@@ -0,0 +1,68 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraftforge.fml.common.EnhancedRuntimeException;
import net.minecraftforge.fml.common.IFMLHandledException;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
/**
* If a mod throws this exception during loading, it will be called back to render
* the error screen through the methods below. This error will not be cleared, and will
* not allow the game to carry on, but might be useful if your mod wishes to report
* a fatal configuration error in a pretty way.
*
* Throw this through a proxy. It won't work on the dedicated server environment.
* @author cpw
*
*/
@SideOnly(Side.CLIENT)
public abstract class CustomModLoadingErrorDisplayException extends EnhancedRuntimeException implements IFMLHandledException
{
public CustomModLoadingErrorDisplayException() {
}
public CustomModLoadingErrorDisplayException(String message, Throwable cause)
{
super(message, cause);
}
private static final long serialVersionUID = 1L;
/**
* Called after the GUI is inited by the parent code. You can do extra stuff here, maybe?
*
* @param errorScreen The error screen we're painting
* @param fontRenderer A font renderer for you
*/
public abstract void initGui(GuiErrorScreen errorScreen, FontRenderer fontRenderer);
/**
* Draw your error to the screen.
*
* <br/><em>Warning: Minecraft is in a deep error state.</em> <strong>All</strong> it can do is stop.
* Do not try and do anything involving complex user interaction here.
*
* @param errorScreen The error screen to draw to
* @param fontRenderer A font renderer for you
* @param mouseRelX Mouse X
* @param mouseRelY Mouse Y
* @param tickTime tick time
*/
public abstract void drawScreen(GuiErrorScreen errorScreen, FontRenderer fontRenderer, int mouseRelX, int mouseRelY, float tickTime);
@Override public void printStackTrace(EnhancedRuntimeException.WrappedPrintStream s){}; // Do Nothing unless the modder wants to.
}
@@ -0,0 +1,18 @@
package net.minecraftforge.fml.client;
import java.util.Map;
public class ExtendedServerListData {
public final String type;
public final boolean isCompatible;
public final Map<String,String> modData;
public final boolean isBlocked;
public ExtendedServerListData(String type, boolean isCompatible, Map<String,String> modData, boolean isBlocked)
{
this.type = type;
this.isCompatible = isCompatible;
this.modData = modData;
this.isBlocked = isBlocked;
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,139 @@
package net.minecraftforge.fml.client;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableSet;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.client.config.ConfigGuiType;
import net.minecraftforge.fml.client.config.DummyConfigElement;
import net.minecraftforge.fml.client.config.GuiConfig;
import net.minecraftforge.fml.client.config.IConfigElement;
import net.minecraftforge.fml.client.config.DummyConfigElement.DummyCategoryElement;
import net.minecraftforge.fml.client.config.DummyConfigElement.DummyListElement;
import net.minecraftforge.fml.client.config.GuiConfigEntries.NumberSliderEntry;
public class FMLConfigGuiFactory implements IModGuiFactory
{
public static class FMLConfigGuiScreen extends GuiConfig
{
public FMLConfigGuiScreen(GuiScreen parent)
{
super(parent, getConfigElements(), "FML", false, false, I18n.format("fml.config.sample.title"));
}
private static List<IConfigElement> getConfigElements()
{
List<IConfigElement> list = new ArrayList<IConfigElement>();
List<IConfigElement> listsList = new ArrayList<IConfigElement>();
List<IConfigElement> stringsList = new ArrayList<IConfigElement>();
List<IConfigElement> numbersList = new ArrayList<IConfigElement>();
Pattern commaDelimitedPattern = Pattern.compile("([A-Za-z]+((,){1}( )*|$))+?");
// Top Level Settings
list.add(new DummyConfigElement("imABoolean", true, ConfigGuiType.BOOLEAN, "fml.config.sample.imABoolean").setRequiresMcRestart(true));
list.add(new DummyConfigElement("imAnInteger", 42, ConfigGuiType.INTEGER, "fml.config.sample.imAnInteger", -1, 256).setRequiresMcRestart(true));
list.add(new DummyConfigElement("imADouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.imADouble", -1.0D, 256.256D).setRequiresMcRestart(true));
list.add(new DummyConfigElement("imAString", "http://www.montypython.net/scripts/string.php", ConfigGuiType.STRING, "fml.config.sample.imAString").setRequiresMcRestart(true));
// Lists category
listsList.add(new DummyListElement("booleanList", new Boolean[] {true, false, true, false, true, false, true, false}, ConfigGuiType.BOOLEAN, "fml.config.sample.booleanList"));
listsList.add(new DummyListElement("booleanListFixed", new Boolean[] {true, false, true, false, true, false, true, false}, ConfigGuiType.BOOLEAN, "fml.config.sample.booleanListFixed", true));
listsList.add(new DummyListElement("booleanListMax", new Boolean[] {true, false, true, false, true, false, true, false}, ConfigGuiType.BOOLEAN, "fml.config.sample.booleanListMax", 10));
listsList.add(new DummyListElement("doubleList", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleList"));
listsList.add(new DummyListElement("doubleListFixed", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleListFixed", true));
listsList.add(new DummyListElement("doubleListMax", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleListMax", 15));
listsList.add(new DummyListElement("doubleListBounded", new Double[] {0.0D, 1.1D, 2.2D, 3.3D, 4.4D, 5.5D, 6.6D, 7.7D, 8.8D, 9.9D}, ConfigGuiType.DOUBLE, "fml.config.sample.doubleListBounded", -1.0D, 10.0D));
listsList.add(new DummyListElement("integerList", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerList"));
listsList.add(new DummyListElement("integerListFixed", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerListFixed", true));
listsList.add(new DummyListElement("integerListMax", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerListMax", 15));
listsList.add(new DummyListElement("integerListBounded", new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, ConfigGuiType.INTEGER, "fml.config.sample.integerListBounded", -1, 10));
listsList.add(new DummyListElement("stringList", new String[] {"An", "array", "of", "string", "values"}, ConfigGuiType.STRING, "fml.config.sample.stringList"));
listsList.add(new DummyListElement("stringListFixed", new String[] {"A", "fixed", "length", "array", "of", "string", "values"}, ConfigGuiType.STRING, "fml.config.sample.stringListFixed", true));
listsList.add(new DummyListElement("stringListMax", new String[] {"An", "array", "of", "string", "values", "with", "a", "max", "length", "of", "15"}, ConfigGuiType.STRING, "fml.config.sample.stringListMax", 15));
listsList.add(new DummyListElement("stringListPattern", new String[] {"Valid", "Not Valid", "Is, Valid", "Comma, Separated, Value"}, ConfigGuiType.STRING, "fml.config.sample.stringListPattern", commaDelimitedPattern));
list.add(new DummyCategoryElement("lists", "fml.config.sample.ctgy.lists", listsList));
// Strings category
stringsList.add(new DummyConfigElement("basicString", "Just a regular String value, anything goes.", ConfigGuiType.STRING, "fml.config.sample.basicString"));
stringsList.add(new DummyConfigElement("cycleString", "this", ConfigGuiType.STRING, "fml.config.sample.cycleString", new String[] {"this", "property", "cycles", "through", "a", "list", "of", "valid", "choices"}));
stringsList.add(new DummyConfigElement("patternString", "only, comma, separated, words, can, be, entered, in, this, box", ConfigGuiType.STRING, "fml.config.sample.patternString", commaDelimitedPattern));
stringsList.add(new DummyConfigElement("chatColorPicker", "c", ConfigGuiType.COLOR, "fml.config.sample.chatColorPicker", new String[] {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}));
stringsList.add(new DummyConfigElement("modIDSelector", "FML", ConfigGuiType.MOD_ID, "fml.config.sample.modIDSelector"));
list.add(new DummyCategoryElement("strings", "fml.config.sample.ctgy.strings", stringsList));
// Numbers category
numbersList.add((new DummyConfigElement("basicInteger", 42, ConfigGuiType.INTEGER, "fml.config.sample.basicInteger")));
numbersList.add((new DummyConfigElement("boundedInteger", 42, ConfigGuiType.INTEGER, "fml.config.sample.boundedInteger", -1, 256)));
numbersList.add((new DummyConfigElement("sliderInteger", 2000, ConfigGuiType.INTEGER, "fml.config.sample.sliderInteger", 100, 10000)).setCustomListEntryClass(NumberSliderEntry.class));
numbersList.add(new DummyConfigElement("basicDouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.basicDouble"));
numbersList.add(new DummyConfigElement("boundedDouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.boundedDouble", -1.0D, 256.256D));
numbersList.add(new DummyConfigElement("sliderDouble", 42.4242D, ConfigGuiType.DOUBLE, "fml.config.sample.sliderDouble", -1.0D, 256.256D).setCustomListEntryClass(NumberSliderEntry.class));
list.add(new DummyCategoryElement("numbers", "fml.config.sample.ctgy.numbers", numbersList));
return list;
}
}
@Override
public void initialize(Minecraft minecraftInstance)
{
}
@Override
public Class<? extends GuiScreen> mainConfigGuiClass()
{
return FMLConfigGuiScreen.class;
}
private static final Set<RuntimeOptionCategoryElement> fmlCategories = ImmutableSet.of(new RuntimeOptionCategoryElement("HELP", "FML"));
@Override
public Set<RuntimeOptionCategoryElement> runtimeGuiCategories()
{
return fmlCategories;
}
@Override
public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element)
{
return new RuntimeOptionGuiHandler() {
@Override
public void paint(int x, int y, int w, int h)
{
// TODO Auto-generated method stub
}
@Override
public void close()
{
}
@Override
public void addWidgets(List<Gui> widgets, int x, int y, int w, int h)
{
widgets.add(new GuiButton(100, x+10, y+10, "HELLO"));
}
@Override
public void actionCallback(int actionId)
{
// TODO Auto-generated method stub
}
};
}
}
@@ -0,0 +1,68 @@
package net.minecraftforge.fml.client;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.logging.log4j.Level;
import javax.imageio.ImageIO;
import net.minecraft.client.resources.FileResourcePack;
import net.minecraftforge.fml.common.FMLContainerHolder;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.ModContainer;
import com.google.common.base.Charsets;
public class FMLFileResourcePack extends FileResourcePack implements FMLContainerHolder {
private ModContainer container;
public FMLFileResourcePack(ModContainer container)
{
super(container.getSource());
this.container = container;
}
@Override
public String getPackName()
{
return "FMLFileResourcePack:"+container.getName();
}
@Override
protected InputStream getInputStreamByName(String resourceName) throws IOException
{
try
{
return super.getInputStreamByName(resourceName);
}
catch (IOException ioe)
{
if ("pack.mcmeta".equals(resourceName))
{
FMLLog.log(container.getName(), Level.DEBUG, "Mod %s is missing a pack.mcmeta file, substituting a dummy one", container.getName());
return new ByteArrayInputStream(("{\n" +
" \"pack\": {\n"+
" \"description\": \"dummy FML pack for "+container.getName()+"\",\n"+
" \"pack_format\": 1\n"+
"}\n" +
"}").getBytes(Charsets.UTF_8));
}
else throw ioe;
}
}
@Override
public BufferedImage getPackImage() throws IOException
{
return ImageIO.read(getInputStreamByName(container.getMetadata().logoFile));
}
@Override
public ModContainer getFMLContainer()
{
return container;
}
}
@@ -0,0 +1,74 @@
package net.minecraftforge.fml.client;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.apache.logging.log4j.Level;
import javax.imageio.ImageIO;
import net.minecraft.client.resources.FolderResourcePack;
import net.minecraftforge.fml.common.FMLContainerHolder;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.ModContainer;
import com.google.common.base.Charsets;
public class FMLFolderResourcePack extends FolderResourcePack implements FMLContainerHolder {
private ModContainer container;
public FMLFolderResourcePack(ModContainer container)
{
super(container.getSource());
this.container = container;
}
@Override
protected boolean hasResourceName(String p_110593_1_)
{
return super.hasResourceName(p_110593_1_);
}
@Override
public String getPackName()
{
return "FMLFileResourcePack:"+container.getName();
}
@Override
protected InputStream getInputStreamByName(String resourceName) throws IOException
{
try
{
return super.getInputStreamByName(resourceName);
}
catch (IOException ioe)
{
if ("pack.mcmeta".equals(resourceName))
{
FMLLog.log(container.getName(), Level.DEBUG, "Mod %s is missing a pack.mcmeta file, substituting a dummy one", container.getName());
return new ByteArrayInputStream(("{\n" +
" \"pack\": {\n"+
" \"description\": \"dummy FML pack for "+container.getName()+"\",\n"+
" \"pack_format\": 1\n"+
"}\n" +
"}").getBytes(Charsets.UTF_8));
}
else throw ioe;
}
}
@Override
public BufferedImage getPackImage() throws IOException
{
return ImageIO.read(getInputStreamByName(container.getMetadata().logoFile));
}
@Override
public ModContainer getFMLContainer()
{
return container;
}
}
@@ -0,0 +1,55 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.client.resources.I18n;
public class GuiAccessDenied extends GuiScreen
{
private GuiScreen parent;
private ServerData data;
public GuiAccessDenied(GuiScreen parent, ServerData data)
{
this.parent = parent;
this.data = data;
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
this.buttonList.add(new GuiButton(1, this.width / 2 - 75, this.height - 38, I18n.format("gui.done")));
}
@Override
protected void actionPerformed(GuiButton p_73875_1_)
{
if (p_73875_1_.enabled && p_73875_1_.id == 1)
{
FMLClientHandler.instance().showGuiScreen(parent);
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = Math.max(85 - 2 * 10, 10);
this.drawCenteredString(this.fontRendererObj, "Forge Mod Loader could not connect to this server", this.width / 2, offset, 0xFFFFFF);
offset += 10;
this.drawCenteredString(this.fontRendererObj, String.format("The server %s has forbidden modded access", data.serverName), this.width / 2, offset, 0xFFFFFF);
super.drawScreen(mouseX, mouseY, partialTicks);
}
}
@@ -0,0 +1,55 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client;
import java.io.File;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
public class GuiBackupFailed extends GuiScreen
{
private GuiScreen parent;
private File zipName;
public GuiBackupFailed(GuiScreen parent, File zipName)
{
this.parent = parent;
this.zipName = zipName;
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
this.buttonList.add(new GuiButton(1, this.width / 2 - 75, this.height - 38, I18n.format("gui.done")));
}
@Override
protected void actionPerformed(GuiButton p_73875_1_)
{
if (p_73875_1_.enabled && p_73875_1_.id == 1)
{
FMLClientHandler.instance().showGuiScreen(parent);
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = Math.max(85 - 2 * 10, 10);
this.drawCenteredString(this.fontRendererObj, String.format("There was an error saving the archive %s", zipName.getName()), this.width / 2, offset, 0xFFFFFF);
offset += 10;
this.drawCenteredString(this.fontRendererObj, String.format("Please fix the problem and try again"), this.width / 2, offset, 0xFFFFFF);
super.drawScreen(mouseX, mouseY, partialTicks);
}
}
@@ -0,0 +1,33 @@
package net.minecraftforge.fml.client;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiOptionButton;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.common.StartupQuery;
public class GuiConfirmation extends GuiNotification
{
public GuiConfirmation(StartupQuery query)
{
super(query);
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
this.buttonList.add(new GuiOptionButton(0, this.width / 2 - 155, this.height - 38, I18n.format("gui.yes")));
this.buttonList.add(new GuiOptionButton(1, this.width / 2 - 155 + 160, this.height - 38, I18n.format("gui.no")));
}
@Override
protected void actionPerformed(GuiButton button)
{
if (button.enabled && (button.id == 0 || button.id == 1))
{
FMLClientHandler.instance().showGuiScreen(null);
query.setResult(button.id == 0);
query.finish();
}
}
}
@@ -0,0 +1,38 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client;
import net.minecraft.client.gui.GuiErrorScreen;
public class GuiCustomModLoadingErrorScreen extends GuiErrorScreen
{
private CustomModLoadingErrorDisplayException customException;
public GuiCustomModLoadingErrorScreen(CustomModLoadingErrorDisplayException customException)
{
super(null,null);
this.customException = customException;
}
@Override
public void initGui()
{
super.initGui();
this.buttonList.clear();
this.customException.initGui(this, fontRendererObj);
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
this.customException.drawScreen(this, fontRendererObj, mouseX, mouseY, partialTicks);
}
}
@@ -0,0 +1,56 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client;
import java.io.File;
import java.util.Map.Entry;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraftforge.fml.common.DuplicateModsFoundException;
import net.minecraftforge.fml.common.ModContainer;
public class GuiDupesFound extends GuiErrorScreen
{
private DuplicateModsFoundException dupes;
public GuiDupesFound(DuplicateModsFoundException dupes)
{
super(null,null);
this.dupes = dupes;
}
@Override
public void initGui()
{
super.initGui();
this.buttonList.clear();
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = Math.max(85 - dupes.dupes.size() * 10, 10);
this.drawCenteredString(this.fontRendererObj, "Forge Mod Loader has found a problem with your minecraft installation", this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRendererObj, "You have mod sources that are duplicate within your system", this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRendererObj, "Mod Id : File name", this.width / 2, offset, 0xFFFFFF);
offset+=5;
for (Entry<ModContainer, File> mc : dupes.dupes.entries())
{
offset+=10;
this.drawCenteredString(this.fontRendererObj, String.format("%s : %s", mc.getKey().getModId(), mc.getValue().getName()), this.width / 2, offset, 0xEEEEEE);
}
}
}
@@ -0,0 +1,55 @@
package net.minecraftforge.fml.client;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
public class GuiIngameModOptions extends GuiScreen
{
private final GuiScreen parentScreen;
protected String title = "Mod Options";
private GuiModOptionList optionList;
public GuiIngameModOptions(GuiScreen parentScreen)
{
this.parentScreen = parentScreen;
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
this.optionList=new GuiModOptionList(this);
this.optionList.registerScrollButtons(this.buttonList, 7, 8);
this.buttonList.add(new GuiButton(200, this.width / 2 - 100, this.height / 6 + 168, I18n.format("gui.done", new Object[0])));
}
@Override
protected void actionPerformed(GuiButton button)
{
if (button.enabled)
{
if (button.id == 200)
{
this.mc.gameSettings.saveOptions();
this.mc.displayGuiScreen(this.parentScreen);
}
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
// force a non-transparent background
this.drawDefaultBackground();
this.optionList.drawScreen(mouseX, mouseY, partialTicks);
this.drawCenteredString(this.fontRendererObj, this.title, this.width / 2, 15, 0xFFFFFF);
super.drawScreen(mouseX, mouseY, partialTicks);
}
FontRenderer getFontRenderer() {
return fontRendererObj;
}
}
@@ -0,0 +1,453 @@
/*
* The FML Forge Mod Loader suite.
* Copyright (C) 2012 cpw
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fml.client;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.imageio.ImageIO;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.I18n;
import net.minecraft.client.resources.IResourcePack;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StringUtils;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.ModContainer.Disableable;
import org.apache.logging.log4j.Level;
import org.lwjgl.opengl.GL11;
import com.google.common.base.Strings;
/**
* @author cpw
*
*/
public class GuiModList extends GuiScreen
{
private enum SortType implements Comparator<ModContainer>
{
NORMAL(24),
A_TO_Z(25){ @Override protected int compare(String name1, String name2){ return name1.compareTo(name2); }},
Z_TO_A(26){ @Override protected int compare(String name1, String name2){ return name2.compareTo(name1); }};
private int buttonID;
private SortType(int buttonID)
{
this.buttonID = buttonID;
}
public static SortType getTypeForButton(GuiButton button)
{
for (SortType t : values())
{
if (t.buttonID == button.id)
{
return t;
}
}
return null;
}
protected int compare(String name1, String name2){ return 0; }
@Override
public int compare(ModContainer o1, ModContainer o2)
{
String name1 = StringUtils.stripControlCodes(o1.getName()).toLowerCase();
String name2 = StringUtils.stripControlCodes(o2.getName()).toLowerCase();
return compare(name1, name2);
}
}
private GuiScreen mainMenu;
private GuiSlotModList modList;
private int selected = -1;
private ModContainer selectedMod;
private int listWidth;
private ArrayList<ModContainer> mods;
private GuiButton configModButton;
private GuiButton disableModButton;
private ResourceLocation cachedLogo;
private Dimension cachedLogoDimensions;
private int buttonMargin = 1;
private int numButtons = SortType.values().length;
private String lastFilterText = "";
private GuiTextField search;
private boolean sorted = false;
private SortType sortType = SortType.NORMAL;
/**
* @param mainMenu
*/
public GuiModList(GuiScreen mainMenu)
{
this.mainMenu = mainMenu;
this.mods = new ArrayList<ModContainer>();
FMLClientHandler.instance().addSpecialModEntries(mods);
// Add child mods to their parent's list
for (ModContainer mod : Loader.instance().getModList())
{
if (mod.getMetadata() != null && mod.getMetadata().parentMod == null && !Strings.isNullOrEmpty(mod.getMetadata().parent))
{
String parentMod = mod.getMetadata().parent;
ModContainer parentContainer = Loader.instance().getIndexedModList().get(parentMod);
if (parentContainer != null)
{
mod.getMetadata().parentMod = parentContainer;
parentContainer.getMetadata().childMods.add(mod);
continue;
}
}
else if (mod.getMetadata() != null && mod.getMetadata().parentMod != null)
{
continue;
}
mods.add(mod);
}
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
for (ModContainer mod : mods)
{
listWidth = Math.max(listWidth,getFontRenderer().getStringWidth(mod.getName()) + 10);
listWidth = Math.max(listWidth,getFontRenderer().getStringWidth(mod.getVersion()) + 10);
}
listWidth = Math.min(listWidth, 150);
this.modList = new GuiSlotModList(this, mods, listWidth);
this.modList.registerScrollButtons(this.buttonList, 7, 8);
this.buttonList.add(new GuiButton(6, ((modList.right + this.width) / 2) - 100, this.height - 38, I18n.format("gui.done")));
configModButton = new GuiButton(20, 10, this.height - 49, this.listWidth, 20, "Config");
disableModButton = new GuiButton(21, 10, this.height - 27, this.listWidth, 20, "Disable");
this.buttonList.add(configModButton);
this.buttonList.add(disableModButton);
search = new GuiTextField(0, getFontRenderer(), 12, modList.bottom + 17, modList.listWidth - 4, 14);
search.setFocused(true);
search.setCanLoseFocus(true);
int width = (modList.listWidth / numButtons);
int x = 10, y = 10;
GuiButton normalSort = new GuiButton(SortType.NORMAL.buttonID, x, y, width - buttonMargin, 20, I18n.format("fml.menu.mods.normal"));
normalSort.enabled = false;
buttonList.add(normalSort);
x += width + buttonMargin;
buttonList.add(new GuiButton(SortType.A_TO_Z.buttonID, x, y, width - buttonMargin, 20, "A-Z"));
x += width + buttonMargin;
buttonList.add(new GuiButton(SortType.Z_TO_A.buttonID, x, y, width - buttonMargin, 20, "Z-A"));
}
@Override
protected void mouseClicked(int x, int y, int button) throws IOException
{
super.mouseClicked(x, y, button);
search.mouseClicked(x, y, button);
if (button == 1 && x >= search.xPosition && x < search.xPosition + search.width && y >= search.yPosition && y < search.yPosition + search.height) {
search.setText("");
}
}
@Override
protected void keyTyped(char c, int keyCode) throws IOException
{
super.keyTyped(c, keyCode);
search.textboxKeyTyped(c, keyCode);
}
@Override
public void updateScreen()
{
super.updateScreen();
search.updateCursorCounter();
if (!search.getText().equals(lastFilterText))
{
reloadMods();
sorted = false;
}
if (!sorted)
{
reloadMods();
Collections.sort(mods, sortType);
selected = modList.selectedIndex = mods.indexOf(selectedMod);
sorted = true;
}
}
private void reloadMods()
{
ArrayList<ModContainer> mods = modList.getMods();
mods.clear();
for (ModContainer m : Loader.instance().getActiveModList())
{
// If it passes the filter, and is not a child mod
if (m.getName().toLowerCase().contains(search.getText().toLowerCase()) && m.getMetadata().parentMod == null)
{
mods.add(m);
}
}
this.mods = mods;
lastFilterText = search.getText();
}
@Override
protected void actionPerformed(GuiButton button) throws IOException {
if (button.enabled)
{
SortType type = SortType.getTypeForButton(button);
if (type != null)
{
for (GuiButton b : (List<GuiButton>) buttonList)
{
if (SortType.getTypeForButton(b) != null)
{
b.enabled = true;
}
}
button.enabled = false;
sorted = false;
sortType = type;
this.mods = modList.getMods();
}
else
{
switch (button.id)
{
case 6:
this.mc.displayGuiScreen(this.mainMenu);
return;
case 20:
try
{
IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod);
GuiScreen newScreen = guiFactory.mainConfigGuiClass().getConstructor(GuiScreen.class).newInstance(this);
this.mc.displayGuiScreen(newScreen);
}
catch (Exception e)
{
FMLLog.log(Level.ERROR, e, "There was a critical issue trying to build the config GUI for %s", selectedMod.getModId());
}
return;
}
}
}
super.actionPerformed(button);
}
public int drawLine(String line, int offset, int shifty)
{
this.fontRendererObj.drawString(line, offset, shifty, 0xd7edea);
return shifty + 10;
}
@Override
public void drawScreen(int p_571_1_, int p_571_2_, float p_571_3_)
{
this.modList.drawScreen(p_571_1_, p_571_2_, p_571_3_);
this.drawCenteredString(this.fontRendererObj, "Mod List", this.width / 2, 16, 0xFFFFFF);
int offset = this.listWidth + 20;
if (selectedMod != null)
{
GlStateManager.enableBlend();
if (!selectedMod.getMetadata().autogenerated)
{
configModButton.visible = true;
disableModButton.visible = true;
disableModButton.packedFGColour = 0xFF3377;
configModButton.enabled = false;
int shifty = 35;
String logoFile = selectedMod.getMetadata().logoFile;
if (!logoFile.isEmpty())
{
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
TextureManager tm = mc.getTextureManager();
IResourcePack pack = FMLClientHandler.instance().getResourcePackFor(selectedMod.getModId());
try
{
if (cachedLogo == null)
{
BufferedImage logo = null;
if (pack!=null)
{
logo = pack.getPackImage();
}
else
{
InputStream logoResource = getClass().getResourceAsStream(logoFile);
if (logoResource != null)
{
logo = ImageIO.read(logoResource);
}
}
if (logo != null)
{
cachedLogo = tm.getDynamicTextureLocation("modlogo", new DynamicTexture(logo));
cachedLogoDimensions = new Dimension(logo.getWidth(), logo.getHeight());
}
}
if (cachedLogo != null)
{
this.mc.renderEngine.bindTexture(cachedLogo);
double scaleX = cachedLogoDimensions.width / 200.0;
double scaleY = cachedLogoDimensions.height / 65.0;
double scale = 1.0;
if (scaleX > 1 || scaleY > 1)
{
scale = 1.0 / Math.max(scaleX, scaleY);
}
cachedLogoDimensions.width *= scale;
cachedLogoDimensions.height *= scale;
int top = 32;
Tessellator tess = Tessellator.getInstance();
WorldRenderer world = tess.getWorldRenderer();
world.startDrawingQuads();
world.addVertexWithUV(offset, top + cachedLogoDimensions.height, zLevel, 0, 1);
world.addVertexWithUV(offset + cachedLogoDimensions.width, top + cachedLogoDimensions.height, zLevel, 1, 1);
world.addVertexWithUV(offset + cachedLogoDimensions.width, top, zLevel, 1, 0);
world.addVertexWithUV(offset, top, zLevel, 0, 0);
tess.draw();
shifty += 65;
}
}
catch (IOException e)
{
;
}
}
this.fontRendererObj.drawStringWithShadow(selectedMod.getMetadata().name, offset, shifty, 0xFFFFFF);
shifty += 12;
shifty = drawLine(String.format("Version: %s (%s)", selectedMod.getDisplayVersion(), selectedMod.getVersion()), offset, shifty);
shifty = drawLine(String.format("Mod ID: '%s' Mod State: %s", selectedMod.getModId(), Loader.instance().getModState(selectedMod)), offset, shifty);
if (!selectedMod.getMetadata().credits.isEmpty())
{
shifty = drawLine(String.format("Credits: %s", selectedMod.getMetadata().credits), offset, shifty);
}
shifty = drawLine(String.format("Authors: %s", selectedMod.getMetadata().getAuthorList()), offset, shifty);
shifty = drawLine(String.format("URL: %s", selectedMod.getMetadata().url), offset, shifty);
shifty = drawLine(selectedMod.getMetadata().childMods.isEmpty() ? "No child mods for this mod" : String.format("Child mods: %s", selectedMod.getMetadata().getChildModList()), offset, shifty);
int rightSide = this.width - offset - 20;
if (rightSide > 20)
{
this.getFontRenderer().drawSplitString(selectedMod.getMetadata().description, offset, shifty + 10, rightSide, 0xDDDDDD);
}
Disableable disableable = selectedMod.canBeDisabled();
if (disableable == Disableable.RESTART)
{
disableModButton.enabled = true;
disableModButton.visible = true;
disableModButton.packedFGColour = 0xFF3377;
}
else if (disableable == Disableable.YES)
{
disableModButton.enabled = true;
disableModButton.visible = true;
disableModButton.packedFGColour = 0;
}
else
{
disableModButton.packedFGColour = 0;
disableModButton.visible = true;
disableModButton.enabled = false;
}
IModGuiFactory guiFactory = FMLClientHandler.instance().getGuiFactoryFor(selectedMod);
if (guiFactory == null || guiFactory.mainConfigGuiClass() == null)
{
configModButton.visible = true;
configModButton.enabled = false;
}
else
{
configModButton.visible = true;
configModButton.enabled = true;
}
}
else
{
offset = ( this.listWidth + this.width ) / 2;
this.drawCenteredString(this.fontRendererObj, selectedMod.getName(), offset, 35, 0xFFFFFF);
this.drawCenteredString(this.fontRendererObj, String.format("Version: %s",selectedMod.getVersion()), offset, 45, 0xFFFFFF);
this.drawCenteredString(this.fontRendererObj, String.format("Mod State: %s",Loader.instance().getModState(selectedMod)), offset, 55, 0xFFFFFF);
this.drawCenteredString(this.fontRendererObj, "No mod information found", offset, 65, 0xDDDDDD);
this.drawCenteredString(this.fontRendererObj, "Ask your mod author to provide a mod mcmod.info file", offset, 75, 0xDDDDDD);
configModButton.visible = false;
disableModButton.visible = false;
}
GlStateManager.disableBlend();
}
else
{
configModButton.visible = false;
disableModButton.visible = false;
}
super.drawScreen(p_571_1_, p_571_2_, p_571_3_);
String text = I18n.format("fml.menu.mods.search");
int x = ((10 + modList.right) / 2) - (getFontRenderer().getStringWidth(text) / 2);
getFontRenderer().drawString(text, x, modList.bottom + 5, 0xFFFFFF);
search.drawTextBox();
}
Minecraft getMinecraftInstance()
{
return mc;
}
FontRenderer getFontRenderer()
{
return fontRendererObj;
}
public void selectModIndex(int index)
{
this.selected = index;
this.selectedMod = (index >= 0 && index <= mods.size()) ? mods.get(selected) : null;
cachedLogo = null;
}
public boolean modIndexSelected(int index)
{
return index == selected;
}
}
@@ -0,0 +1,47 @@
package net.minecraftforge.fml.client;
import net.minecraft.client.renderer.Tessellator;
public class GuiModOptionList extends GuiScrollingList {
private GuiIngameModOptions parent;
public GuiModOptionList(GuiIngameModOptions parent)
{
super(parent.mc, 150, parent.height, 32, parent.height - 65 + 4, 10, 35);
this.parent = parent;
}
@Override
protected int getSize()
{
return 1;
}
@Override
protected void elementClicked(int index, boolean doubleClick)
{
// TODO Auto-generated method stub
}
@Override
protected boolean isSelected(int index)
{
return false;
}
@Override
protected void drawBackground()
{
}
@Override
protected void drawSlot(int var1, int var2, int var3, int var4, Tessellator var5)
{
this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth("Test 1", listWidth - 10), this.left + 3 , var3 + 2, 0xFF2222);
this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth("TEST 2", listWidth - 10), this.left + 3 , var3 + 12, 0xFF2222);
this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth("DISABLED", listWidth - 10), this.left + 3 , var3 + 22, 0xFF2222);
}
}
@@ -0,0 +1,63 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraftforge.fml.common.MissingModsException;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
public class GuiModsMissing extends GuiErrorScreen
{
private MissingModsException modsMissing;
public GuiModsMissing(MissingModsException modsMissing)
{
super(null,null);
this.modsMissing = modsMissing;
}
@Override
public void initGui()
{
super.initGui();
this.buttonList.clear();
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = Math.max(85 - modsMissing.missingMods.size() * 10, 10);
this.drawCenteredString(this.fontRendererObj, "Forge Mod Loader has found a problem with your minecraft installation", this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRendererObj, "The mods and versions listed below could not be found", this.width / 2, offset, 0xFFFFFF);
offset+=5;
for (ArtifactVersion v : modsMissing.missingMods)
{
offset+=10;
if (v instanceof DefaultArtifactVersion)
{
DefaultArtifactVersion dav = (DefaultArtifactVersion)v;
if (dav.getRange() != null && dav.getRange().isUnboundedAbove())
{
this.drawCenteredString(this.fontRendererObj, String.format("%s : minimum version required is %s", v.getLabel(), dav.getRange().getLowerBoundString()), this.width / 2, offset, 0xEEEEEE);
continue;
}
}
this.drawCenteredString(this.fontRendererObj, String.format("%s : %s", v.getLabel(), v.getRangeString()), this.width / 2, offset, 0xEEEEEE);
}
offset+=20;
this.drawCenteredString(this.fontRendererObj, "The file 'logs/fml-client-latest.log' contains more information", this.width / 2, offset, 0xFFFFFF);
}
}
@@ -0,0 +1,63 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.common.MissingModsException;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
public class GuiModsMissingForServer extends GuiScreen
{
private MissingModsException modsMissing;
public GuiModsMissingForServer(MissingModsException modsMissing)
{
this.modsMissing = modsMissing;
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
this.buttonList.add(new GuiButton(1, this.width / 2 - 75, this.height - 38, I18n.format("gui.done")));
}
@Override
protected void actionPerformed(GuiButton p_73875_1_)
{
if (p_73875_1_.enabled && p_73875_1_.id == 1)
{
FMLClientHandler.instance().showGuiScreen(null);
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = Math.max(85 - modsMissing.missingMods.size() * 10, 10);
this.drawCenteredString(this.fontRendererObj, "Forge Mod Loader could not connect to this server", this.width / 2, offset, 0xFFFFFF);
offset += 10;
this.drawCenteredString(this.fontRendererObj, "The mods and versions listed below could not be found", this.width / 2, offset, 0xFFFFFF);
offset += 10;
this.drawCenteredString(this.fontRendererObj, "They are required to play on this server", this.width / 2, offset, 0xFFFFFF);
offset += 5;
for (ArtifactVersion v : modsMissing.missingMods)
{
offset += 10;
this.drawCenteredString(this.fontRendererObj, String.format("%s : %s", v.getLabel(), v.getRangeString()), this.width / 2, offset, 0xEEEEEE);
}
super.drawScreen(mouseX, mouseY, partialTicks);
}
}
@@ -0,0 +1,62 @@
package net.minecraftforge.fml.client;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.common.StartupQuery;
public class GuiNotification extends GuiScreen
{
public GuiNotification(StartupQuery query)
{
this.query = query;
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
this.buttonList.add(new GuiButton(0, this.width / 2 - 100, this.height - 38, I18n.format("gui.done")));
}
@Override
protected void actionPerformed(GuiButton button)
{
if (button.enabled && button.id == 0)
{
FMLClientHandler.instance().showGuiScreen(null);
query.finish();
}
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
String[] lines = query.getText().split("\n");
int spaceAvailable = this.height - 38 - 20;
int spaceRequired = Math.min(spaceAvailable, 10 + 10 * lines.length);
int offset = 10 + (spaceAvailable - spaceRequired) / 2; // vertically centered
for (String line : lines)
{
if (offset >= spaceAvailable)
{
this.drawCenteredString(this.fontRendererObj, "...", this.width / 2, offset, 0xFFFFFF);
break;
}
else
{
if (!line.isEmpty()) this.drawCenteredString(this.fontRendererObj, line, this.width / 2, offset, 0xFFFFFF);
offset += 10;
}
}
super.drawScreen(mouseX, mouseY, partialTicks);
}
protected final StartupQuery query;
}
@@ -0,0 +1,96 @@
package net.minecraftforge.fml.client;
import java.io.File;
import java.io.IOException;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiLabel;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiSelectWorld;
import net.minecraft.client.gui.GuiYesNo;
import net.minecraft.client.gui.GuiYesNoCallback;
import net.minecraft.world.WorldSettings;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.fml.common.StartupQuery;
import net.minecraftforge.fml.common.ZipperUtil;
import org.apache.logging.log4j.Level;
public class GuiOldSaveLoadConfirm extends GuiYesNo implements GuiYesNoCallback {
private String dirName;
private String saveName;
private File zip;
private GuiScreen parent;
public GuiOldSaveLoadConfirm(String dirName, String saveName, GuiScreen parent)
{
super(null, "", "", 0);
this.parent = parent;
this.dirName = dirName;
this.saveName = saveName;
this.zip = new File(FMLClientHandler.instance().getClient().mcDataDir,String.format("%s-%2$td%2$tm%2$ty%2$tH%2$tM%2$tS.zip", dirName, System.currentTimeMillis()));
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
this.drawCenteredString(this.fontRendererObj, String.format("The world %s contains pre-update modding data", saveName), this.width / 2, 50, 16777215);
this.drawCenteredString(this.fontRendererObj, String.format("There may be problems updating it to this version"), this.width / 2, 70, 16777215);
this.drawCenteredString(this.fontRendererObj, String.format("FML will save a zip to %s", zip.getName()), this.width / 2, 90, 16777215);
this.drawCenteredString(this.fontRendererObj, String.format("Do you wish to continue loading?"), this.width / 2, 110, 16777215);
int k;
for (k = 0; k < this.buttonList.size(); ++k)
{
((GuiButton)this.buttonList.get(k)).drawButton(this.mc, mouseX, mouseY);
}
for (k = 0; k < this.labelList.size(); ++k)
{
((GuiLabel)this.labelList.get(k)).drawLabel(this.mc, mouseX, mouseY);
}
}
@Override
protected void actionPerformed(GuiButton button)
{
if (button.id == 1)
{
ObfuscationReflectionHelper.setPrivateValue(GuiSelectWorld.class, (GuiSelectWorld)parentScreen, false, "field_"+"146634_i");
FMLClientHandler.instance().showGuiScreen(parent);
}
else
{
FMLLog.info("Capturing current state of world %s into file %s", saveName, zip.getAbsolutePath());
try
{
String skip = System.getProperty("fml.doNotBackup");
if (skip == null || !"true".equals(skip))
{
ZipperUtil.zip(new File(FMLClientHandler.instance().getSavesDir(), dirName), zip);
}
else
{
for (int x = 0; x < 10; x++)
FMLLog.severe("!!!!!!!!!! UPDATING WORLD WITHOUT DOING BACKUP !!!!!!!!!!!!!!!!");
}
} catch (IOException e)
{
FMLLog.log(Level.WARN, e, "There was a problem saving the backup %s. Please fix and try again", zip.getName());
FMLClientHandler.instance().showGuiScreen(new GuiBackupFailed(parent, zip));
return;
}
FMLClientHandler.instance().showGuiScreen(null);
try
{
mc.launchIntegratedServer(dirName, saveName, (WorldSettings)null);
}
catch (StartupQuery.AbortedException e)
{
// ignore
}
}
}
}
@@ -0,0 +1,459 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.WorldRenderer;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
public abstract class GuiScrollingList
{
private final Minecraft client;
protected final int listWidth;
protected final int listHeight;
protected final int top;
protected final int bottom;
protected final int right;
protected final int left;
protected final int slotHeight;
private int scrollUpActionId;
private int scrollDownActionId;
protected int mouseX;
protected int mouseY;
private float initialMouseClickY = -2.0F;
private float scrollFactor;
private float scrollDistance;
protected int selectedIndex = -1;
private long lastClickTime = 0L;
private boolean field_25123_p = true;
private boolean field_27262_q;
private int field_27261_r;
public GuiScrollingList(Minecraft client, int width, int height, int top, int bottom, int left, int entryHeight)
{
this.client = client;
this.listWidth = width;
this.listHeight = height;
this.top = top;
this.bottom = bottom;
this.slotHeight = entryHeight;
this.left = left;
this.right = width + this.left;
}
public void func_27258_a(boolean p_27258_1_)
{
this.field_25123_p = p_27258_1_;
}
protected void func_27259_a(boolean p_27259_1_, int p_27259_2_)
{
this.field_27262_q = p_27259_1_;
this.field_27261_r = p_27259_2_;
if (!p_27259_1_)
{
this.field_27261_r = 0;
}
}
protected abstract int getSize();
protected abstract void elementClicked(int index, boolean doubleClick);
protected abstract boolean isSelected(int index);
protected int getContentHeight()
{
return this.getSize() * this.slotHeight + this.field_27261_r;
}
protected abstract void drawBackground();
protected abstract void drawSlot(int var1, int var2, int var3, int var4, Tessellator var5);
protected void func_27260_a(int p_27260_1_, int p_27260_2_, Tessellator p_27260_3_) {}
protected void func_27255_a(int p_27255_1_, int p_27255_2_) {}
protected void func_27257_b(int p_27257_1_, int p_27257_2_) {}
public int func_27256_c(int p_27256_1_, int p_27256_2_)
{
int var3 = this.left + 1;
int var4 = this.left + this.listWidth - 7;
int var5 = p_27256_2_ - this.top - this.field_27261_r + (int)this.scrollDistance - 4;
int var6 = var5 / this.slotHeight;
return p_27256_1_ >= var3 && p_27256_1_ <= var4 && var6 >= 0 && var5 >= 0 && var6 < this.getSize() ? var6 : -1;
}
public void registerScrollButtons(@SuppressWarnings("rawtypes") List p_22240_1_, int p_22240_2_, int p_22240_3_)
{
this.scrollUpActionId = p_22240_2_;
this.scrollDownActionId = p_22240_3_;
}
private void applyScrollLimits()
{
int var1 = this.getContentHeight() - (this.bottom - this.top - 4);
if (var1 < 0)
{
var1 /= 2;
}
if (this.scrollDistance < 0.0F)
{
this.scrollDistance = 0.0F;
}
if (this.scrollDistance > (float)var1)
{
this.scrollDistance = (float)var1;
}
}
public void actionPerformed(GuiButton button)
{
if (button.enabled)
{
if (button.id == this.scrollUpActionId)
{
this.scrollDistance -= (float)(this.slotHeight * 2 / 3);
this.initialMouseClickY = -2.0F;
this.applyScrollLimits();
}
else if (button.id == this.scrollDownActionId)
{
this.scrollDistance += (float)(this.slotHeight * 2 / 3);
this.initialMouseClickY = -2.0F;
this.applyScrollLimits();
}
}
}
public void drawScreen(int mouseX, int mouseY, float p_22243_3_)
{
this.mouseX = mouseX;
this.mouseY = mouseY;
this.drawBackground();
int listLength = this.getSize();
int scrollBarXStart = this.left + this.listWidth - 6;
int scrollBarXEnd = scrollBarXStart + 6;
int boxLeft = this.left;
int boxRight = scrollBarXStart-1;
int var10;
int var11;
int var13;
int var19;
if (Mouse.isButtonDown(0))
{
if (this.initialMouseClickY == -1.0F)
{
boolean var7 = true;
if (mouseY >= this.top && mouseY <= this.bottom)
{
var10 = mouseY - this.top - this.field_27261_r + (int)this.scrollDistance - 4;
var11 = var10 / this.slotHeight;
if (mouseX >= boxLeft && mouseX <= boxRight && var11 >= 0 && var10 >= 0 && var11 < listLength)
{
boolean var12 = var11 == this.selectedIndex && System.currentTimeMillis() - this.lastClickTime < 250L;
this.elementClicked(var11, var12);
this.selectedIndex = var11;
this.lastClickTime = System.currentTimeMillis();
}
else if (mouseX >= boxLeft && mouseX <= boxRight && var10 < 0)
{
this.func_27255_a(mouseX - boxLeft, mouseY - this.top + (int)this.scrollDistance - 4);
var7 = false;
}
if (mouseX >= scrollBarXStart && mouseX <= scrollBarXEnd)
{
this.scrollFactor = -1.0F;
var19 = this.getContentHeight() - (this.bottom - this.top - 4);
if (var19 < 1)
{
var19 = 1;
}
var13 = (int)((float)((this.bottom - this.top) * (this.bottom - this.top)) / (float)this.getContentHeight());
if (var13 < 32)
{
var13 = 32;
}
if (var13 > this.bottom - this.top - 8)
{
var13 = this.bottom - this.top - 8;
}
this.scrollFactor /= (float)(this.bottom - this.top - var13) / (float)var19;
}
else
{
this.scrollFactor = 1.0F;
}
if (var7)
{
this.initialMouseClickY = (float)mouseY;
}
else
{
this.initialMouseClickY = -2.0F;
}
}
else
{
this.initialMouseClickY = -2.0F;
}
}
else if (this.initialMouseClickY >= 0.0F)
{
this.scrollDistance -= ((float)mouseY - this.initialMouseClickY) * this.scrollFactor;
this.initialMouseClickY = (float)mouseY;
}
}
else
{
while (Mouse.next())
{
int var16 = Mouse.getEventDWheel();
if (var16 != 0)
{
if (var16 > 0)
{
var16 = -1;
}
else if (var16 < 0)
{
var16 = 1;
}
this.scrollDistance += (float)(var16 * this.slotHeight / 2);
}
}
this.initialMouseClickY = -1.0F;
}
this.applyScrollLimits();
Tessellator tess = Tessellator.getInstance();
WorldRenderer worldr = tess.getWorldRenderer();
if (this.client.theWorld != null)
{
this.drawGradientRect(this.left, this.top, this.right, this.bottom, -1072689136, -804253680);
}
else
{
GlStateManager.disableLighting();
GlStateManager.disableFog();
this.client.renderEngine.bindTexture(Gui.optionsBackground);
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
float var17 = 32.0F;
worldr.startDrawingQuads();
worldr.setColorOpaque_I(2105376);
worldr.addVertexWithUV((double)this.left, (double)this.bottom, 0.0D, (double)((float)this.left / var17), (double)((float)(this.bottom + (int)this.scrollDistance) / var17));
worldr.addVertexWithUV((double)this.right, (double)this.bottom, 0.0D, (double)((float)this.right / var17), (double)((float)(this.bottom + (int)this.scrollDistance) / var17));
worldr.addVertexWithUV((double)this.right, (double)this.top, 0.0D, (double)((float)this.right / var17), (double)((float)(this.top + (int)this.scrollDistance) / var17));
worldr.addVertexWithUV((double)this.left, (double)this.top, 0.0D, (double)((float)this.left / var17), (double)((float)(this.top + (int)this.scrollDistance) / var17));
tess.draw();
}
// boxRight = this.listWidth / 2 - 92 - 16;
var10 = this.top + 4 - (int)this.scrollDistance;
if (this.field_27262_q)
{
this.func_27260_a(boxRight, var10, tess);
}
int var14;
for (var11 = 0; var11 < listLength; ++var11)
{
var19 = var10 + var11 * this.slotHeight + this.field_27261_r;
var13 = this.slotHeight - 4;
if (var19 <= this.bottom && var19 + var13 >= this.top)
{
if (this.field_25123_p && this.isSelected(var11))
{
var14 = boxLeft;
int var15 = boxRight;
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
GlStateManager.disableTexture2D();
worldr.startDrawingQuads();
worldr.setColorOpaque_I(8421504);
worldr.addVertexWithUV((double)var14, (double)(var19 + var13 + 2), 0.0D, 0.0D, 1.0D);
worldr.addVertexWithUV((double)var15, (double)(var19 + var13 + 2), 0.0D, 1.0D, 1.0D);
worldr.addVertexWithUV((double)var15, (double)(var19 - 2), 0.0D, 1.0D, 0.0D);
worldr.addVertexWithUV((double)var14, (double)(var19 - 2), 0.0D, 0.0D, 0.0D);
worldr.setColorOpaque_I(0);
worldr.addVertexWithUV((double)(var14 + 1), (double)(var19 + var13 + 1), 0.0D, 0.0D, 1.0D);
worldr.addVertexWithUV((double)(var15 - 1), (double)(var19 + var13 + 1), 0.0D, 1.0D, 1.0D);
worldr.addVertexWithUV((double)(var15 - 1), (double)(var19 - 1), 0.0D, 1.0D, 0.0D);
worldr.addVertexWithUV((double)(var14 + 1), (double)(var19 - 1), 0.0D, 0.0D, 0.0D);
tess.draw();
GlStateManager.enableTexture2D();
}
this.drawSlot(var11, boxRight, var19, var13, tess);
}
}
GlStateManager.disableDepth();
byte border = 4;
if (this.client.theWorld == null)
{
this.overlayBackground(0, this.top, 255, 255);
this.overlayBackground(this.bottom, this.listHeight, 255, 255);
}
GlStateManager.enableBlend();
GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GlStateManager.disableAlpha();
GlStateManager.shadeModel(GL11.GL_SMOOTH);
GlStateManager.disableTexture2D();
worldr.startDrawingQuads();
worldr.setColorRGBA_I(0, 0);
worldr.addVertexWithUV((double)this.left, (double)(this.top + border), 0.0D, 0.0D, 1.0D);
worldr.addVertexWithUV((double)this.right, (double)(this.top + border), 0.0D, 1.0D, 1.0D);
worldr.setColorRGBA_I(0, 255);
worldr.addVertexWithUV((double)this.right, (double)this.top, 0.0D, 1.0D, 0.0D);
worldr.addVertexWithUV((double)this.left, (double)this.top, 0.0D, 0.0D, 0.0D);
tess.draw();
worldr.startDrawingQuads();
worldr.setColorRGBA_I(0, 255);
worldr.addVertexWithUV((double)this.left, (double)this.bottom, 0.0D, 0.0D, 1.0D);
worldr.addVertexWithUV((double)this.right, (double)this.bottom, 0.0D, 1.0D, 1.0D);
worldr.setColorRGBA_I(0, 0);
worldr.addVertexWithUV((double)this.right, (double)(this.bottom - border), 0.0D, 1.0D, 0.0D);
worldr.addVertexWithUV((double)this.left, (double)(this.bottom - border), 0.0D, 0.0D, 0.0D);
tess.draw();
var19 = this.getContentHeight() - (this.bottom - this.top - 4);
if (var19 > 0)
{
var13 = (this.bottom - this.top) * (this.bottom - this.top) / this.getContentHeight();
if (var13 < 32)
{
var13 = 32;
}
if (var13 > this.bottom - this.top - 8)
{
var13 = this.bottom - this.top - 8;
}
var14 = (int)this.scrollDistance * (this.bottom - this.top - var13) / var19 + this.top;
if (var14 < this.top)
{
var14 = this.top;
}
worldr.startDrawingQuads();
worldr.setColorRGBA_I(0, 255);
worldr.addVertexWithUV((double)scrollBarXStart, (double)this.bottom, 0.0D, 0.0D, 1.0D);
worldr.addVertexWithUV((double)scrollBarXEnd, (double)this.bottom, 0.0D, 1.0D, 1.0D);
worldr.addVertexWithUV((double)scrollBarXEnd, (double)this.top, 0.0D, 1.0D, 0.0D);
worldr.addVertexWithUV((double)scrollBarXStart, (double)this.top, 0.0D, 0.0D, 0.0D);
tess.draw();
worldr.startDrawingQuads();
worldr.setColorRGBA_I(8421504, 255);
worldr.addVertexWithUV((double)scrollBarXStart, (double)(var14 + var13), 0.0D, 0.0D, 1.0D);
worldr.addVertexWithUV((double)scrollBarXEnd, (double)(var14 + var13), 0.0D, 1.0D, 1.0D);
worldr.addVertexWithUV((double)scrollBarXEnd, (double)var14, 0.0D, 1.0D, 0.0D);
worldr.addVertexWithUV((double)scrollBarXStart, (double)var14, 0.0D, 0.0D, 0.0D);
tess.draw();
worldr.startDrawingQuads();
worldr.setColorRGBA_I(12632256, 255);
worldr.addVertexWithUV((double)scrollBarXStart, (double)(var14 + var13 - 1), 0.0D, 0.0D, 1.0D);
worldr.addVertexWithUV((double)(scrollBarXEnd - 1), (double)(var14 + var13 - 1), 0.0D, 1.0D, 1.0D);
worldr.addVertexWithUV((double)(scrollBarXEnd - 1), (double)var14, 0.0D, 1.0D, 0.0D);
worldr.addVertexWithUV((double)scrollBarXStart, (double)var14, 0.0D, 0.0D, 0.0D);
tess.draw();
}
this.func_27257_b(mouseX, mouseY);
GlStateManager.enableTexture2D();
GlStateManager.shadeModel(GL11.GL_FLAT);
GlStateManager.enableAlpha();
GlStateManager.disableBlend();
}
private void overlayBackground(int p_22239_1_, int p_22239_2_, int p_22239_3_, int p_22239_4_)
{
Tessellator var5 = Tessellator.getInstance();
WorldRenderer worldr = var5.getWorldRenderer();
this.client.renderEngine.bindTexture(Gui.optionsBackground);
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
float var6 = 32.0F;
worldr.startDrawingQuads();
worldr.setColorRGBA_I(4210752, p_22239_4_);
worldr.addVertexWithUV(0.0D, (double)p_22239_2_, 0.0D, 0.0D, (double)((float)p_22239_2_ / var6));
worldr.addVertexWithUV((double)this.listWidth + 30, (double)p_22239_2_, 0.0D, (double)((float)(this.listWidth + 30) / var6), (double)((float)p_22239_2_ / var6));
worldr.setColorRGBA_I(4210752, p_22239_3_);
worldr.addVertexWithUV((double)this.listWidth + 30, (double)p_22239_1_, 0.0D, (double)((float)(this.listWidth + 30) / var6), (double)((float)p_22239_1_ / var6));
worldr.addVertexWithUV(0.0D, (double)p_22239_1_, 0.0D, 0.0D, (double)((float)p_22239_1_ / var6));
var5.draw();
}
protected void drawGradientRect(int par1, int par2, int par3, int par4, int par5, int par6)
{
float f = (float)(par5 >> 24 & 255) / 255.0F;
float f1 = (float)(par5 >> 16 & 255) / 255.0F;
float f2 = (float)(par5 >> 8 & 255) / 255.0F;
float f3 = (float)(par5 & 255) / 255.0F;
float f4 = (float)(par6 >> 24 & 255) / 255.0F;
float f5 = (float)(par6 >> 16 & 255) / 255.0F;
float f6 = (float)(par6 >> 8 & 255) / 255.0F;
float f7 = (float)(par6 & 255) / 255.0F;
GlStateManager.disableTexture2D();
GlStateManager.enableBlend();
GlStateManager.disableAlpha();
GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0);
GlStateManager.shadeModel(GL11.GL_SMOOTH);
Tessellator tessellator = Tessellator.getInstance();
WorldRenderer worldrenderer = tessellator.getWorldRenderer();
worldrenderer.startDrawingQuads();
worldrenderer.setColorRGBA_F(f1, f2, f3, f);
worldrenderer.addVertex((double)par3, (double)par2, 0.0D);
worldrenderer.addVertex((double)par1, (double)par2, 0.0D);
worldrenderer.setColorRGBA_F(f5, f6, f7, f4);
worldrenderer.addVertex((double)par1, (double)par4, 0.0D);
worldrenderer.addVertex((double)par3, (double)par4, 0.0D);
tessellator.draw();
GlStateManager.shadeModel(GL11.GL_FLAT);
GlStateManager.disableBlend();
GlStateManager.enableAlpha();
GlStateManager.enableTexture2D();
}
}
@@ -0,0 +1,94 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client;
import java.util.ArrayList;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.StringUtils;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.LoaderState.ModState;
import net.minecraftforge.fml.common.ModContainer;
/**
* @author cpw
*
*/
public class GuiSlotModList extends GuiScrollingList
{
private GuiModList parent;
private ArrayList<ModContainer> mods;
public GuiSlotModList(GuiModList parent, ArrayList<ModContainer> mods, int listWidth)
{
super(parent.getMinecraftInstance(), listWidth, parent.height, 32, parent.height - 88 + 4, 10, 35);
this.parent=parent;
this.mods=mods;
}
@Override
protected int getSize()
{
return mods.size();
}
@Override
protected void elementClicked(int var1, boolean var2)
{
this.parent.selectModIndex(var1);
}
@Override
protected boolean isSelected(int var1)
{
return this.parent.modIndexSelected(var1);
}
@Override
protected void drawBackground()
{
this.parent.drawDefaultBackground();
}
@Override
protected int getContentHeight()
{
return (this.getSize()) * 35 + 1;
}
ArrayList<ModContainer> getMods()
{
return mods;
}
@Override
protected void drawSlot(int listIndex, int var2, int var3, int var4, Tessellator var5)
{
ModContainer mc=mods.get(listIndex);
String name = StringUtils.stripControlCodes(mc.getName());
String version = StringUtils.stripControlCodes(mc.getDisplayVersion());
if (Loader.instance().getModState(mc)==ModState.DISABLED)
{
this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth(name, listWidth - 10), this.left + 3 , var3 + 2, 0xFF2222);
this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth(version, listWidth - 10), this.left + 3 , var3 + 12, 0xFF2222);
this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth("DISABLED", listWidth - 10), this.left + 3 , var3 + 22, 0xFF2222);
}
else
{
this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth(name, listWidth - 10), this.left + 3 , var3 + 2, 0xFFFFFF);
this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth(version, listWidth - 10), this.left + 3 , var3 + 12, 0xCCCCCC);
this.parent.getFontRenderer().drawString(this.parent.getFontRenderer().trimStringToWidth(mc.getMetadata() !=null ? mc.getMetadata().getChildModCountString() : "Metadata not found", listWidth - 10), this.left + 3 , var3 + 22, 0xCCCCCC);
}
}
}
@@ -0,0 +1,44 @@
package net.minecraftforge.fml.client;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.toposort.ModSortingException;
import net.minecraftforge.fml.common.toposort.ModSortingException.SortingExceptionData;
public class GuiSortingProblem extends GuiScreen {
private SortingExceptionData<ModContainer> failedList;
public GuiSortingProblem(ModSortingException modSorting)
{
this.failedList = modSorting.getExceptionData();
}
@Override
public void initGui()
{
super.initGui();
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = Math.max(85 - (failedList.getVisitedNodes().size() + 3) * 10, 10);
this.drawCenteredString(this.fontRendererObj, "Forge Mod Loader has found a problem with your minecraft installation", this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRendererObj, "A mod sorting cycle was detected and loading cannot continue", this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRendererObj, String.format("The first mod in the cycle is %s", failedList.getFirstBadNode()), this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRendererObj, "The remainder of the cycle involves these mods", this.width / 2, offset, 0xFFFFFF);
offset+=5;
for (ModContainer mc : failedList.getVisitedNodes())
{
offset+=10;
this.drawCenteredString(this.fontRendererObj, String.format("%s : before: %s, after: %s", mc.toString(), mc.getDependants(), mc.getDependencies()), this.width / 2, offset, 0xEEEEEE);
}
offset+=20;
this.drawCenteredString(this.fontRendererObj, "The file 'ForgeModLoader-client-0.log' contains more information", this.width / 2, offset, 0xFFFFFF);
}
}
@@ -0,0 +1,47 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client;
import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.WrongMinecraftVersionException;
public class GuiWrongMinecraft extends GuiErrorScreen
{
private WrongMinecraftVersionException wrongMC;
public GuiWrongMinecraft(WrongMinecraftVersionException wrongMC)
{
super(null,null);
this.wrongMC = wrongMC;
}
@Override
public void initGui()
{
super.initGui();
this.buttonList.clear();
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
int offset = 75;
this.drawCenteredString(this.fontRendererObj, "Forge Mod Loader has found a problem with your minecraft installation", this.width / 2, offset, 0xFFFFFF);
offset+=10;
this.drawCenteredString(this.fontRendererObj, String.format("The mod listed below does not want to run in Minecraft version %s", Loader.instance().getMinecraftModContainer().getVersion()), this.width / 2, offset, 0xFFFFFF);
offset+=5;
offset+=10;
this.drawCenteredString(this.fontRendererObj, String.format("%s (%s) wants Minecraft %s", wrongMC.mod.getName(), wrongMC.mod.getModId(), wrongMC.mod.acceptableMinecraftVersionRange()), this.width / 2, offset, 0xEEEEEE);
offset+=20;
this.drawCenteredString(this.fontRendererObj, "The file 'ForgeModLoader-client-0.log' contains more information", this.width / 2, offset, 0xFFFFFF);
}
}
@@ -0,0 +1,129 @@
package net.minecraftforge.fml.client;
import java.util.List;
import java.util.Set;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiScreen;
public interface IModGuiFactory {
/**
* Called when instantiated to initialize with the active minecraft instance.
*
* @param minecraftInstance the instance
*/
public void initialize(Minecraft minecraftInstance);
/**
* Return the name of a class extending {@link GuiScreen}. This class will
* be instantiated when the "config" button is pressed in the mod list. It will
* have a single argument constructor - the "parent" screen, the same as all
* Minecraft GUIs. The expected behaviour is that this screen will replace the
* "mod list" screen completely, and will return to the mod list screen through
* the parent link, once the appropriate action is taken from the config screen.
*
* A null from this method indicates that the mod does not provide a "config"
* button GUI screen, and the config button will be hidden/disabled.
*
* This config GUI is anticipated to provide configuration to the mod in a friendly
* visual way. It should not be abused to set internals such as IDs (they're gonna
* keep disappearing anyway), but rather, interesting behaviours. This config GUI
* is never run when a server game is running, and should be used to configure
* desired behaviours that affect server state. Costs, mod game modes, stuff like that
* can be changed here.
*
* @return A class that will be instantiated on clicks on the config button
* or null if no GUI is desired.
*/
public Class<? extends GuiScreen> mainConfigGuiClass();
/**
* Return a list of the "runtime" categories this mod wishes to populate with
* GUI elements.
*
* Runtime categories are created on demand and organized in a 'lite' tree format.
* The parent represents the parent node in the tree. There is one special parent
* 'Help' that will always list first, and is generally meant to provide Help type
* content for mods. The remaining parents will sort alphabetically, though
* this may change if there is a lot of alphabetic abuse. "AAA" is probably never a valid
* category parent.
*
* Runtime configuration itself falls into two flavours: in-game help, which is
* generally non interactive except for the text it wishes to show, and client-only
* affecting behaviours. This would include things like toggling minimaps, or cheat modes
* or anything NOT affecting the behaviour of the server. Please don't abuse this to
* change the state of the server in any way, this is intended to behave identically
* when the server is local or remote.
*
* @return the set of options this mod wishes to have available, or empty if none
*/
public Set<RuntimeOptionCategoryElement> runtimeGuiCategories();
/**
* Return an instance of a {@link RuntimeOptionGuiHandler} that handles painting the
* right hand side option screen for the specified {@link RuntimeOptionCategoryElement}.
*
* @param element The element we wish to paint for
* @return The Handler for painting it
*/
public RuntimeOptionGuiHandler getHandlerFor(RuntimeOptionCategoryElement element);
/**
* Represents an option category and entry in the runtime gui options list.
*
* @author cpw
*
*/
public static class RuntimeOptionCategoryElement {
public final String parent;
public final String child;
public RuntimeOptionCategoryElement(String parent, String child)
{
this.parent = parent;
this.child = child;
}
}
/**
* Responsible for painting the mod specific section of runtime options GUI for a particular category
*
* @author cpw
*
*/
public interface RuntimeOptionGuiHandler {
/**
* Called to add widgets to the screen, such as buttons.
* GUI identifier numbers should start at 100 and increase.
* The callback will be through {@link #actionCallback(int)}
*
* @param x X
* @param y Y
* @param w width
* @param h height
*/
public void addWidgets(List<Gui> widgetList, int x, int y, int w, int h);
/**
* Called to paint the rectangle specified.
* @param x X
* @param y Y
* @param w width
* @param h height
*/
public void paint(int x, int y, int w, int h);
/**
* Called if a widget with id >= 100 is fired.
*
* @param actionId the actionId of the firing widget
*/
public void actionCallback(int actionId);
/**
* Called when this handler is about to go away (probably replaced by another one, or closing the
* option screen)
*/
public void close();
}
}
@@ -0,0 +1,816 @@
package net.minecraftforge.fml.client;
import static org.lwjgl.opengl.GL11.GL_ALPHA_TEST;
import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11.GL_GREATER;
import static org.lwjgl.opengl.GL11.GL_LEQUAL;
import static org.lwjgl.opengl.GL11.GL_LIGHTING;
import static org.lwjgl.opengl.GL11.GL_MODELVIEW;
import static org.lwjgl.opengl.GL11.GL_NEAREST;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_PROJECTION;
import static org.lwjgl.opengl.GL11.GL_QUADS;
import static org.lwjgl.opengl.GL11.GL_RENDERER;
import static org.lwjgl.opengl.GL11.GL_RGBA;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER;
import static org.lwjgl.opengl.GL11.GL_VENDOR;
import static org.lwjgl.opengl.GL11.GL_VERSION;
import static org.lwjgl.opengl.GL11.glAlphaFunc;
import static org.lwjgl.opengl.GL11.glBegin;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glBlendFunc;
import static org.lwjgl.opengl.GL11.glClear;
import static org.lwjgl.opengl.GL11.glClearColor;
import static org.lwjgl.opengl.GL11.glColor3ub;
import static org.lwjgl.opengl.GL11.glDeleteTextures;
import static org.lwjgl.opengl.GL11.glDepthFunc;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glEnd;
import static org.lwjgl.opengl.GL11.glGenTextures;
import static org.lwjgl.opengl.GL11.glGetString;
import static org.lwjgl.opengl.GL11.glLoadIdentity;
import static org.lwjgl.opengl.GL11.glMatrixMode;
import static org.lwjgl.opengl.GL11.glOrtho;
import static org.lwjgl.opengl.GL11.glPopMatrix;
import static org.lwjgl.opengl.GL11.glPushMatrix;
import static org.lwjgl.opengl.GL11.glRotatef;
import static org.lwjgl.opengl.GL11.glScalef;
import static org.lwjgl.opengl.GL11.glTexCoord2f;
import static org.lwjgl.opengl.GL11.glTexImage2D;
import static org.lwjgl.opengl.GL11.glTexParameteri;
import static org.lwjgl.opengl.GL11.glTexSubImage2D;
import static org.lwjgl.opengl.GL11.glTranslatef;
import static org.lwjgl.opengl.GL11.glVertex2f;
import static org.lwjgl.opengl.GL11.glViewport;
import static org.lwjgl.opengl.GL12.GL_BGRA;
import static org.lwjgl.opengl.GL12.GL_UNSIGNED_INT_8_8_8_8_REV;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.nio.IntBuffer;
import java.util.Iterator;
import java.util.Properties;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.FileResourcePack;
import net.minecraft.client.resources.FolderResourcePack;
import net.minecraft.client.resources.IResourcePack;
import net.minecraft.crash.CrashReport;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.EnhancedRuntimeException;
import net.minecraftforge.fml.common.EnhancedRuntimeException.WrappedPrintStream;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.ICrashCallable;
import net.minecraftforge.fml.common.ProgressManager;
import net.minecraftforge.fml.common.ProgressManager.ProgressBar;
import net.minecraftforge.fml.common.asm.FMLSanityChecker;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.Level;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.Drawable;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.SharedDrawable;
import org.lwjgl.util.glu.GLU;
/**
* @deprecated not a stable API, will break, don't use this yet
*/
@Deprecated
public class SplashProgress
{
private static Drawable d;
private static volatile boolean pause = false;
private static volatile boolean done = false;
private static Thread thread;
private static volatile Throwable threadError;
private static int angle = 0;
private static final Lock lock = new ReentrantLock(true);
private static SplashFontRenderer fontRenderer;
private static final IResourcePack mcPack = Minecraft.getMinecraft().mcDefaultResourcePack;
private static final IResourcePack fmlPack = createResourcePack(FMLSanityChecker.fmlLocation);
private static IResourcePack miscPack;
private static Texture fontTexture;
private static Texture logoTexture;
private static Texture forgeTexture;
private static Properties config;
private static boolean enabled;
private static boolean rotate;
private static int logoOffset;
private static int backgroundColor;
private static int fontColor;
private static int barBorderColor;
private static int barColor;
private static int barBackgroundColor;
static final Semaphore mutex = new Semaphore(1);
private static String getString(String name, String def)
{
String value = config.getProperty(name, def);
config.setProperty(name, value);
return value;
}
private static boolean getBool(String name, boolean def)
{
return Boolean.parseBoolean(getString(name, Boolean.toString(def)));
}
private static int getInt(String name, int def)
{
return Integer.decode(getString(name, Integer.toString(def)));
}
private static int getHex(String name, int def)
{
return Integer.decode(getString(name, "0x" + Integer.toString(def, 16).toUpperCase()));
}
public static void start()
{
File configFile = new File(Minecraft.getMinecraft().mcDataDir, "config/splash.properties");
FileReader r = null;
config = new Properties();
try
{
r = new FileReader(configFile);
config.load(r);
}
catch(IOException e)
{
FMLLog.info("Could not load splash.properties, will create a default one");
}
finally
{
IOUtils.closeQuietly(r);
}
// Enable if we have the flag, and there's either no optifine, or optifine has added a key to the blackboard ("optifine.ForgeSplashCompatible")
// Optifine authors - add this key to the blackboard if you feel your modifications are now compatible with this code.
enabled = getBool("enabled", true) && ( (!FMLClientHandler.instance().hasOptifine()) || Launch.blackboard.containsKey("optifine.ForgeSplashCompatible"));
rotate = getBool("rotate", false);
logoOffset = getInt("logoOffset", 0);
backgroundColor = getHex("background", 0xFFFFFF);
fontColor = getHex("font", 0x000000);
barBorderColor = getHex("barBorder", 0xC0C0C0);
barColor = getHex("bar", 0xCB3D35);
barBackgroundColor = getHex("barBackground", 0xFFFFFF);
final ResourceLocation fontLoc = new ResourceLocation(getString("fontTexture", "textures/font/ascii.png"));
final ResourceLocation logoLoc = new ResourceLocation(getString("logoTexture", "textures/gui/title/mojang.png"));
final ResourceLocation forgeLoc = new ResourceLocation(getString("forgeTexture", "fml:textures/gui/forge.gif"));
File miscPackFile = new File(Minecraft.getMinecraft().mcDataDir, getString("resourcePackPath", "resources"));
FileWriter w = null;
try
{
w = new FileWriter(configFile);
config.store(w, "Splash screen properties");
}
catch(IOException e)
{
FMLLog.log(Level.ERROR, e, "Could not save the splash.properties file");
}
finally
{
IOUtils.closeQuietly(w);
}
miscPack = createResourcePack(miscPackFile);
if(!enabled) return;
// getting debug info out of the way, while we still can
FMLCommonHandler.instance().registerCrashCallable(new ICrashCallable()
{
public String call() throws Exception
{
return "' Vendor: '" + glGetString(GL_VENDOR) +
"' Version: '" + glGetString(GL_VERSION) +
"' Renderer: '" + glGetString(GL_RENDERER) +
"'";
}
public String getLabel()
{
return "GL info";
}
});
CrashReport report = CrashReport.makeCrashReport(new Throwable()
{
@Override public String getMessage(){ return "This is just a prompt for computer specs to be printed. THIS IS NOT A ERROR"; }
@Override public void printStackTrace(final PrintWriter s){ s.println(getMessage()); }
@Override public void printStackTrace(final PrintStream s) { s.println(getMessage()); }
}, "Loading screen debug info");
System.out.println(report.getCompleteReport());
try
{
d = new SharedDrawable(Display.getDrawable());
Display.getDrawable().releaseContext();
d.makeCurrent();
}
catch (LWJGLException e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
Thread mainThread = Thread.currentThread();
thread = new Thread(new Runnable()
{
private final int barWidth = 400;
private final int barHeight = 20;
private final int textHeight2 = 20;
private final int barOffset = 55;
public void run()
{
setGL();
fontTexture = new Texture(fontLoc);
logoTexture = new Texture(logoLoc);
forgeTexture = new Texture(forgeLoc);
glEnable(GL_TEXTURE_2D);
fontRenderer = new SplashFontRenderer();
glDisable(GL_TEXTURE_2D);
while(!done)
{
ProgressBar first = null, penult = null, last = null;
Iterator<ProgressBar> i = ProgressManager.barIterator();
while(i.hasNext())
{
if(first == null) first = i.next();
else
{
penult = last;
last = i.next();
}
}
glClear(GL_COLOR_BUFFER_BIT);
// matrix setup
int w = Display.getWidth();
int h = Display.getHeight();
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(320 - w/2, 320 + w/2, 240 + h/2, 240 - h/2, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// mojang logo
setColor(backgroundColor);
glEnable(GL_TEXTURE_2D);
logoTexture.bind();
glBegin(GL_QUADS);
logoTexture.texCoord(0, 0, 0);
glVertex2f(320 - 256, 240 - 256);
logoTexture.texCoord(0, 0, 1);
glVertex2f(320 - 256, 240 + 256);
logoTexture.texCoord(0, 1, 1);
glVertex2f(320 + 256, 240 + 256);
logoTexture.texCoord(0, 1, 0);
glVertex2f(320 + 256, 240 - 256);
glEnd();
glDisable(GL_TEXTURE_2D);
// bars
if(first != null)
{
glPushMatrix();
glTranslatef(320 - (float)barWidth / 2, 310, 0);
drawBar(first);
if(penult != null)
{
glTranslatef(0, barOffset, 0);
drawBar(penult);
}
if(last != null)
{
glTranslatef(0, barOffset, 0);
drawBar(last);
}
glPopMatrix();
}
angle += 1;
// forge logo
setColor(backgroundColor);
float fw = (float)forgeTexture.getWidth() / 2 / 2;
float fh = (float)forgeTexture.getHeight() / 2 / 2;
if(rotate)
{
float sh = Math.max(fw, fh);
glTranslatef(320 + w/2 - sh - logoOffset, 240 + h/2 - sh - logoOffset, 0);
glRotatef(angle, 0, 0, 1);
}
else
{
glTranslatef(320 + w/2 - fw - logoOffset, 240 + h/2 - fh - logoOffset, 0);
}
int f = (angle / 10) % forgeTexture.getFrames();
glEnable(GL_TEXTURE_2D);
forgeTexture.bind();
glBegin(GL_QUADS);
forgeTexture.texCoord(f, 0, 0);
glVertex2f(-fw, -fh);
forgeTexture.texCoord(f, 0, 1);
glVertex2f(-fw, fh);
forgeTexture.texCoord(f, 1, 1);
glVertex2f(fw, fh);
forgeTexture.texCoord(f, 1, 0);
glVertex2f(fw, -fh);
glEnd();
glDisable(GL_TEXTURE_2D);
// We use mutex to indicate safely to the main thread that we're taking the display global lock
// So the main thread can skip processing messages while we're updating.
// There are system setups where this call can pause for a while, because the GL implementation
// is trying to impose a framerate or other thing is occurring. Without the mutex, the main
// thread would delay waiting for the same global display lock
mutex.acquireUninterruptibly();
Display.update();
// As soon as we're done, we release the mutex. The other thread can now ping the processmessages
// call as often as it wants until we get get back here again
mutex.release();
if(pause)
{
clearGL();
setGL();
}
Display.sync(100);
}
clearGL();
}
private void setColor(int color)
{
glColor3ub((byte)((color >> 16) & 0xFF), (byte)((color >> 8) & 0xFF), (byte)(color & 0xFF));
}
private void drawBox(int w, int h)
{
glBegin(GL_QUADS);
glVertex2f(0, 0);
glVertex2f(0, h);
glVertex2f(w, h);
glVertex2f(w, 0);
glEnd();
}
private void drawBar(ProgressBar b)
{
glPushMatrix();
// title - message
setColor(fontColor);
glScalef(2, 2, 1);
glEnable(GL_TEXTURE_2D);
fontRenderer.drawString(b.getTitle() + " - " + b.getMessage(), 0, 0, 0x000000);
glDisable(GL_TEXTURE_2D);
glPopMatrix();
// border
glPushMatrix();
glTranslatef(0, textHeight2, 0);
setColor(barBorderColor);
drawBox(barWidth, barHeight);
// interior
setColor(barBackgroundColor);
glTranslatef(1, 1, 0);
drawBox(barWidth - 2, barHeight - 2);
// slidy part
setColor(barColor);
drawBox((barWidth - 2) * (b.getStep() + 1) / (b.getSteps() + 1), barHeight - 2); // Step can sometimes be 0.
// progress text
String progress = "" + b.getStep() + "/" + b.getSteps();
glTranslatef(((float)barWidth - 2) / 2 - fontRenderer.getStringWidth(progress), 2, 0);
setColor(fontColor);
glScalef(2, 2, 1);
glEnable(GL_TEXTURE_2D);
fontRenderer.drawString(progress, 0, 0, 0x000000);
glPopMatrix();
}
private void setGL()
{
lock.lock();
try
{
Display.getDrawable().makeCurrent();
}
catch (LWJGLException e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
glClearColor((float)((backgroundColor >> 16) & 0xFF) / 0xFF, (float)((backgroundColor >> 8) & 0xFF) / 0xFF, (float)(backgroundColor & 0xFF) / 0xFF, 1);
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
private void clearGL()
{
Minecraft mc = Minecraft.getMinecraft();
mc.displayWidth = Display.getWidth();
mc.displayHeight = Display.getHeight();
mc.resize(mc.displayWidth, mc.displayHeight);
glClearColor(1, 1, 1, 1);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, .1f);
try
{
Display.getDrawable().releaseContext();
}
catch (LWJGLException e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
finally
{
lock.unlock();
}
}
});
thread.setUncaughtExceptionHandler(new UncaughtExceptionHandler()
{
public void uncaughtException(Thread t, Throwable e)
{
FMLLog.log(Level.ERROR, e, "Splash thread Exception");
threadError = e;
}
});
thread.start();
checkThreadState();
}
private static void checkThreadState()
{
if(thread.getState() == Thread.State.TERMINATED || threadError != null)
{
throw new IllegalStateException("Splash thread", threadError);
}
}
/**
* Call before you need to explicitly modify GL context state during loading.
* Resource loading doesn't usually require this call.
* Call {@link #resume()} when you're done.
* @deprecated not a stable API, will break, don't use this yet
*/
@Deprecated
public static void pause()
{
if(!enabled) return;
checkThreadState();
pause = true;
lock.lock();
try
{
d.releaseContext();
Display.getDrawable().makeCurrent();
}
catch (LWJGLException e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* @deprecated not a stable API, will break, don't use this yet
*/
@Deprecated
public static void resume()
{
if(!enabled) return;
checkThreadState();
pause = false;
try
{
Display.getDrawable().releaseContext();
d.makeCurrent();
}
catch (LWJGLException e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
lock.unlock();
}
public static void finish()
{
if(!enabled) return;
try
{
checkThreadState();
done = true;
thread.join();
d.releaseContext();
Display.getDrawable().makeCurrent();
fontTexture.delete();
logoTexture.delete();
forgeTexture.delete();
}
catch (Exception e)
{
e.printStackTrace();
if (disableSplash())
{
throw new EnhancedRuntimeException(e)
{
@Override
protected void printStackTrace(WrappedPrintStream stream)
{
stream.println("SplashProgress has detected a error loading Minecraft.");
stream.println("This can sometimes be caused by bad video drivers.");
stream.println("We have automatically disabeled the new Splash Screen in config/splash.properties.");
stream.println("Try reloading minecraft before reporting any errors.");
}
};
}
else
{
throw new EnhancedRuntimeException(e)
{
@Override
protected void printStackTrace(WrappedPrintStream stream)
{
stream.println("SplashProgress has detected a error loading Minecraft.");
stream.println("This can sometimes be caused by bad video drivers.");
stream.println("Please try disabeling the new Splash Screen in config/splash.properties.");
stream.println("After doing so, try reloading minecraft before reporting any errors.");
}
};
}
}
}
private static boolean disableSplash()
{
File configFile = new File(Minecraft.getMinecraft().mcDataDir, "config/splash.properties");
File parent = configFile.getParentFile();
if (!parent.exists())
parent.mkdirs();
FileReader r = null;
enabled = false;
config.setProperty("enabled", "false");
FileWriter w = null;
try
{
w = new FileWriter(configFile);
config.store(w, "Splash screen properties");
}
catch(IOException e)
{
FMLLog.log(Level.ERROR, e, "Could not save the splash.properties file");
return false;
}
finally
{
IOUtils.closeQuietly(w);
}
return true;
}
private static IResourcePack createResourcePack(File file)
{
if(file.isDirectory())
{
return new FolderResourcePack(file);
}
else
{
return new FileResourcePack(file);
}
}
private static final IntBuffer buf = BufferUtils.createIntBuffer(4 * 1024 * 1024);
private static class Texture
{
private final ResourceLocation location;
private final int name;
private final int width;
private final int height;
private final int frames;
private final int size;
public Texture(ResourceLocation location)
{
InputStream s = null;
try
{
this.location = location;
s = open(location);
ImageInputStream stream = ImageIO.createImageInputStream(s);
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
if(!readers.hasNext()) throw new IOException("No suitable reader found for image" + location);
ImageReader reader = readers.next();
reader.setInput(stream);
frames = reader.getNumImages(true);
BufferedImage[] images = new BufferedImage[frames];
for(int i = 0; i < frames; i++)
{
images[i] = reader.read(i);
}
reader.dispose();
int size = 1;
width = images[0].getWidth();
height = images[0].getHeight();
while((size / width) * (size / height) < frames) size *= 2;
this.size = size;
glEnable(GL_TEXTURE_2D);
synchronized(SplashProgress.class)
{
name = glGenTextures();
glBindTexture(GL_TEXTURE_2D, name);
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, size, size, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, (IntBuffer)null);
checkGLError("Texture creation");
for(int i = 0; i * (size / width) < frames; i++)
{
for(int j = 0; i * (size / width) + j < frames && j < size / width; j++)
{
buf.clear();
BufferedImage image = images[i * (size / width) + j];
for(int k = 0; k < height; k++)
{
for(int l = 0; l < width; l++)
{
buf.put(image.getRGB(l, k));
}
}
buf.position(0).limit(width * height);
glTexSubImage2D(GL_TEXTURE_2D, 0, j * width, i * height, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buf);
checkGLError("Texture uploading");
}
}
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
}
catch(IOException e)
{
e.printStackTrace();
throw new RuntimeException(e);
}
finally
{
IOUtils.closeQuietly(s);
}
}
public ResourceLocation getLocation()
{
return location;
}
public int getName()
{
return name;
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
public int getFrames()
{
return frames;
}
public int getSize()
{
return size;
}
public void bind()
{
glBindTexture(GL_TEXTURE_2D, name);
}
public void delete()
{
glDeleteTextures(name);
}
public float getU(int frame, float u)
{
return width * (frame % (size / width) + u) / size;
}
public float getV(int frame, float v)
{
return height * (frame / (size / width) + v) / size;
}
public void texCoord(int frame, float u, float v)
{
glTexCoord2f(getU(frame, u), getV(frame, v));
}
}
private static class SplashFontRenderer extends FontRenderer
{
public SplashFontRenderer()
{
super(Minecraft.getMinecraft().gameSettings, fontTexture.getLocation(), null, false);
super.onResourceManagerReload(null);
}
@Override
protected void bindTexture(ResourceLocation location)
{
if(location != locationFontTexture) throw new IllegalArgumentException();
fontTexture.bind();
}
@Override
protected InputStream getResourceInputStream(ResourceLocation location) throws IOException
{
return Minecraft.getMinecraft().mcDefaultResourcePack.getInputStream(location);
}
}
public static void drawVanillaScreen(TextureManager renderEngine) throws LWJGLException
{
if(!enabled)
{
Minecraft.getMinecraft().drawSplashScreen(renderEngine);
}
}
public static void clearVanillaResources(TextureManager renderEngine, ResourceLocation mojangLogo)
{
if(!enabled)
{
renderEngine.deleteTexture(mojangLogo);
}
}
public static void checkGLError(String where)
{
int err = GL11.glGetError();
if (err != 0)
{
throw new IllegalStateException(where + ": " + GLU.gluErrorString(err));
}
}
private static InputStream open(ResourceLocation loc) throws IOException
{
if(miscPack.resourceExists(loc))
{
return miscPack.getInputStream(loc);
}
else if(fmlPack.resourceExists(loc))
{
return fmlPack.getInputStream(loc);
}
return mcPack.getInputStream(loc);
}
}
@@ -0,0 +1,24 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
public enum ConfigGuiType
{
STRING,
INTEGER,
BOOLEAN,
DOUBLE,
COLOR,
MOD_ID,
CONFIG_CATEGORY;
}
@@ -0,0 +1,417 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.fml.client.config.GuiConfigEntries.IConfigEntry;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries.IArrayEntry;
/**
* This class's main purpose is to provide the necessary objects for a sample Config GUI for FML, although
* there may be practical uses for the objects defined here such as using the DummyCategoryElement object as a
* wrapper for a custom IGuiConfigListEntry object that opens a special screen.
*
* @author bspkrs
*/
public class DummyConfigElement implements IConfigElement
{
protected boolean isProperty = true;
protected boolean isList = false;
protected ConfigGuiType type;
protected String name;
protected String langKey;
protected Object value;
protected Object defaultValue;
protected Object[] values;
protected Object[] defaultValues;
protected String[] validValues;
protected Pattern validStringPattern;
protected Object minValue;
protected Object maxValue;
protected boolean requiresWorldRestart = false;
protected boolean requiresMcRestart = false;
protected boolean isListFixedLength = false;
protected int maxListLength = -1;
protected List<IConfigElement> childElements;
protected Class<? extends IConfigEntry> configEntryClass;
protected Class<? extends IArrayEntry> arrayEntryClass;
/**
* This class provides a Dummy Category IConfigElement. It can be used to define a custom list of GUI entries that will
* appear on the child screen or to specify a custom IGuiConfigListEntryfor a special category.
*/
public static class DummyCategoryElement extends DummyConfigElement
{
public DummyCategoryElement(String name, String langKey, List<IConfigElement> childElements)
{
this(name, langKey, childElements, (Class<? extends IConfigEntry>) null);
}
public DummyCategoryElement(String name, String langKey, Class<? extends IConfigEntry> customListEntryClass)
{
this(name, langKey, new ArrayList<IConfigElement>(), customListEntryClass);
}
public DummyCategoryElement(String name, String langKey, List<IConfigElement> childElements, Class<? extends IConfigEntry> customListEntryClass)
{
super(name, (Object) null, ConfigGuiType.CONFIG_CATEGORY, langKey);
this.childElements = childElements;
this.configEntryClass = customListEntryClass;
isProperty = false;
}
}
/**
* This class provides a dummy array-type IConfigElement.
*/
public static class DummyListElement extends DummyConfigElement
{
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, int maxListLength, Pattern validStringPattern, Object minValue, Object maxValue)
{
super(name, (Object) null, type, langKey, minValue, maxValue);
this.defaultValues = defaultValues;
this.values = defaultValues;
this.isListFixedLength = isListFixedLength;
this.maxListLength = maxListLength;
this.validStringPattern = validStringPattern;
isList = true;
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey)
{
this(name, defaultValues, type, langKey, false, -1, (Pattern) null, (Object) null, (Object) null);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength)
{
this(name, defaultValues, type, langKey, isListFixedLength, -1, (Pattern) null, (Object) null, (Object) null);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, int maxListLength)
{
this(name, defaultValues, type, langKey, false, maxListLength, (Pattern) null, (Object) null, (Object) null);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, Object minValue, Object maxValue)
{
this(name, defaultValues, type, langKey, false, -1, (Pattern) null, minValue, maxValue);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, Object minValue, Object maxValue)
{
this(name, defaultValues, type, langKey, isListFixedLength, -1, (Pattern) null, minValue, maxValue);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, int maxListLength, Object minValue, Object maxValue)
{
this(name, defaultValues, type, langKey, false, maxListLength, (Pattern) null, minValue, maxValue);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, int maxListLength, Object minValue, Object maxValue)
{
this(name, defaultValues, type, langKey, isListFixedLength, maxListLength, (Pattern) null, minValue, maxValue);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, Pattern validStringPattern)
{
this(name, defaultValues, type, langKey, false, -1, validStringPattern, (Object) null, (Object) null);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, boolean isListFixedLength, Pattern validStringPattern)
{
this(name, defaultValues, type, langKey, isListFixedLength, -1, validStringPattern, (Object) null, (Object) null);
}
public DummyListElement(String name, Object[] defaultValues, ConfigGuiType type, String langKey, int maxListLength, Pattern validStringPattern)
{
this(name, defaultValues, type, langKey, false, maxListLength, validStringPattern, (Object) null, (Object) null);
}
public DummyListElement setCustomEditListEntryClass(Class<? extends IArrayEntry> clazz)
{
this.arrayEntryClass = clazz;
return this;
}
@Override
public Object getDefault()
{
return Arrays.toString(this.defaultValues);
}
}
public DummyConfigElement(String name, Object defaultValue, ConfigGuiType type, String langKey, String[] validValues, Pattern validStringPattern, Object minValue, Object maxValue)
{
this.name = name;
this.defaultValue = defaultValue;
this.value = defaultValue;
this.type = type;
this.langKey = langKey;
this.validValues = validValues;
this.validStringPattern = validStringPattern;
if (minValue == null)
{
if (type == ConfigGuiType.INTEGER)
this.minValue = (Integer) Integer.MIN_VALUE;
else if (type == ConfigGuiType.DOUBLE)
this.minValue = (Double) (-Double.MAX_VALUE);
}
else
this.minValue = minValue;
if (maxValue == null)
{
if (type == ConfigGuiType.INTEGER)
this.maxValue = (Integer) Integer.MAX_VALUE;
else if (type == ConfigGuiType.DOUBLE)
this.maxValue = (Double) Double.MAX_VALUE;
}
else
this.maxValue = maxValue;
}
public DummyConfigElement(String name, Object defaultValue, ConfigGuiType type, String langKey, Pattern validStringPattern)
{
this(name, defaultValue, type, langKey, (String[]) null, validStringPattern, (Object) null, (Object) null);
}
public DummyConfigElement(String name, Object defaultValue, ConfigGuiType type, String langKey, String[] validValues)
{
this(name, defaultValue, type, langKey, validValues, (Pattern) null, (Object) null, (Object) null);
}
public DummyConfigElement(String name, Object defaultValue, ConfigGuiType type, String langKey)
{
this(name, defaultValue, type, langKey, (String[]) null, (Pattern) null, (Object) null, (Object) null);
}
public DummyConfigElement(String name, Object defaultValue, ConfigGuiType type, String langKey, Object minValue, Object maxValue)
{
this(name, defaultValue, type, langKey, (String[]) null, (Pattern) null, minValue, maxValue);
}
public DummyConfigElement setCustomListEntryClass(Class<? extends IConfigEntry> clazz)
{
this.configEntryClass = clazz;
return this;
}
@Override
public boolean isProperty()
{
return isProperty;
}
public IConfigElement setConfigEntryClass(Class<? extends IConfigEntry> clazz)
{
this.configEntryClass = clazz;
return this;
}
@Override
public Class<? extends IConfigEntry> getConfigEntryClass()
{
return configEntryClass;
}
public IConfigElement setArrayEntryClass(Class<? extends IArrayEntry> clazz)
{
this.arrayEntryClass = clazz;
return this;
}
@Override
public Class<? extends IArrayEntry> getArrayEntryClass()
{
return arrayEntryClass;
}
@Override
public String getName()
{
return name;
}
@Override
public String getQualifiedName()
{
return name;
}
@Override
public String getLanguageKey()
{
return langKey;
}
@Override
public String getComment()
{
return I18n.format(langKey + ".tooltip");
}
@Override
public List<IConfigElement> getChildElements()
{
return childElements;
}
@Override
public ConfigGuiType getType()
{
return type;
}
@Override
public boolean isList()
{
return isList;
}
@Override
public boolean isListLengthFixed()
{
return this.isListFixedLength;
}
@Override
public int getMaxListLength()
{
return this.maxListLength;
}
@Override
public boolean isDefault()
{
if (isProperty)
{
if (!isList)
{
if (value != null)
return value.equals(defaultValue);
else
return defaultValue == null;
}
else
{
return Arrays.deepEquals(values, defaultValues);
}
}
return true;
}
@Override
public Object getDefault()
{
return defaultValue;
}
@Override
public Object[] getDefaults()
{
return defaultValues;
}
@Override
public void setToDefault()
{
if (isList)
this.values = Arrays.copyOf(this.defaultValues, this.defaultValues.length);
else
this.value = defaultValue;
}
public IConfigElement setRequiresWorldRestart(boolean requiresWorldRestart)
{
this.requiresWorldRestart = requiresWorldRestart;
return this;
}
@Override
public boolean requiresWorldRestart()
{
return requiresWorldRestart;
}
@Override
public boolean showInGui()
{
return true;
}
public IConfigElement setRequiresMcRestart(boolean requiresMcRestart)
{
this.requiresMcRestart = this.requiresWorldRestart = requiresMcRestart;
return this;
}
@Override
public boolean requiresMcRestart()
{
return requiresMcRestart;
}
@Override
public String[] getValidValues()
{
return validValues;
}
@Override
public Pattern getValidationPattern()
{
return validStringPattern;
}
@Override
public Object get()
{
return value;
}
@Override
public Object[] getList()
{
return values;
}
@Override
public void set(Object value)
{
defaultValue = value;
}
@Override
public void set(Object[] aVal)
{
defaultValues = aVal;
}
@Override
public Object getMinValue()
{
return minValue;
}
@Override
public Object getMaxValue()
{
return maxValue;
}
}
@@ -0,0 +1,77 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
/**
* This class provides a button that fixes several bugs present in the vanilla GuiButton drawing code.
* The gist of it is that it allows buttons of any size without gaps in the graphics and with the
* borders drawn properly. It also prevents button text from extending out of the sides of the button by
* trimming the end of the string and adding an ellipsis.<br/><br/>
*
* The code that handles drawing the button is in GuiUtils.
*
* @author bspkrs
*/
public class GuiButtonExt extends GuiButton
{
public GuiButtonExt(int id, int xPos, int yPos, String displayString)
{
super(id, xPos, yPos, displayString);
}
public GuiButtonExt(int id, int xPos, int yPos, int width, int height, String displayString)
{
super(id, xPos, yPos, width, height, displayString);
}
/**
* Draws this button to the screen.
*/
@Override
public void drawButton(Minecraft mc, int mouseX, int mouseY)
{
if (this.visible)
{
this.hovered = mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.width && mouseY < this.yPosition + this.height;
int k = this.getHoverState(this.hovered);
GuiUtils.drawContinuousTexturedBox(buttonTextures, this.xPosition, this.yPosition, 0, 46 + k * 20, this.width, this.height, 200, 20, 2, 3, 2, 2, this.zLevel);
this.mouseDragged(mc, mouseX, mouseY);
int color = 14737632;
if (packedFGColour != 0)
{
color = packedFGColour;
}
else if (!this.enabled)
{
color = 10526880;
}
else if (this.hovered)
{
color = 16777120;
}
String buttonText = this.displayString;
int strWidth = mc.fontRendererObj.getStringWidth(buttonText);
int ellipsisWidth = mc.fontRendererObj.getStringWidth("...");
if (strWidth > width - 6 && strWidth > ellipsisWidth)
buttonText = mc.fontRendererObj.trimStringToWidth(buttonText, width - 6 - ellipsisWidth).trim() + "...";
this.drawCenteredString(mc.fontRendererObj, buttonText, this.xPosition + this.width / 2, this.yPosition + (this.height - 8) / 2, color);
}
}
}
@@ -0,0 +1,84 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
/**
* This class provides a checkbox style control.
*
* @author bspkrs
*/
public class GuiCheckBox extends GuiButton
{
private boolean isChecked;
private int boxWidth;
public GuiCheckBox(int id, int xPos, int yPos, String displayString, boolean isChecked)
{
super(id, xPos, yPos, displayString);
this.isChecked = isChecked;
this.boxWidth = 11;
this.height = 11;
this.width = this.boxWidth + 2 + Minecraft.getMinecraft().fontRendererObj.getStringWidth(displayString);
}
@Override
public void drawButton(Minecraft mc, int mouseX, int mouseY)
{
if (this.visible)
{
this.hovered = mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.boxWidth && mouseY < this.yPosition + this.height;
GuiUtils.drawContinuousTexturedBox(buttonTextures, this.xPosition, this.yPosition, 0, 46, this.boxWidth, this.height, 200, 20, 2, 3, 2, 2, this.zLevel);
this.mouseDragged(mc, mouseX, mouseY);
int color = 14737632;
if (packedFGColour != 0)
{
color = packedFGColour;
}
else if (!this.enabled)
{
color = 10526880;
}
if (this.isChecked)
this.drawCenteredString(mc.fontRendererObj, "x", this.xPosition + this.boxWidth / 2 + 1, this.yPosition + 1, 14737632);
this.drawString(mc.fontRendererObj, displayString, xPosition + this.boxWidth + 2, yPosition + 2, color);
}
}
@Override
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY)
{
if (this.enabled && this.visible && mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.width && mouseY < this.yPosition + this.height)
{
this.isChecked = !this.isChecked;
return true;
}
return false;
}
public boolean isChecked()
{
return this.isChecked;
}
public void setIsChecked(boolean isChecked)
{
this.isChecked = isChecked;
}
}
@@ -0,0 +1,352 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import static net.minecraftforge.fml.client.config.GuiUtils.RESET_CHAR;
import static net.minecraftforge.fml.client.config.GuiUtils.UNDO_CHAR;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.ChatComponentText;
import net.minecraftforge.fml.client.config.GuiConfigEntries.IConfigEntry;
import net.minecraftforge.fml.client.event.ConfigChangedEvent;
import net.minecraftforge.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent;
import net.minecraftforge.fml.client.event.ConfigChangedEvent.PostConfigChangedEvent;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.eventhandler.Event.Result;
import org.lwjgl.input.Keyboard;
/**
* This class is the base GuiScreen for all config GUI screens. It can be extended by mods to provide the top-level config screen
* that will be called when the Config button is clicked from the Main Menu Mods list.
*
* @author bspkrs
*/
public class GuiConfig extends GuiScreen
{
/**
* A reference to the screen object that created this. Used for navigating between screens.
*/
public final GuiScreen parentScreen;
public String title = "Config GUI";
public String titleLine2;
public final List<IConfigElement> configElements;
public final List<IConfigEntry> initEntries;
public GuiConfigEntries entryList;
protected GuiButtonExt btnDefaultAll;
protected GuiButtonExt btnUndoAll;
protected GuiCheckBox chkApplyGlobally;
public final String modID;
/**
* When set to a non-null value the OnConfigChanged and PostConfigChanged events will be posted when the Done button is pressed
* if any configElements were changed (includes child screens). If not defined, the events will be posted if the parent gui is null
* or if the parent gui is not an instance of GuiConfig.
*/
public final String configID;
public final boolean isWorldRunning;
public final boolean allRequireWorldRestart;
public final boolean allRequireMcRestart;
public boolean needsRefresh = true;
protected HoverChecker undoHoverChecker;
protected HoverChecker resetHoverChecker;
protected HoverChecker checkBoxHoverChecker;
/**
* GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. If a non-null value is passed for configID,
* the OnConfigChanged and PostConfigChanged events will be posted when the Done button is pressed if any configElements were changed
* (includes child screens). If configID is not defined, the events will be posted if the parent gui is null or if the parent gui
* is not an instance of GuiConfig.
*
* @param parentScreen the parent GuiScreen object
* @param configElements a List of IConfigElement objects
* @param modID the mod ID for the mod whose config settings will be edited
* @param configID an identifier that will be passed to the OnConfigChanged and PostConfigChanged events. Setting this value will force
* the save action to be called when the Done button is pressed on this screen if any configElements were changed.
* @param allRequireWorldRestart send true if all configElements on this screen require a world restart
* @param allRequireMcRestart send true if all configElements on this screen require MC to be restarted
* @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being
* edited.
*/
public GuiConfig(GuiScreen parentScreen, List<IConfigElement> configElements, String modID, String configID,
boolean allRequireWorldRestart, boolean allRequireMcRestart, String title)
{
this(parentScreen, configElements, modID, configID, allRequireWorldRestart, allRequireMcRestart, title, null);
}
/**
* GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. This constructor passes null for configID.
* If configID is not defined, the events will be posted if the parent gui is null or if the parent gui is not an instance of GuiConfig.
*
* @param parentScreen the parent GuiScreen object
* @param configElements a List of IConfigElement objects
* @param modID the mod ID for the mod whose config settings will be edited
* @param allRequireWorldRestart send true if all configElements on this screen require a world restart
* @param allRequireMcRestart send true if all configElements on this screen require MC to be restarted
* @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being
* edited.
*/
public GuiConfig(GuiScreen parentScreen, List<IConfigElement> configElements, String modID,
boolean allRequireWorldRestart, boolean allRequireMcRestart, String title)
{
this(parentScreen, configElements, modID, null, allRequireWorldRestart, allRequireMcRestart, title, null);
}
/**
* GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. This constructor passes null for configID.
* If configID is not defined, the events will be posted if the parent gui is null or if the parent gui is not an instance of GuiConfig.
*
* @param parentScreen the parent GuiScreen object
* @param configElements a List of IConfigElement objects
* @param modID the mod ID for the mod whose config settings will be edited
* @param allRequireWorldRestart send true if all configElements on this screen require a world restart
* @param allRequireMcRestart send true if all configElements on this screen require MC to be restarted
* @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being
* edited.
* @param titleLine2 the desired title second line for this screen. Typically this is used to send the category name of the category
* currently being edited.
*/
public GuiConfig(GuiScreen parentScreen, List<IConfigElement> configElements, String modID,
boolean allRequireWorldRestart, boolean allRequireMcRestart, String title, String titleLine2)
{
this(parentScreen, configElements, modID, null, allRequireWorldRestart, allRequireMcRestart, title, titleLine2);
}
/**
* GuiConfig constructor that will use ConfigChangedEvent when editing is concluded. titleLine2 is specified in this constructor.
* If a non-null value is passed for configID, the OnConfigChanged and PostConfigChanged events will be posted when the Done button is
* pressed if any configElements were changed (includes child screens). If configID is not defined, the events will be posted if the parent
* gui is null or if the parent gui is not an instance of GuiConfig.
*
* @param parentScreen the parent GuiScreen object
* @param configElements a List of IConfigElement objects
* @param modID the mod ID for the mod whose config settings will be edited
* @param configID an identifier that will be passed to the OnConfigChanged and PostConfigChanged events
* @param allRequireWorldRestart send true if all configElements on this screen require a world restart
* @param allRequireMcRestart send true if all configElements on this screen require MC to be restarted
* @param title the desired title for this screen. For consistency it is recommended that you pass the path of the config file being
* edited.
* @param titleLine2 the desired title second line for this screen. Typically this is used to send the category name of the category
* currently being edited.
*/
public GuiConfig(GuiScreen parentScreen, List<IConfigElement> configElements, String modID, String configID,
boolean allRequireWorldRestart, boolean allRequireMcRestart, String title, String titleLine2)
{
this.mc = Minecraft.getMinecraft();
this.parentScreen = parentScreen;
this.configElements = configElements;
this.entryList = new GuiConfigEntries(this, mc);
this.initEntries = new ArrayList<IConfigEntry>(entryList.listEntries);
this.allRequireWorldRestart = allRequireWorldRestart;
this.allRequireMcRestart = allRequireMcRestart;
this.modID = modID;
this.configID = configID;
this.isWorldRunning = mc.theWorld != null;
if (title != null)
this.title = title;
this.titleLine2 = titleLine2;
if (this.titleLine2 != null && this.titleLine2.startsWith(" > "))
this.titleLine2 = this.titleLine2.replaceFirst(" > ", "");
}
public static String getAbridgedConfigPath(String path)
{
Minecraft mc = Minecraft.getMinecraft();
if (mc.mcDataDir.getAbsolutePath().endsWith("."))
return path.replace("\\", "/").replace(mc.mcDataDir.getAbsolutePath().replace("\\", "/").substring(0, mc.mcDataDir.getAbsolutePath().length() - 1), "/.minecraft/");
else
return path.replace("\\", "/").replace(mc.mcDataDir.getAbsolutePath().replace("\\", "/"), "/.minecraft");
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
Keyboard.enableRepeatEvents(true);
if (this.entryList == null || this.needsRefresh)
{
this.entryList = new GuiConfigEntries(this, mc);
this.needsRefresh = false;
}
int undoGlyphWidth = mc.fontRendererObj.getStringWidth(UNDO_CHAR) * 2;
int resetGlyphWidth = mc.fontRendererObj.getStringWidth(RESET_CHAR) * 2;
int doneWidth = Math.max(mc.fontRendererObj.getStringWidth(I18n.format("gui.done")) + 20, 100);
int undoWidth = mc.fontRendererObj.getStringWidth(" " + I18n.format("fml.configgui.tooltip.undoChanges")) + undoGlyphWidth + 20;
int resetWidth = mc.fontRendererObj.getStringWidth(" " + I18n.format("fml.configgui.tooltip.resetToDefault")) + resetGlyphWidth + 20;
int checkWidth = mc.fontRendererObj.getStringWidth(I18n.format("fml.configgui.applyGlobally")) + 13;
int buttonWidthHalf = (doneWidth + 5 + undoWidth + 5 + resetWidth + 5 + checkWidth) / 2;
this.buttonList.add(new GuiButtonExt(2000, this.width / 2 - buttonWidthHalf, this.height - 29, doneWidth, 20, I18n.format("gui.done")));
this.buttonList.add(this.btnDefaultAll = new GuiUnicodeGlyphButton(2001, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5,
this.height - 29, resetWidth, 20, " " + I18n.format("fml.configgui.tooltip.resetToDefault"), RESET_CHAR, 2.0F));
this.buttonList.add(btnUndoAll = new GuiUnicodeGlyphButton(2002, this.width / 2 - buttonWidthHalf + doneWidth + 5,
this.height - 29, undoWidth, 20, " " + I18n.format("fml.configgui.tooltip.undoChanges"), UNDO_CHAR, 2.0F));
this.buttonList.add(chkApplyGlobally = new GuiCheckBox(2003, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5 + resetWidth + 5,
this.height - 24, I18n.format("fml.configgui.applyGlobally"), false));
this.undoHoverChecker = new HoverChecker(this.btnUndoAll, 800);
this.resetHoverChecker = new HoverChecker(this.btnDefaultAll, 800);
this.checkBoxHoverChecker = new HoverChecker(chkApplyGlobally, 800);
this.entryList.initGui();
}
@Override
public void onGuiClosed()
{
this.entryList.onGuiClosed();
if (this.configID != null && this.parentScreen instanceof GuiConfig)
{
GuiConfig parentGuiConfig = (GuiConfig) this.parentScreen;
parentGuiConfig.needsRefresh = true;
parentGuiConfig.initGui();
}
if (!(this.parentScreen instanceof GuiConfig))
Keyboard.enableRepeatEvents(false);
}
@Override
protected void actionPerformed(GuiButton button)
{
if (button.id == 2000)
{
boolean flag = true;
try
{
if ((configID != null || this.parentScreen == null || !(this.parentScreen instanceof GuiConfig))
&& (this.entryList.hasChangedEntry(true)))
{
boolean requiresMcRestart = this.entryList.saveConfigElements();
if (Loader.isModLoaded(modID))
{
ConfigChangedEvent event = new OnConfigChangedEvent(modID, configID, isWorldRunning, requiresMcRestart);
FMLCommonHandler.instance().bus().post(event);
if (!event.getResult().equals(Result.DENY))
FMLCommonHandler.instance().bus().post(new PostConfigChangedEvent(modID, configID, isWorldRunning, requiresMcRestart));
if (requiresMcRestart)
{
flag = false;
mc.displayGuiScreen(new GuiMessageDialog(parentScreen, "fml.configgui.gameRestartTitle",
new ChatComponentText(I18n.format("fml.configgui.gameRestartRequired")), "fml.configgui.confirmRestartMessage"));
}
if (this.parentScreen instanceof GuiConfig)
((GuiConfig) this.parentScreen).needsRefresh = true;
}
}
}
catch (Throwable e)
{
e.printStackTrace();
}
if (flag)
this.mc.displayGuiScreen(this.parentScreen);
}
else if (button.id == 2001)
{
this.entryList.setAllToDefault(this.chkApplyGlobally.isChecked());
}
else if (button.id == 2002)
{
this.entryList.undoAllChanges(this.chkApplyGlobally.isChecked());
}
}
public void handleMouseInput() throws IOException
{
super.handleMouseInput();
this.entryList.handleMouseInput();
}
@Override
protected void mouseClicked(int x, int y, int mouseEvent) throws IOException
{
if (mouseEvent != 0 || !this.entryList.mouseClicked(x, y, mouseEvent))
{
this.entryList.mouseClickedPassThru(x, y, mouseEvent);
super.mouseClicked(x, y, mouseEvent);
}
}
@Override
protected void mouseReleased(int x, int y, int mouseEvent)
{
if (mouseEvent != 0 || !this.entryList.mouseReleased(x, y, mouseEvent))
{
super.mouseReleased(x, y, mouseEvent);
}
}
@Override
protected void keyTyped(char eventChar, int eventKey)
{
if (eventKey == Keyboard.KEY_ESCAPE)
this.mc.displayGuiScreen(parentScreen);
else
this.entryList.keyTyped(eventChar, eventKey);
}
@Override
public void updateScreen()
{
super.updateScreen();
this.entryList.updateScreen();
}
@Override
public void drawScreen(int mouseX, int mouseY, float partialTicks)
{
this.drawDefaultBackground();
this.entryList.drawScreen(mouseX, mouseY, partialTicks);
this.drawCenteredString(this.fontRendererObj, this.title, this.width / 2, 8, 16777215);
String title2 = this.titleLine2;
if (title2 != null)
{
int strWidth = mc.fontRendererObj.getStringWidth(title2);
int elipsisWidth = mc.fontRendererObj.getStringWidth("...");
if (strWidth > width - 6 && strWidth > elipsisWidth)
title2 = mc.fontRendererObj.trimStringToWidth(title2, width - 6 - elipsisWidth).trim() + "...";
this.drawCenteredString(this.fontRendererObj, title2, this.width / 2, 18, 16777215);
}
this.btnUndoAll.enabled = this.entryList.areAnyEntriesEnabled(this.chkApplyGlobally.isChecked()) && this.entryList.hasChangedEntry(this.chkApplyGlobally.isChecked());
this.btnDefaultAll.enabled = this.entryList.areAnyEntriesEnabled(this.chkApplyGlobally.isChecked()) && !this.entryList.areAllEntriesDefault(this.chkApplyGlobally.isChecked());
super.drawScreen(mouseX, mouseY, partialTicks);
this.entryList.drawScreenPost(mouseX, mouseY, partialTicks);
if (this.undoHoverChecker.checkHover(mouseX, mouseY))
this.drawToolTip(this.mc.fontRendererObj.listFormattedStringToWidth(I18n.format("fml.configgui.tooltip.undoAll"), 300), mouseX, mouseY);
if (this.resetHoverChecker.checkHover(mouseX, mouseY))
this.drawToolTip(this.mc.fontRendererObj.listFormattedStringToWidth(I18n.format("fml.configgui.tooltip.resetAll"), 300), mouseX, mouseY);
if (this.checkBoxHoverChecker.checkHover(mouseX, mouseY))
this.drawToolTip(this.mc.fontRendererObj.listFormattedStringToWidth(I18n.format("fml.configgui.tooltip.applyGlobally"), 300), mouseX, mouseY);
}
@SuppressWarnings("rawtypes")
public void drawToolTip(List stringList, int x, int y)
{
this.drawHoveringText(stringList, x, y);
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,214 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import static net.minecraftforge.fml.client.config.GuiUtils.RESET_CHAR;
import static net.minecraftforge.fml.client.config.GuiUtils.UNDO_CHAR;
import org.lwjgl.input.Keyboard;
/**
* This class is the base screen used for editing an array-type property. It provides a list of array entries for the user to edit.
* This screen is invoked from a GuiConfig screen by controls that use the EditListPropEntry IGuiConfigListEntry object.
*
* @author bspkrs
*/
public class GuiEditArray extends GuiScreen
{
protected GuiScreen parentScreen;
protected IConfigElement configElement;
protected GuiEditArrayEntries entryList;
protected GuiButtonExt btnUndoChanges, btnDefault, btnDone;
protected String title;
protected String titleLine2;
protected String titleLine3;
protected int slotIndex;
protected final Object[] beforeValues;
protected Object[] currentValues;
protected HoverChecker tooltipHoverChecker;
@SuppressWarnings("rawtypes")
protected List toolTip;
protected boolean enabled;
@SuppressWarnings("rawtypes")
public GuiEditArray(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Object[] currentValues, boolean enabled)
{
this.mc = Minecraft.getMinecraft();
this.parentScreen = parentScreen;
this.configElement = configElement;
this.slotIndex = slotIndex;
this.beforeValues = currentValues;
this.currentValues = currentValues;
this.toolTip = new ArrayList();
this.enabled = enabled;
String propName = I18n.format(configElement.getLanguageKey());
String comment;
comment = I18n.format(configElement.getLanguageKey() + ".tooltip",
"\n" + EnumChatFormatting.AQUA, configElement.getDefault(), configElement.getMinValue(), configElement.getMaxValue());
if (!comment.equals(configElement.getLanguageKey() + ".tooltip"))
toolTip = mc.fontRendererObj.listFormattedStringToWidth(
EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.YELLOW + comment, 300);
else if (configElement.getComment() != null && !configElement.getComment().trim().isEmpty())
toolTip = mc.fontRendererObj.listFormattedStringToWidth(
EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.YELLOW + configElement.getComment(), 300);
else
toolTip = mc.fontRendererObj.listFormattedStringToWidth(
EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.RED + "No tooltip defined.", 300);
if (parentScreen instanceof GuiConfig)
{
this.title = ((GuiConfig) parentScreen).title;
if (((GuiConfig) parentScreen).titleLine2 != null)
{
this.titleLine2 = ((GuiConfig) parentScreen).titleLine2;
this.titleLine3 = I18n.format(configElement.getLanguageKey());
}
else
this.titleLine2 = I18n.format(configElement.getLanguageKey());
this.tooltipHoverChecker = new HoverChecker(28, 37, 0, parentScreen.width, 800);
}
else
{
this.title = I18n.format(configElement.getLanguageKey());
this.tooltipHoverChecker = new HoverChecker(8, 17, 0, parentScreen.width, 800);
}
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
this.entryList = new GuiEditArrayEntries(this, this.mc, this.configElement, this.beforeValues, this.currentValues);
int undoGlyphWidth = mc.fontRendererObj.getStringWidth(UNDO_CHAR) * 2;
int resetGlyphWidth = mc.fontRendererObj.getStringWidth(RESET_CHAR) * 2;
int doneWidth = Math.max(mc.fontRendererObj.getStringWidth(I18n.format("gui.done")) + 20, 100);
int undoWidth = mc.fontRendererObj.getStringWidth(" " + I18n.format("fml.configgui.tooltip.undoChanges")) + undoGlyphWidth + 20;
int resetWidth = mc.fontRendererObj.getStringWidth(" " + I18n.format("fml.configgui.tooltip.resetToDefault")) + resetGlyphWidth + 20;
int buttonWidthHalf = (doneWidth + 5 + undoWidth + 5 + resetWidth) / 2;
this.buttonList.add(btnDone = new GuiButtonExt(2000, this.width / 2 - buttonWidthHalf, this.height - 29, doneWidth, 20, I18n.format("gui.done")));
this.buttonList.add(btnDefault = new GuiUnicodeGlyphButton(2001, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5,
this.height - 29, resetWidth, 20, " " + I18n.format("fml.configgui.tooltip.resetToDefault"), RESET_CHAR, 2.0F));
this.buttonList.add(btnUndoChanges = new GuiUnicodeGlyphButton(2002, this.width / 2 - buttonWidthHalf + doneWidth + 5,
this.height - 29, undoWidth, 20, " " + I18n.format("fml.configgui.tooltip.undoChanges"), UNDO_CHAR, 2.0F));
}
@Override
protected void actionPerformed(GuiButton button)
{
if (button.id == 2000)
{
try
{
this.entryList.saveListChanges();
}
catch (Throwable e)
{
e.printStackTrace();
}
this.mc.displayGuiScreen(this.parentScreen);
}
else if (button.id == 2001)
{
this.currentValues = configElement.getDefaults();
this.entryList = new GuiEditArrayEntries(this, this.mc, this.configElement, this.beforeValues, this.currentValues);
}
else if (button.id == 2002)
{
this.currentValues = Arrays.copyOf(beforeValues, beforeValues.length);
this.entryList = new GuiEditArrayEntries(this, this.mc, this.configElement, this.beforeValues, this.currentValues);
}
}
public void handleMouseInput() throws IOException
{
super.handleMouseInput();
this.entryList.handleMouseInput();
}
@Override
protected void mouseClicked(int x, int y, int mouseEvent) throws IOException
{
if (mouseEvent != 0 || !this.entryList.mouseClicked(x, y, mouseEvent))
{
this.entryList.mouseClickedPassThru(x, y, mouseEvent);
super.mouseClicked(x, y, mouseEvent);
}
}
@Override
protected void mouseReleased(int x, int y, int mouseEvent)
{
if (mouseEvent != 0 || !this.entryList.mouseReleased(x, y, mouseEvent))
{
super.mouseReleased(x, y, mouseEvent);
}
}
@Override
protected void keyTyped(char eventChar, int eventKey)
{
if (eventKey == Keyboard.KEY_ESCAPE)
this.mc.displayGuiScreen(parentScreen);
else
this.entryList.keyTyped(eventChar, eventKey);
}
@Override
public void updateScreen()
{
super.updateScreen();
this.entryList.updateScreen();
}
@Override
public void drawScreen(int par1, int par2, float par3)
{
this.drawDefaultBackground();
this.entryList.drawScreen(par1, par2, par3);
this.drawCenteredString(this.fontRendererObj, this.title, this.width / 2, 8, 16777215);
if (this.titleLine2 != null)
this.drawCenteredString(this.fontRendererObj, this.titleLine2, this.width / 2, 18, 16777215);
if (this.titleLine3 != null)
this.drawCenteredString(this.fontRendererObj, this.titleLine3, this.width / 2, 28, 16777215);
this.btnDone.enabled = this.entryList.isListSavable();
this.btnDefault.enabled = enabled && !this.entryList.isDefault();
this.btnUndoChanges.enabled = enabled && this.entryList.isChanged();
super.drawScreen(par1, par2, par3);
this.entryList.drawScreenPost(par1, par2, par3);
if (this.tooltipHoverChecker != null && this.tooltipHoverChecker.checkHover(par1, par2))
drawToolTip(this.toolTip, par1, par2);
}
@SuppressWarnings("rawtypes")
public void drawToolTip(List stringList, int x, int y)
{
this.drawHoveringText(stringList, x, y);
}
}
@@ -0,0 +1,674 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiListExtended;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import net.minecraftforge.fml.client.config.GuiConfigEntries.ArrayEntry;
import net.minecraftforge.fml.common.FMLLog;
import org.lwjgl.input.Keyboard;
import static net.minecraftforge.fml.client.config.GuiUtils.INVALID;
import static net.minecraftforge.fml.client.config.GuiUtils.VALID;
/**
* This class implements the scrolling list functionality of the GuiEditList screen. It also provides all the default controls
* for editing array-type properties.
*
* @author bspkrs
*/
@SuppressWarnings("rawtypes")
public class GuiEditArrayEntries extends GuiListExtended
{
protected GuiEditArray owningGui;
public Minecraft mc;
public IConfigElement configElement;
public List<IArrayEntry> listEntries;
public boolean isDefault;
public boolean isChanged;
public boolean canAddMoreEntries;
public final int controlWidth;
public final Object[] beforeValues;
public Object[] currentValues;
public GuiEditArrayEntries(GuiEditArray parent, Minecraft mc, IConfigElement configElement, Object[] beforeValues, Object[] currentValues)
{
super(mc, parent.width, parent.height, parent.titleLine2 != null ? (parent.titleLine3 != null ? 43 : 33) : 23, parent.height - 32, 20);
this.owningGui = parent;
this.mc = mc;
this.configElement = configElement;
this.beforeValues = beforeValues;
this.currentValues = currentValues;
this.setShowSelectionBox(false);
this.isChanged = !Arrays.deepEquals(beforeValues, currentValues);
this.isDefault = Arrays.deepEquals(currentValues, configElement.getDefaults());
this.canAddMoreEntries = !configElement.isListLengthFixed() && (configElement.getMaxListLength() == -1 || currentValues.length < configElement.getMaxListLength());
listEntries = new ArrayList<IArrayEntry>();
controlWidth = (parent.width / 2) - (configElement.isListLengthFixed() ? 0 : 48);
if (configElement.isList() && configElement.getArrayEntryClass() != null)
{
Class<? extends IArrayEntry> clazz = configElement.getArrayEntryClass();
for (Object value : currentValues)
{
try
{
listEntries.add(clazz.getConstructor(GuiEditArray.class, GuiEditArrayEntries.class, IConfigElement.class, Object.class)
.newInstance(this.owningGui, this, configElement, value.toString()));
}
catch (Throwable e)
{
FMLLog.severe("There was a critical error instantiating the custom IGuiEditListEntry for property %s.", configElement.getName());
e.printStackTrace();
}
}
}
else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.BOOLEAN))
for (Object value : currentValues)
listEntries.add(new BooleanEntry(this.owningGui, this, configElement, Boolean.valueOf(value.toString())));
else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.INTEGER))
for (Object value : currentValues)
listEntries.add(new IntegerEntry(this.owningGui, this, configElement, Integer.parseInt(value.toString())));
else if (configElement.isList() && configElement.getType().equals(ConfigGuiType.DOUBLE))
for (Object value : currentValues)
listEntries.add(new DoubleEntry(this.owningGui, this, configElement, Double.parseDouble(value.toString())));
else if (configElement.isList())
for (Object value : currentValues)
listEntries.add(new StringEntry(this.owningGui, this, configElement, value.toString()));
if (!configElement.isListLengthFixed())
listEntries.add(new BaseEntry(this.owningGui, this, configElement));
}
@Override
protected int getScrollBarX()
{
return width - (width / 4);
}
/**
* Gets the width of the list
*/
@Override
public int getListWidth()
{
return owningGui.width;
}
@Override
public IArrayEntry getListEntry(int index)
{
return listEntries.get(index);
}
@Override
protected int getSize()
{
return listEntries.size();
}
public void addNewEntry(int index)
{
if (configElement.isList() && configElement.getType() == ConfigGuiType.BOOLEAN)
listEntries.add(index, new BooleanEntry(this.owningGui, this, this.configElement, Boolean.valueOf(true)));
else if (configElement.isList() && configElement.getType() == ConfigGuiType.INTEGER)
listEntries.add(index, new IntegerEntry(this.owningGui, this, this.configElement, 0));
else if (configElement.isList() && configElement.getType() == ConfigGuiType.DOUBLE)
listEntries.add(index, new DoubleEntry(this.owningGui, this, this.configElement, 0.0D));
else if (configElement.isList())
listEntries.add(index, new StringEntry(this.owningGui, this, this.configElement, ""));
this.canAddMoreEntries = !configElement.isListLengthFixed()
&& (configElement.getMaxListLength() == -1 || this.listEntries.size() - 1 < configElement.getMaxListLength());
keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_END);
}
public void removeEntry(int index)
{
this.listEntries.remove(index);
this.canAddMoreEntries = !configElement.isListLengthFixed()
&& (configElement.getMaxListLength() == -1 || this.listEntries.size() - 1 < configElement.getMaxListLength());
keyTyped((char) Keyboard.CHAR_NONE, Keyboard.KEY_END);
}
public boolean isChanged()
{
return isChanged;
}
public boolean isDefault()
{
return isDefault;
}
public void recalculateState()
{
isDefault = true;
isChanged = false;
int listLength = configElement.isListLengthFixed() ? listEntries.size() : listEntries.size() - 1;
if (listLength != configElement.getDefaults().length)
{
isDefault = false;
}
if (listLength != beforeValues.length)
{
isChanged = true;
}
if (isDefault)
for (int i = 0; i < listLength; i++)
if (!configElement.getDefaults()[i].equals(listEntries.get(i).getValue()))
isDefault = false;
if (!isChanged)
for (int i = 0; i < listLength; i++)
if (!beforeValues[i].equals(listEntries.get(i).getValue()))
isChanged = true;
}
protected void keyTyped(char eventChar, int eventKey)
{
for (IArrayEntry entry : this.listEntries)
entry.keyTyped(eventChar, eventKey);
recalculateState();
}
protected void updateScreen()
{
for (IArrayEntry entry : this.listEntries)
entry.updateCursorCounter();
}
protected void mouseClickedPassThru(int x, int y, int mouseEvent)
{
for (IArrayEntry entry : this.listEntries)
entry.mouseClicked(x, y, mouseEvent);
}
protected boolean isListSavable()
{
for (IArrayEntry entry : this.listEntries)
if (!entry.isValueSavable())
return false;
return true;
}
protected void saveListChanges()
{
int listLength = configElement.isListLengthFixed() ? listEntries.size() : listEntries.size() - 1;
if (owningGui.slotIndex != -1 && owningGui.parentScreen != null
&& owningGui.parentScreen instanceof GuiConfig
&& ((GuiConfig) owningGui.parentScreen).entryList.getListEntry(owningGui.slotIndex) instanceof ArrayEntry)
{
ArrayEntry entry = (ArrayEntry) ((GuiConfig) owningGui.parentScreen).entryList.getListEntry(owningGui.slotIndex);
Object[] ao = new Object[listLength];
for (int i = 0; i < listLength; i++)
ao[i] = listEntries.get(i).getValue();
entry.setListFromChildScreen(ao);
}
else
{
if (configElement.isList() && configElement.getType() == ConfigGuiType.BOOLEAN)
{
Boolean[] abol = new Boolean[listLength];
for (int i = 0; i < listLength; i++)
abol[i] = Boolean.valueOf(listEntries.get(i).getValue().toString());
configElement.set(abol);
}
else if (configElement.isList() && configElement.getType() == ConfigGuiType.INTEGER)
{
Integer[] ai = new Integer[listLength];
for (int i = 0; i < listLength; i++)
ai[i] = Integer.valueOf(listEntries.get(i).getValue().toString());
configElement.set(ai);
}
else if (configElement.isList() && configElement.getType() == ConfigGuiType.DOUBLE)
{
Double[] ad = new Double[listLength];
for (int i = 0; i < listLength; i++)
ad[i] = Double.valueOf(listEntries.get(i).getValue().toString());
configElement.set(ad);
}
else if (configElement.isList())
{
String[] as = new String[listLength];
for (int i = 0; i < listLength; i++)
as[i] = listEntries.get(i).getValue().toString();
configElement.set(as);
}
}
}
protected void drawScreenPost(int mouseX, int mouseY, float f)
{
for (IArrayEntry entry : this.listEntries)
entry.drawToolTip(mouseX, mouseY);
}
/**
* IGuiListEntry Inner Classes
*/
public static class DoubleEntry extends StringEntry
{
public DoubleEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Double value)
{
super(owningScreen, owningEntryList, configElement, value);
this.isValidated = true;
}
@Override
public void keyTyped(char eventChar, int eventKey)
{
if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
|| eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
{
String validChars = "0123456789";
String before = this.textFieldValue.getText();
if (validChars.contains(String.valueOf(eventChar)) ||
(!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-')
|| (!before.contains(".") && eventChar == '.')
|| eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
|| eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey);
if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-"))
{
try
{
double value = Double.parseDouble(textFieldValue.getText().trim());
if (value < Double.valueOf(configElement.getMinValue().toString()) || value > Double.valueOf(configElement.getMaxValue().toString()))
this.isValidValue = false;
else
this.isValidValue = true;
}
catch (Throwable e)
{
this.isValidValue = false;
}
}
else
this.isValidValue = false;
}
}
@Override
public Double getValue()
{
try
{
return Double.valueOf(this.textFieldValue.getText().trim());
}
catch (Throwable e)
{
return Double.MAX_VALUE;
}
}
}
public static class IntegerEntry extends StringEntry
{
public IntegerEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Integer value)
{
super(owningScreen, owningEntryList, configElement, value);
this.isValidated = true;
}
@Override
public void keyTyped(char eventChar, int eventKey)
{
if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
|| eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
{
String validChars = "0123456789";
String before = this.textFieldValue.getText();
if (validChars.contains(String.valueOf(eventChar))
|| (!before.startsWith("-") && this.textFieldValue.getCursorPosition() == 0 && eventChar == '-')
|| eventKey == Keyboard.KEY_BACK || eventKey == Keyboard.KEY_DELETE
|| eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT || eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey);
if (!textFieldValue.getText().trim().isEmpty() && !textFieldValue.getText().trim().equals("-"))
{
try
{
long value = Long.parseLong(textFieldValue.getText().trim());
if (value < Integer.valueOf(configElement.getMinValue().toString()) || value > Integer.valueOf(configElement.getMaxValue().toString()))
this.isValidValue = false;
else
this.isValidValue = true;
}
catch (Throwable e)
{
this.isValidValue = false;
}
}
else
this.isValidValue = false;
}
}
@Override
public Integer getValue()
{
try
{
return Integer.valueOf(this.textFieldValue.getText().trim());
}
catch (Throwable e)
{
return Integer.MAX_VALUE;
}
}
}
public static class StringEntry extends BaseEntry
{
protected final GuiTextField textFieldValue;
public StringEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, Object value)
{
super(owningScreen, owningEntryList, configElement);
this.textFieldValue = new GuiTextField(0, owningEntryList.mc.fontRendererObj, owningEntryList.width / 4 + 1, 0, owningEntryList.controlWidth - 3, 16);
this.textFieldValue.setMaxStringLength(10000);
this.textFieldValue.setText(value.toString());
this.isValidated = configElement.getValidationPattern() != null;
if (configElement.getValidationPattern() != null)
{
if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches())
isValidValue = true;
else
isValidValue = false;
}
}
@Override
public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected)
{
super.drawEntry(slotIndex, x, y, listWidth, slotHeight, mouseX, mouseY, isSelected);
if (configElement.isListLengthFixed() || slotIndex != owningEntryList.listEntries.size() - 1)
{
this.textFieldValue.setVisible(true);
this.textFieldValue.yPosition = y + 1;
this.textFieldValue.drawTextBox();
}
else
this.textFieldValue.setVisible(false);
}
@Override
public void keyTyped(char eventChar, int eventKey)
{
if (owningScreen.enabled || eventKey == Keyboard.KEY_LEFT || eventKey == Keyboard.KEY_RIGHT
|| eventKey == Keyboard.KEY_HOME || eventKey == Keyboard.KEY_END)
{
this.textFieldValue.textboxKeyTyped((owningScreen.enabled ? eventChar : Keyboard.CHAR_NONE), eventKey);
if (configElement.getValidationPattern() != null)
{
if (configElement.getValidationPattern().matcher(this.textFieldValue.getText().trim()).matches())
isValidValue = true;
else
isValidValue = false;
}
}
}
@Override
public void updateCursorCounter()
{
this.textFieldValue.updateCursorCounter();
}
@Override
public void mouseClicked(int x, int y, int mouseEvent)
{
this.textFieldValue.mouseClicked(x, y, mouseEvent);
}
@Override
public Object getValue()
{
return this.textFieldValue.getText().trim();
}
}
public static class BooleanEntry extends BaseEntry
{
protected final GuiButtonExt btnValue;
private boolean value;
public BooleanEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement, boolean value)
{
super(owningScreen, owningEntryList, configElement);
this.value = value;
this.btnValue = new GuiButtonExt(0, 0, 0, owningEntryList.controlWidth, 18, I18n.format(String.valueOf(value)));
this.btnValue.enabled = owningScreen.enabled;
this.isValidated = false;
}
@Override
public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected)
{
super.drawEntry(slotIndex, x, y, listWidth, slotHeight, mouseX, mouseY, isSelected);
this.btnValue.xPosition = listWidth / 4;
this.btnValue.yPosition = y;
String trans = I18n.format(String.valueOf(value));
if (!trans.equals(String.valueOf(value)))
this.btnValue.displayString = trans;
else
this.btnValue.displayString = String.valueOf(value);
btnValue.packedFGColour = value ? GuiUtils.getColorCode('2', true) : GuiUtils.getColorCode('4', true);
this.btnValue.drawButton(owningEntryList.mc, mouseX, mouseY);
}
@Override
public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{
if (this.btnValue.mousePressed(owningEntryList.mc, x, y))
{
btnValue.playPressSound(owningEntryList.mc.getSoundHandler());
value = !value;
owningEntryList.recalculateState();
return true;
}
return super.mousePressed(index, x, y, mouseEvent, relativeX, relativeY);
}
@Override
public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{
this.btnValue.mouseReleased(x, y);
super.mouseReleased(index, x, y, mouseEvent, relativeX, relativeY);
}
@Override
public Object getValue()
{
return Boolean.valueOf(value);
}
}
public static class BaseEntry implements IArrayEntry
{
protected final GuiEditArray owningScreen;
protected final GuiEditArrayEntries owningEntryList;
protected final IConfigElement configElement;
protected final GuiButtonExt btnAddNewEntryAbove;
private final HoverChecker addNewEntryAboveHoverChecker;
protected final GuiButtonExt btnRemoveEntry;
private final HoverChecker removeEntryHoverChecker;
private List addNewToolTip, removeToolTip;
protected boolean isValidValue = true;
protected boolean isValidated = false;
@SuppressWarnings({ "unchecked" })
public BaseEntry(GuiEditArray owningScreen, GuiEditArrayEntries owningEntryList, IConfigElement configElement)
{
this.owningScreen = owningScreen;
this.owningEntryList = owningEntryList;
this.configElement = configElement;
this.btnAddNewEntryAbove = new GuiButtonExt(0, 0, 0, 18, 18, "+");
this.btnAddNewEntryAbove.packedFGColour = GuiUtils.getColorCode('2', true);
this.btnAddNewEntryAbove.enabled = owningScreen.enabled;
this.btnRemoveEntry = new GuiButtonExt(0, 0, 0, 18, 18, "x");
this.btnRemoveEntry.packedFGColour = GuiUtils.getColorCode('c', true);
this.btnRemoveEntry.enabled = owningScreen.enabled;
this.addNewEntryAboveHoverChecker = new HoverChecker(this.btnAddNewEntryAbove, 800);
this.removeEntryHoverChecker = new HoverChecker(this.btnRemoveEntry, 800);
this.addNewToolTip = new ArrayList();
this.removeToolTip = new ArrayList();
addNewToolTip.add(I18n.format("fml.configgui.tooltip.addNewEntryAbove"));
removeToolTip.add(I18n.format("fml.configgui.tooltip.removeEntry"));
}
@Override
public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected)
{
if (this.getValue() != null && this.isValidated)
owningEntryList.mc.fontRendererObj.drawString(
isValidValue ? EnumChatFormatting.GREEN + VALID : EnumChatFormatting.RED + INVALID,
listWidth / 4 - owningEntryList.mc.fontRendererObj.getStringWidth(VALID) - 2,
y + slotHeight / 2 - owningEntryList.mc.fontRendererObj.FONT_HEIGHT / 2,
16777215);
int half = listWidth / 2;
if (owningEntryList.canAddMoreEntries)
{
this.btnAddNewEntryAbove.visible = true;
this.btnAddNewEntryAbove.xPosition = half + ((half / 2) - 44);
this.btnAddNewEntryAbove.yPosition = y;
this.btnAddNewEntryAbove.drawButton(owningEntryList.mc, mouseX, mouseY);
}
else
this.btnAddNewEntryAbove.visible = false;
if (!configElement.isListLengthFixed() && slotIndex != owningEntryList.listEntries.size() - 1)
{
this.btnRemoveEntry.visible = true;
this.btnRemoveEntry.xPosition = half + ((half / 2) - 22);
this.btnRemoveEntry.yPosition = y;
this.btnRemoveEntry.drawButton(owningEntryList.mc, mouseX, mouseY);
}
else
this.btnRemoveEntry.visible = false;
}
@Override
public void drawToolTip(int mouseX, int mouseY)
{
boolean canHover = mouseY < owningEntryList.bottom && mouseY > owningEntryList.top;
if (this.btnAddNewEntryAbove.visible && this.addNewEntryAboveHoverChecker.checkHover(mouseX, mouseY, canHover))
owningScreen.drawToolTip(this.addNewToolTip, mouseX, mouseY);
if (this.btnRemoveEntry.visible && this.removeEntryHoverChecker.checkHover(mouseX, mouseY, canHover))
owningScreen.drawToolTip(this.removeToolTip, mouseX, mouseY);
}
@Override
public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{
if (this.btnAddNewEntryAbove.mousePressed(owningEntryList.mc, x, y))
{
btnAddNewEntryAbove.playPressSound(owningEntryList.mc.getSoundHandler());
owningEntryList.addNewEntry(index);
owningEntryList.recalculateState();
return true;
}
else if (this.btnRemoveEntry.mousePressed(owningEntryList.mc, x, y))
{
btnRemoveEntry.playPressSound(owningEntryList.mc.getSoundHandler());
owningEntryList.removeEntry(index);
owningEntryList.recalculateState();
return true;
}
return false;
}
@Override
public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{
this.btnAddNewEntryAbove.mouseReleased(x, y);
this.btnRemoveEntry.mouseReleased(x, y);
}
@Override
public void keyTyped(char eventChar, int eventKey)
{}
@Override
public void updateCursorCounter()
{}
@Override
public void mouseClicked(int x, int y, int mouseEvent)
{}
@Override
public boolean isValueSavable()
{
return isValidValue;
}
@Override
public Object getValue()
{
return null;
}
@Override
public void setSelected(int p_178011_1_, int p_178011_2_, int p_178011_3_){}
}
public static interface IArrayEntry extends GuiListExtended.IGuiListEntry
{
public void keyTyped(char eventChar, int eventKey);
public void updateCursorCounter();
public void mouseClicked(int x, int y, int mouseEvent);
public void drawToolTip(int mouseX, int mouseY);
public boolean isValueSavable();
public Object getValue();
}
}
@@ -0,0 +1,25 @@
package net.minecraftforge.fml.client.config;
import net.minecraft.client.gui.GuiDisconnected;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.IChatComponent;
public class GuiMessageDialog extends GuiDisconnected
{
protected String buttonText;
public GuiMessageDialog(GuiScreen nextScreen, String title, IChatComponent message, String buttonText)
{
super(nextScreen, title, message);
this.buttonText = buttonText;
}
@Override
public void initGui()
{
super.initGui();
((GuiButton) buttonList.get(0)).displayString = I18n.format(buttonText);
}
}
@@ -0,0 +1,182 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.EnumChatFormatting;
import static net.minecraftforge.fml.client.config.GuiUtils.RESET_CHAR;
import static net.minecraftforge.fml.client.config.GuiUtils.UNDO_CHAR;
/**
* This class provides a screen that allows the user to select a value from a list.
*
* @author bspkrs
*/
public class GuiSelectString extends GuiScreen
{
protected GuiScreen parentScreen;
protected IConfigElement configElement;
protected GuiSelectStringEntries entryList;
protected GuiButtonExt btnUndoChanges, btnDefault, btnDone;
protected String title;
protected String titleLine2;
protected String titleLine3;
protected int slotIndex;
protected final Map<Object, String> selectableValues;
public final Object beforeValue;
public Object currentValue;
protected HoverChecker tooltipHoverChecker;
@SuppressWarnings("rawtypes")
protected List toolTip;
protected boolean enabled;
@SuppressWarnings("rawtypes")
public GuiSelectString(GuiScreen parentScreen, IConfigElement configElement, int slotIndex, Map<Object, String> selectableValues, Object currentValue, boolean enabled)
{
this.mc = Minecraft.getMinecraft();
this.parentScreen = parentScreen;
this.configElement = configElement;
this.slotIndex = slotIndex;
this.selectableValues = selectableValues;
this.beforeValue = currentValue;
this.currentValue = currentValue;
this.toolTip = new ArrayList();
this.enabled = enabled;
String propName = I18n.format(configElement.getLanguageKey());
String comment;
comment = I18n.format(configElement.getLanguageKey() + ".tooltip",
"\n" + EnumChatFormatting.AQUA, configElement.getDefault(), configElement.getMinValue(), configElement.getMaxValue());
if (!comment.equals(configElement.getLanguageKey() + ".tooltip"))
toolTip = mc.fontRendererObj.listFormattedStringToWidth(
EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.YELLOW + comment, 300);
else if (configElement.getComment() != null && !configElement.getComment().trim().isEmpty())
toolTip = mc.fontRendererObj.listFormattedStringToWidth(
EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.YELLOW + configElement.getComment(), 300);
else
toolTip = mc.fontRendererObj.listFormattedStringToWidth(
EnumChatFormatting.GREEN + propName + "\n" + EnumChatFormatting.RED + "No tooltip defined.", 300);
if (parentScreen instanceof GuiConfig)
{
this.title = ((GuiConfig) parentScreen).title;
this.titleLine2 = ((GuiConfig) parentScreen).titleLine2;
this.titleLine3 = I18n.format(configElement.getLanguageKey());
this.tooltipHoverChecker = new HoverChecker(28, 37, 0, parentScreen.width, 800);
}
else
{
this.title = I18n.format(configElement.getLanguageKey());
this.tooltipHoverChecker = new HoverChecker(8, 17, 0, parentScreen.width, 800);
}
}
@SuppressWarnings("unchecked")
@Override
public void initGui()
{
this.entryList = new GuiSelectStringEntries(this, this.mc, this.configElement, this.selectableValues);
int undoGlyphWidth = mc.fontRendererObj.getStringWidth(UNDO_CHAR) * 2;
int resetGlyphWidth = mc.fontRendererObj.getStringWidth(RESET_CHAR) * 2;
int doneWidth = Math.max(mc.fontRendererObj.getStringWidth(I18n.format("gui.done")) + 20, 100);
int undoWidth = mc.fontRendererObj.getStringWidth(" " + I18n.format("fml.configgui.tooltip.undoChanges")) + undoGlyphWidth + 20;
int resetWidth = mc.fontRendererObj.getStringWidth(" " + I18n.format("fml.configgui.tooltip.resetToDefault")) + resetGlyphWidth + 20;
int buttonWidthHalf = (doneWidth + 5 + undoWidth + 5 + resetWidth) / 2;
this.buttonList.add(btnDone = new GuiButtonExt(2000, this.width / 2 - buttonWidthHalf, this.height - 29, doneWidth, 20, I18n.format("gui.done")));
this.buttonList.add(btnDefault = new GuiUnicodeGlyphButton(2001, this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5,
this.height - 29, resetWidth, 20, " " + I18n.format("fml.configgui.tooltip.resetToDefault"), RESET_CHAR, 2.0F));
this.buttonList.add(btnUndoChanges = new GuiUnicodeGlyphButton(2002, this.width / 2 - buttonWidthHalf + doneWidth + 5,
this.height - 29, undoWidth, 20, " " + I18n.format("fml.configgui.tooltip.undoChanges"), UNDO_CHAR, 2.0F));
}
@Override
protected void actionPerformed(GuiButton button)
{
if (button.id == 2000)
{
try
{
this.entryList.saveChanges();
}
catch (Throwable e)
{
e.printStackTrace();
}
this.mc.displayGuiScreen(this.parentScreen);
}
else if (button.id == 2001)
{
this.currentValue = configElement.getDefault();
this.entryList = new GuiSelectStringEntries(this, this.mc, this.configElement, this.selectableValues);
}
else if (button.id == 2002)
{
this.currentValue = beforeValue;
this.entryList = new GuiSelectStringEntries(this, this.mc, this.configElement, this.selectableValues);
}
}
public void handleMouseInput() throws IOException
{
super.handleMouseInput();
this.entryList.handleMouseInput();
}
@Override
protected void mouseReleased(int x, int y, int mouseEvent)
{
if (mouseEvent != 0 || !this.entryList.mouseReleased(x, y, mouseEvent))
{
super.mouseReleased(x, y, mouseEvent);
}
}
@Override
public void drawScreen(int par1, int par2, float par3)
{
this.drawDefaultBackground();
this.entryList.drawScreen(par1, par2, par3);
this.drawCenteredString(this.fontRendererObj, this.title, this.width / 2, 8, 16777215);
if (this.titleLine2 != null)
this.drawCenteredString(this.fontRendererObj, this.titleLine2, this.width / 2, 18, 16777215);
if (this.titleLine3 != null)
this.drawCenteredString(this.fontRendererObj, this.titleLine3, this.width / 2, 28, 16777215);
this.btnDone.enabled = currentValue != null;
this.btnDefault.enabled = enabled && !this.entryList.isDefault();
this.btnUndoChanges.enabled = enabled && this.entryList.isChanged();
super.drawScreen(par1, par2, par3);
if (this.tooltipHoverChecker != null && this.tooltipHoverChecker.checkHover(par1, par2))
drawToolTip(this.toolTip, par1, par2);
}
@SuppressWarnings("rawtypes")
public void drawToolTip(List stringList, int x, int y)
{
this.drawHoveringText(stringList, x, y);
}
}
@@ -0,0 +1,198 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiListExtended;
import net.minecraftforge.fml.client.config.GuiConfigEntries.SelectValueEntry;
/**
* This class implements the scrolling list functionality of the GuiSelectString screen.
*
* @author bspkrs
*/
public class GuiSelectStringEntries extends GuiListExtended
{
public GuiSelectString owningScreen;
public Minecraft mc;
public IConfigElement configElement;
public List<IGuiSelectStringListEntry> listEntries;
public final Map<Object, String> selectableValues;
public int selectedIndex = -1;
public int maxEntryWidth = 0;
public GuiSelectStringEntries(GuiSelectString owningScreen, Minecraft mc, IConfigElement configElement, Map<Object, String> selectableValues)
{
super(mc, owningScreen.width, owningScreen.height, owningScreen.titleLine2 != null ? (owningScreen.titleLine3 != null ? 43 : 33) : 23,
owningScreen.height - 32, 11);
this.owningScreen = owningScreen;
this.mc = mc;
this.configElement = configElement;
this.selectableValues = selectableValues;
this.setShowSelectionBox(true);
listEntries = new ArrayList<IGuiSelectStringListEntry>();
int index = 0;
List<Entry<Object, String>> sortedList = new ArrayList<Entry<Object, String>>(selectableValues.entrySet());
Collections.sort(sortedList, new EntryComparator());
for (Entry<Object, String> entry : sortedList)
{
listEntries.add(new ListEntry(this, entry));
if (mc.fontRendererObj.getStringWidth(entry.getValue()) > maxEntryWidth)
maxEntryWidth = mc.fontRendererObj.getStringWidth(entry.getValue());
if (owningScreen.currentValue.equals(entry.getKey()))
{
this.selectedIndex = index;
}
index++;
}
}
public static class EntryComparator implements Comparator<Entry<Object, String>>
{
@Override
public int compare(Entry<Object, String> o1, Entry<Object, String> o2)
{
int compare = o1.getValue().toLowerCase(Locale.US).compareTo(o2.getValue().toLowerCase(Locale.US));
if (compare == 0)
compare = o1.getKey().toString().toLowerCase(Locale.US).compareTo(o2.getKey().toString().toLowerCase(Locale.US));
return compare;
}
}
/**
* The element in the slot that was clicked, boolean for whether it was double clicked or not
*/
@Override
protected void elementClicked(int index, boolean doubleClick, int mouseX, int mouseY)
{
selectedIndex = index;
owningScreen.currentValue = listEntries.get(index).getValue();
}
/**
* Returns true if the element passed in is currently selected
*/
@Override
protected boolean isSelected(int index)
{
return index == selectedIndex;
}
@Override
protected int getScrollBarX()
{
return width / 2 + this.maxEntryWidth / 2 + 5;
}
/**
* Gets the width of the list
*/
@Override
public int getListWidth()
{
return maxEntryWidth + 5;
}
@Override
public IGuiSelectStringListEntry getListEntry(int index)
{
return listEntries.get(index);
}
@Override
protected int getSize()
{
return listEntries.size();
}
public boolean isChanged()
{
return owningScreen.beforeValue != null ? !owningScreen.beforeValue.equals(owningScreen.currentValue) : owningScreen.currentValue != null;
}
public boolean isDefault()
{
return owningScreen.currentValue != null ? owningScreen.currentValue.equals(configElement.getDefault()) : configElement.getDefault() == null;
}
public void saveChanges()
{
if (owningScreen.slotIndex != -1 && owningScreen.parentScreen != null
&& owningScreen.parentScreen instanceof GuiConfig
&& ((GuiConfig) owningScreen.parentScreen).entryList.getListEntry(owningScreen.slotIndex) instanceof SelectValueEntry)
{
SelectValueEntry entry = (SelectValueEntry) ((GuiConfig) owningScreen.parentScreen).entryList.getListEntry(owningScreen.slotIndex);
entry.setValueFromChildScreen(owningScreen.currentValue);
}
else
configElement.set(owningScreen.currentValue);
}
public static class ListEntry implements IGuiSelectStringListEntry
{
protected final GuiSelectStringEntries owningList;
protected final Entry<Object, String> value;
public ListEntry(GuiSelectStringEntries owningList, Entry<Object, String> value)
{
this.owningList = owningList;
this.value = value;
}
@Override
public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, int mouseX, int mouseY, boolean isSelected)
{
owningList.mc.fontRendererObj.drawString(value.getValue(), x + 1, y, slotIndex == owningList.selectedIndex ? 16777215 : 14737632);
}
@Override
public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{
return false;
}
@Override
public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY)
{}
@Override
public Object getValue()
{
return value.getKey();
}
@Override
public void setSelected(int p_178011_1_, int p_178011_2_, int p_178011_3_){}
}
public static interface IGuiSelectStringListEntry extends GuiListExtended.IGuiListEntry
{
public Object getValue();
}
}
@@ -0,0 +1,205 @@
package net.minecraftforge.fml.client.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import org.lwjgl.opengl.GL11;
/**
* This class is blatantly stolen from iChunUtils with permission.
*
* @author iChun
*/
public class GuiSlider extends GuiButtonExt
{
/** The value of this slider control. */
public double sliderValue = 1.0F;
public String dispString = "";
/** Is this slider control being dragged. */
public boolean dragging = false;
public boolean showDecimal = true;
public double minValue = 0.0D;
public double maxValue = 5.0D;
public int precision = 1;
public ISlider parent = null;
public String suffix = "";
public boolean drawString = true;
public GuiSlider(int id, int xPos, int yPos, int width, int height, String prefix, String suf, double minVal, double maxVal, double currentVal, boolean showDec, boolean drawStr)
{
this(id, xPos, yPos, width, height, prefix, suf, minVal, maxVal, currentVal, showDec, drawStr, null);
}
public GuiSlider(int id, int xPos, int yPos, int width, int height, String prefix, String suf, double minVal, double maxVal, double currentVal, boolean showDec, boolean drawStr, ISlider par)
{
super(id, xPos, yPos, width, height, prefix);
minValue = minVal;
maxValue = maxVal;
sliderValue = (currentVal - minValue) / (maxValue - minValue);
dispString = prefix;
parent = par;
suffix = suf;
showDecimal = showDec;
String val;
if (showDecimal)
{
val = Double.toString(sliderValue * (maxValue - minValue) + minValue);
precision = Math.min(val.substring(val.indexOf(".") + 1).length(), 4);
}
else
{
val = Integer.toString((int)Math.round(sliderValue * (maxValue - minValue) + minValue));
precision = 0;
}
displayString = dispString + val + suffix;
drawString = drawStr;
if(!drawString)
{
displayString = "";
}
}
public GuiSlider(int id, int xPos, int yPos, String displayStr, double minVal, double maxVal, double currentVal, ISlider par)
{
this(id, xPos, yPos, 150, 20, displayStr, "", minVal, maxVal, currentVal, true, true, par);
}
/**
* Returns 0 if the button is disabled, 1 if the mouse is NOT hovering over this button and 2 if it IS hovering over
* this button.
*/
@Override
public int getHoverState(boolean par1)
{
return 0;
}
/**
* Fired when the mouse button is dragged. Equivalent of MouseListener.mouseDragged(MouseEvent e).
*/
@Override
protected void mouseDragged(Minecraft par1Minecraft, int par2, int par3)
{
if (this.visible)
{
if (this.dragging)
{
this.sliderValue = (par2 - (this.xPosition + 4)) / (float)(this.width - 8);
updateSlider();
}
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderValue * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20);
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderValue * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20);
}
}
/**
* Returns true if the mouse has been pressed on this control. Equivalent of MouseListener.mousePressed(MouseEvent
* e).
*/
@Override
public boolean mousePressed(Minecraft par1Minecraft, int par2, int par3)
{
if (super.mousePressed(par1Minecraft, par2, par3))
{
this.sliderValue = (float)(par2 - (this.xPosition + 4)) / (float)(this.width - 8);
updateSlider();
this.dragging = true;
return true;
}
else
{
return false;
}
}
public void updateSlider()
{
if (this.sliderValue < 0.0F)
{
this.sliderValue = 0.0F;
}
if (this.sliderValue > 1.0F)
{
this.sliderValue = 1.0F;
}
String val;
if (showDecimal)
{
val = Double.toString(sliderValue * (maxValue - minValue) + minValue);
if (val.substring(val.indexOf(".") + 1).length() > precision)
{
val = val.substring(0, val.indexOf(".") + precision + 1);
if (val.endsWith("."))
{
val = val.substring(0, val.indexOf(".") + precision);
}
}
else
{
while (val.substring(val.indexOf(".") + 1).length() < precision)
{
val = val + "0";
}
}
}
else
{
val = Integer.toString((int)Math.round(sliderValue * (maxValue - minValue) + minValue));
}
if(drawString)
{
displayString = dispString + val + suffix;
}
if (parent != null)
{
parent.onChangeSliderValue(this);
}
}
/**
* Fired when the mouse button is released. Equivalent of MouseListener.mouseReleased(MouseEvent e).
*/
@Override
public void mouseReleased(int par1, int par2)
{
this.dragging = false;
}
public int getValueInt()
{
return (int)Math.round(sliderValue * (maxValue - minValue) + minValue);
}
public double getValue()
{
return sliderValue * (maxValue - minValue) + minValue;
}
public void setValue(double d)
{
this.sliderValue = (d - minValue) / (maxValue - minValue);
}
public static interface ISlider
{
void onChangeSliderValue(GuiSlider slider);
}
}
@@ -0,0 +1,84 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import org.lwjgl.opengl.GL11;
/**
* This class provides a button that shows a string glyph at the beginning. The glyph can be scaled using the glyphScale parameter.
*
* @author bspkrs
*/
public class GuiUnicodeGlyphButton extends GuiButtonExt
{
public String glyph;
public float glyphScale;
public GuiUnicodeGlyphButton(int id, int xPos, int yPos, int width, int height, String displayString, String glyph, float glyphScale)
{
super(id, xPos, yPos, width, height, displayString);
this.glyph = glyph;
this.glyphScale = glyphScale;
}
@Override
public void drawButton(Minecraft mc, int mouseX, int mouseY)
{
if (this.visible)
{
this.hovered = mouseX >= this.xPosition && mouseY >= this.yPosition && mouseX < this.xPosition + this.width && mouseY < this.yPosition + this.height;
int k = this.getHoverState(this.hovered);
GuiUtils.drawContinuousTexturedBox(buttonTextures, this.xPosition, this.yPosition, 0, 46 + k * 20, this.width, this.height, 200, 20, 2, 3, 2, 2, this.zLevel);
this.mouseDragged(mc, mouseX, mouseY);
int color = 14737632;
if (packedFGColour != 0)
{
color = packedFGColour;
}
else if (!this.enabled)
{
color = 10526880;
}
else if (this.hovered)
{
color = 16777120;
}
String buttonText = this.displayString;
int glyphWidth = (int) (mc.fontRendererObj.getStringWidth(glyph) * glyphScale);
int strWidth = mc.fontRendererObj.getStringWidth(buttonText);
int elipsisWidth = mc.fontRendererObj.getStringWidth("...");
int totalWidth = strWidth + glyphWidth;
if (totalWidth > width - 6 && totalWidth > elipsisWidth)
buttonText = mc.fontRendererObj.trimStringToWidth(buttonText, width - 6 - elipsisWidth).trim() + "...";
strWidth = mc.fontRendererObj.getStringWidth(buttonText);
totalWidth = glyphWidth + strWidth;
GlStateManager.pushMatrix();
GlStateManager.scale(glyphScale, glyphScale, 1.0F);
this.drawCenteredString(mc.fontRendererObj, glyph,
(int) (((this.xPosition + (this.width / 2) - (strWidth / 2)) / glyphScale) - (glyphWidth / (2 * glyphScale)) + 2),
(int) (((this.yPosition + ((this.height - 8) / glyphScale) / 2) - 1) / glyphScale), color);
GlStateManager.popMatrix();
this.drawCenteredString(mc.fontRendererObj, buttonText, (int) (this.xPosition + (this.width / 2) + (glyphWidth / glyphScale)),
this.yPosition + (this.height - 8) / 2, color);
}
}
}
@@ -0,0 +1,196 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.WorldRenderer;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11;
/**
* This class provides several methods and constants used by the Config GUI classes.
*
* @author bspkrs
*/
public class GuiUtils
{
public static final String UNDO_CHAR = "\u21B6";
public static final String RESET_CHAR = "\u2604";
public static final String VALID = "\u2714";
public static final String INVALID = "\u2715";
public static int[] colorCodes = new int[] { 0, 170, 43520, 43690, 11141120, 11141290, 16755200, 11184810, 5592405, 5592575, 5635925, 5636095, 16733525, 16733695, 16777045, 16777215,
0, 42, 10752, 10794, 2752512, 2752554, 2763264, 2763306, 1381653, 1381695, 1392405, 1392447, 4134165, 4134207, 4144917, 4144959 };
public static int getColorCode(char c, boolean isLighter)
{
return colorCodes[isLighter ? "0123456789abcdef".indexOf(c) : "0123456789abcdef".indexOf(c) + 16];
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. It is assumed that the desired texture ResourceLocation object has been bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param borderSize the size of the box's borders
* @param zLevel the zLevel to draw at
*/
public static void drawContinuousTexturedBox(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int borderSize, float zLevel)
{
drawContinuousTexturedBox(x, y, u, v, width, height, textureWidth, textureHeight, borderSize, borderSize, borderSize, borderSize, zLevel);
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. The provided ResourceLocation object will be bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param res the ResourceLocation object that contains the desired image
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param borderSize the size of the box's borders
* @param zLevel the zLevel to draw at
*/
public static void drawContinuousTexturedBox(ResourceLocation res, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int borderSize, float zLevel)
{
drawContinuousTexturedBox(res, x, y, u, v, width, height, textureWidth, textureHeight, borderSize, borderSize, borderSize, borderSize, zLevel);
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. The provided ResourceLocation object will be bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param res the ResourceLocation object that contains the desired image
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param topBorder the size of the box's top border
* @param bottomBorder the size of the box's bottom border
* @param leftBorder the size of the box's left border
* @param rightBorder the size of the box's right border
* @param zLevel the zLevel to draw at
*/
public static void drawContinuousTexturedBox(ResourceLocation res, int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int topBorder, int bottomBorder, int leftBorder, int rightBorder, float zLevel)
{
Minecraft.getMinecraft().getTextureManager().bindTexture(res);
drawContinuousTexturedBox(x, y, u, v, width, height, textureWidth, textureHeight, topBorder, bottomBorder, leftBorder, rightBorder, zLevel);
}
/**
* Draws a textured box of any size (smallest size is borderSize * 2 square) based on a fixed size textured box with continuous borders
* and filler. It is assumed that the desired texture ResourceLocation object has been bound using
* Minecraft.getMinecraft().getTextureManager().bindTexture(resourceLocation).
*
* @param x x axis offset
* @param y y axis offset
* @param u bound resource location image x offset
* @param v bound resource location image y offset
* @param width the desired box width
* @param height the desired box height
* @param textureWidth the width of the box texture in the resource location image
* @param textureHeight the height of the box texture in the resource location image
* @param topBorder the size of the box's top border
* @param bottomBorder the size of the box's bottom border
* @param leftBorder the size of the box's left border
* @param rightBorder the size of the box's right border
* @param zLevel the zLevel to draw at
*/
public static void drawContinuousTexturedBox(int x, int y, int u, int v, int width, int height, int textureWidth, int textureHeight,
int topBorder, int bottomBorder, int leftBorder, int rightBorder, float zLevel)
{
GlStateManager.color(1.0F, 1.0F, 1.0F, 1.0F);
GlStateManager.enableBlend();
GlStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 1, 0);
int fillerWidth = textureWidth - leftBorder - rightBorder;
int fillerHeight = textureHeight - topBorder - bottomBorder;
int canvasWidth = width - leftBorder - rightBorder;
int canvasHeight = height - topBorder - bottomBorder;
int xPasses = canvasWidth / fillerWidth;
int remainderWidth = canvasWidth % fillerWidth;
int yPasses = canvasHeight / fillerHeight;
int remainderHeight = canvasHeight % fillerHeight;
// Draw Border
// Top Left
drawTexturedModalRect(x, y, u, v, leftBorder, topBorder, zLevel);
// Top Right
drawTexturedModalRect(x + leftBorder + canvasWidth, y, u + leftBorder + fillerWidth, v, rightBorder, topBorder, zLevel);
// Bottom Left
drawTexturedModalRect(x, y + topBorder + canvasHeight, u, v + topBorder + fillerHeight, leftBorder, bottomBorder, zLevel);
// Bottom Right
drawTexturedModalRect(x + leftBorder + canvasWidth, y + topBorder + canvasHeight, u + leftBorder + fillerWidth, v + topBorder + fillerHeight, rightBorder, bottomBorder, zLevel);
for (int i = 0; i < xPasses + (remainderWidth > 0 ? 1 : 0); i++)
{
// Top Border
drawTexturedModalRect(x + leftBorder + (i * fillerWidth), y, u + leftBorder, v, (i == xPasses ? remainderWidth : fillerWidth), topBorder, zLevel);
// Bottom Border
drawTexturedModalRect(x + leftBorder + (i * fillerWidth), y + topBorder + canvasHeight, u + leftBorder, v + topBorder + fillerHeight, (i == xPasses ? remainderWidth : fillerWidth), bottomBorder, zLevel);
// Throw in some filler for good measure
for (int j = 0; j < yPasses + (remainderHeight > 0 ? 1 : 0); j++)
drawTexturedModalRect(x + leftBorder + (i * fillerWidth), y + topBorder + (j * fillerHeight), u + leftBorder, v + topBorder, (i == xPasses ? remainderWidth : fillerWidth), (j == yPasses ? remainderHeight : fillerHeight), zLevel);
}
// Side Borders
for (int j = 0; j < yPasses + (remainderHeight > 0 ? 1 : 0); j++)
{
// Left Border
drawTexturedModalRect(x, y + topBorder + (j * fillerHeight), u, v + topBorder, leftBorder, (j == yPasses ? remainderHeight : fillerHeight), zLevel);
// Right Border
drawTexturedModalRect(x + leftBorder + canvasWidth, y + topBorder + (j * fillerHeight), u + leftBorder + fillerWidth, v + topBorder, rightBorder, (j == yPasses ? remainderHeight : fillerHeight), zLevel);
}
}
public static void drawTexturedModalRect(int x, int y, int u, int v, int width, int height, float zLevel)
{
float var7 = 0.00390625F;
float var8 = 0.00390625F;
Tessellator tessellator = Tessellator.getInstance();
WorldRenderer worldr = tessellator.getWorldRenderer();
worldr.startDrawingQuads();
worldr.addVertexWithUV((x + 0), (y + height), zLevel, ((u + 0) * var7), ((v + height) * var8));
worldr.addVertexWithUV((x + width), (y + height), zLevel, ((u + width) * var7), ((v + height) * var8));
worldr.addVertexWithUV((x + width), (y + 0), zLevel, ((u + width) * var7), ((v + 0) * var8));
worldr.addVertexWithUV((x + 0), (y + 0), zLevel, ((u + 0) * var7), ((v + 0) * var8));
tessellator.draw();
}
}
@@ -0,0 +1,96 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import net.minecraft.client.gui.GuiButton;
/**
* This class implements an easy way to check if the mouse has hovered within a certain region of the screen for a given
* period of time. The region can be defined manually or by supplying a GuiButton object.
*
* @author bspkrs
*/
public class HoverChecker
{
private int top, bottom, left, right, threshold;
private GuiButton button;
private long hoverStart;
public HoverChecker(int top, int bottom, int left, int right, int threshold)
{
this.top = top;
this.bottom = bottom;
this.left = left;
this.right = right;
this.threshold = threshold;
this.hoverStart = -1;
}
public HoverChecker(GuiButton button, int threshold)
{
this.button = button;
this.threshold = threshold;
}
/**
* Call this method if the intended region has changed such as if the region must follow a scrolling list.
* It is not necessary to call this method if a GuiButton defines the hover region.
*/
public void updateBounds(int top, int bottom, int left, int right)
{
this.top = top;
this.bottom = bottom;
this.left = left;
this.right = right;
}
/**
* Checks if the mouse is in the hover region. If the specified time period has elapsed the method returns true.
* The hover timer is reset if the mouse is not within the region.
*/
public boolean checkHover(int mouseX, int mouseY)
{
return checkHover(mouseX, mouseY, true);
}
/**
* Checks if the mouse is in the hover region. If the specified time period has elapsed the method returns true.
* The hover timer is reset if the mouse is not within the region.
*/
public boolean checkHover(int mouseX, int mouseY, boolean canHover)
{
if (this.button != null)
{
this.top = button.yPosition;
this.bottom = button.yPosition + button.height;
this.left = button.xPosition;
this.right = button.xPosition + button.width;
canHover = canHover && button.visible;
}
if (canHover && hoverStart == -1 && mouseY >= top && mouseY <= bottom && mouseX >= left && mouseX <= right)
hoverStart = System.currentTimeMillis();
else if (!canHover || mouseY < top || mouseY > bottom || mouseX < left || mouseX > right)
resetHoverTimer();
return canHover && hoverStart != -1 && System.currentTimeMillis() - hoverStart >= threshold;
}
/**
* Manually resets the hover timer.
*/
public void resetHoverTimer()
{
hoverStart = -1;
}
}
@@ -0,0 +1,170 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.config;
import java.util.List;
import java.util.regex.Pattern;
import net.minecraftforge.fml.client.config.GuiConfigEntries.IConfigEntry;
import net.minecraftforge.fml.client.config.GuiEditArrayEntries.IArrayEntry;
/**
* This interface provides the information needed by GuiConfig and GuiConfigEntries to display config elements for editing.
*
* @author bspkrs
*/
public interface IConfigElement
{
/**
* [Property, Category] Is this object a property object?
*/
public boolean isProperty();
/**
* This method returns a class that implements {@code IConfigEntry} or null. This class MUST
* provide a constructor with the following parameter types: {@code GuiConfig}, {@code GuiConfigEntries}, {@code IConfigElement}
*/
public Class<? extends IConfigEntry> getConfigEntryClass();
/**
* This method returns a class that implements {@code IArrayEntry}. This class MUST provide a constructor with the
* following parameter types: {@code GuiEditArray}, {@code GuiEditArrayEntries}, {@code IConfigElement}, {@code Object}
*/
public Class<? extends IArrayEntry> getArrayEntryClass();
/**
* [Property, Category] Gets the name of this object.
*/
public String getName();
/**
* [Category] Gets the qualified name of this object. This is typically only used for category objects.
*/
public String getQualifiedName();
/**
* [Property, Category] Gets a language key for localization of config GUI entry names. If the same key is specified with .tooltip
* appended to the end, that key will return a localized tooltip when the mouse hovers over the property label/category button.
*/
public String getLanguageKey();
/**
* [Property, Category] Gets the comment for this object. Used for the tooltip if getLanguageKey() + ".tooltip" is not defined in the
* .lang file.
*/
public String getComment();
/**
* [Category] Gets this category's child categories/properties.
*/
public List<IConfigElement> getChildElements();
/**
* [Property, Category] Gets the ConfigGuiType value corresponding to the type of this property object, or CONFIG_CATEGORY if this is a
* category object.
*/
public ConfigGuiType getType();
/**
* [Property] Is this property object a list?
*/
public boolean isList();
/**
* [Property] Does this list property have to remain a fixed length?
*/
public boolean isListLengthFixed();
/**
* [Property] Gets the max length of this list property, or -1 if the length is unlimited.
*/
public int getMaxListLength();
/**
* [Property] Is this property value equal to the default value?
*/
public boolean isDefault();
/**
* [Property] Gets this property's default value. If this element is an array, this method should return a String
* representation of that array using Arrays.toString()
*/
public Object getDefault();
/**
* [Property] Gets this property's default values.
*/
public Object[] getDefaults();
/**
* [Property] Sets this property's value to the default value.
*/
public void setToDefault();
/**
* [Property, Category] Whether or not this element is safe to modify while a world is running. For Categories return false if ANY properties
* in the category are modifiable while a world is running, true if all are not.
*/
public boolean requiresWorldRestart();
/**
* [Property, Category] Whether or not this element should be allowed to show on config GUIs.
*/
public boolean showInGui();
/**
* [Property, Category] Whether or not this element requires Minecraft to be restarted when changed.
*/
public boolean requiresMcRestart();
/**
* [Property] Gets this property value.
*/
public Object get();
/**
* [Property] Gets this property value as a list. Generally you should be sure of whether the property is a list before calling this.
*/
public Object[] getList();
/**
* [Property] Sets this property's value.
*/
public void set(Object value);
/**
* [Property] Sets this property's value to the specified array.
*/
public void set(Object[] aVal);
/**
* [Property] Gets a String array of valid values for this property. This is generally used for String properties to allow the user to
* select a value from a list of valid values.
*/
public String[] getValidValues();
/**
* [Property] Gets this property's minimum value.
*/
public Object getMinValue();
/**
* [Property] Gets this property's maximum value.
*/
public Object getMaxValue();
/**
* [Property] Gets a Pattern object used in String property input validation.
*/
public Pattern getValidationPattern();
}
@@ -0,0 +1,85 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2014 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors (this class):
* bspkrs - implementation
*/
package net.minecraftforge.fml.client.event;
import net.minecraftforge.fml.common.eventhandler.Event;
import net.minecraftforge.fml.common.eventhandler.Event.HasResult;
/**
* These events are posted from the GuiConfig screen when the done button is pressed. The events are only posted
* if the parent screen is not an instance of GuiConfig or if the configID field has been set for
* the GuiConfig screen.
*
* Listeners for this event should use OnConfigChanged or PostConfigChanged and check for a specific mod ID.
* For best results the listener should refresh any objects/fields that are set based on the mod's config
* and should serialize the modified config.
*
* @author bspkrs
*/
@HasResult
public class ConfigChangedEvent extends Event
{
/**
* The Mod ID of the mod whose configuration just changed.
*/
public final String modID;
/**
* Whether or not a world is currently running.
*/
public final boolean isWorldRunning;
/**
* Will be set to true if any elements were changed that require a restart of Minecraft.
*/
public final boolean requiresMcRestart;
/**
* A String identifier for this ConfigChangedEvent.
*/
public final String configID;
public ConfigChangedEvent(String modID, String configID, boolean isWorldRunning, boolean requiresMcRestart)
{
this.modID = modID;
this.configID = configID;
this.isWorldRunning = isWorldRunning;
this.requiresMcRestart = requiresMcRestart;
}
/**
* This event is intended to be consumed by the mod whose config has been changed. It fires when the Done button
* has been clicked on a GuiConfig screen and the following conditions are met:<br/>
* - at least one config element has been changed<br/>
* - one of these 2 conditions are met:<br/>
* 1) the parent screen is null or is not an instance of GuiConfig<br/>
* 2) the configID field has been set to a non-null value for the GuiConfig screen<br/><br/>
* Modders should check the modID field of the event to ensure they are only acting on their own config screen's event!
*/
public static class OnConfigChangedEvent extends ConfigChangedEvent
{
public OnConfigChangedEvent(String modID, String configID, boolean isWorldRunning, boolean requiresMcRestart)
{
super(modID, configID, isWorldRunning, requiresMcRestart);
}
}
/**
* This event is provided for mods to consume if they want to be able to check if other mods' configs have been changed.
* This event only fires if the OnConfigChangedEvent result is not DENY.
*/
public static class PostConfigChangedEvent extends ConfigChangedEvent
{
public PostConfigChangedEvent(String modID, String configID, boolean isWorldRunning, boolean requiresMcRestart)
{
super(modID, configID, isWorldRunning, requiresMcRestart);
}
}
}
@@ -0,0 +1,51 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client.registry;
import org.apache.commons.lang3.ArrayUtils;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher;
import net.minecraft.client.renderer.tileentity.TileEntitySpecialRenderer;
import net.minecraft.client.settings.KeyBinding;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.fml.common.registry.GameRegistry;
public class ClientRegistry
{
/**
*
* Utility method for registering a tile entity and it's renderer at once - generally you should register them separately
*
* @param tileEntityClass
* @param id
* @param specialRenderer
*/
public static void registerTileEntity(Class <? extends TileEntity > tileEntityClass, String id, TileEntitySpecialRenderer specialRenderer)
{
GameRegistry.registerTileEntity(tileEntityClass, id);
bindTileEntitySpecialRenderer(tileEntityClass, specialRenderer);
}
@SuppressWarnings("unchecked")
public static void bindTileEntitySpecialRenderer(Class <? extends TileEntity> tileEntityClass, TileEntitySpecialRenderer specialRenderer)
{
TileEntityRendererDispatcher.instance.mapSpecialRenderers.put(tileEntityClass, specialRenderer);
specialRenderer.setRendererDispatcher(TileEntityRendererDispatcher.instance);
}
public static void registerKeyBinding(KeyBinding key)
{
Minecraft.getMinecraft().gameSettings.keyBindings = ArrayUtils.add(Minecraft.getMinecraft().gameSettings.keyBindings, key);
}
}
@@ -0,0 +1,62 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.client.registry;
import java.util.List;
import java.util.Map;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.entity.Entity;
import com.google.common.collect.Lists;
/**
* @author cpw
*
*/
public class RenderingRegistry
{
private static final RenderingRegistry INSTANCE = new RenderingRegistry();
private List<EntityRendererInfo> entityRenderers = Lists.newArrayList();
/**
* Register an entity rendering handler. This will, after mod initialization, be inserted into the main
* render map for entities
*
* @param entityClass
* @param renderer
*/
public static void registerEntityRenderingHandler(Class<? extends Entity> entityClass, Render renderer)
{
INSTANCE.entityRenderers.add(new EntityRendererInfo(entityClass, renderer));
}
private static class EntityRendererInfo
{
public EntityRendererInfo(Class<? extends Entity> target, Render renderer)
{
this.target = target;
this.renderer = renderer;
}
private Class<? extends Entity> target;
private Render renderer;
}
public static void loadEntityRenderers(Map<Class<? extends Entity>, Render> rendererMap)
{
for (EntityRendererInfo info : INSTANCE.entityRenderers)
{
rendererMap.put(info.target, info.renderer);
}
}
}
@@ -0,0 +1,14 @@
package net.minecraftforge.fml.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PACKAGE)
public @interface API {
String owner();
String provides();
String apiVersion();
}
@@ -0,0 +1,41 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Declare a variable to be populated by a Bukkit Plugin proxy instance if the bukkit coremod
* is available. It can only be applied to field typed as {@link BukkitProxy}
* Generally it should be used in conjunction with {@link Mod#bukkitPlugin()} specifying the
* plugin to load.
*
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BukkitPluginRef
{
/**
* A reference (possibly version specific) to a Bukkit Plugin by name, using the name@versionbound
* specification. If this is a bukkit enabled environment the field annotated by this
* will be populated with a {@link BukkitProxy} instance if possible. This proxy will be gotten by
* reflectively calling the "getModProxy" method on the bukkit plugin instance.
* @return The name of the plugin which we will inject into this field
*/
String value();
}
@@ -0,0 +1,24 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
/**
* A marker interface for retrieving a proxy to a bukkit plugin.
* Fields associated with {@link BukkitPluginRef} annotations should
* declare this type and cast down if the target is available (not null)
* @author cpw
*
*/
public interface BukkitProxy
{
}
@@ -0,0 +1,68 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.cert.Certificate;
public class CertificateHelper {
private static final String HEXES = "0123456789abcdef";
public static String getFingerprint(Certificate certificate)
{
if (certificate == null)
{
return "NO VALID CERTIFICATE FOUND";
}
try
{
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] der = certificate.getEncoded();
md.update(der);
byte[] digest = md.digest();
return hexify(digest);
}
catch (Exception e)
{
return null;
}
}
public static String getFingerprint(ByteBuffer buffer)
{
try
{
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(buffer);
byte[] chksum = digest.digest();
return hexify(chksum);
}
catch (Exception e)
{
return null;
}
}
private static String hexify(byte[] chksum)
{
final StringBuilder hex = new StringBuilder( 2 * chksum.length );
for ( final byte b : chksum ) {
hex.append(HEXES.charAt((b & 0xF0) >> 4))
.append(HEXES.charAt((b & 0x0F)));
}
return hex.toString();
}
}
@@ -0,0 +1,10 @@
package net.minecraftforge.fml.common;
public class ClassNameUtils
{
public static String shortName(Class<?> clz)
{
String nm = clz.getName();
return nm.indexOf('.') > -1 ? nm.substring(nm.lastIndexOf('.')+1) : nm;
}
}
@@ -0,0 +1,209 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.io.File;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionRange;
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.EventBus;
public class DummyModContainer implements ModContainer
{
private ModMetadata md;
private ArtifactVersion processedVersion;
private String label;
public DummyModContainer(ModMetadata md)
{
this.md = md;
}
public DummyModContainer(String label)
{
this.label = label;
}
public DummyModContainer()
{
}
@Override
public void bindMetadata(MetadataCollection mc)
{
}
@Override
public List<ArtifactVersion> getDependants()
{
return Collections.emptyList();
}
@Override
public List<ArtifactVersion> getDependencies()
{
return Collections.emptyList();
}
@Override
public Set<ArtifactVersion> getRequirements()
{
return Collections.emptySet();
}
@Override
public ModMetadata getMetadata()
{
return md;
}
@Override
public Object getMod()
{
return null;
}
@Override
public String getModId()
{
return md.modId;
}
@Override
public String getName()
{
return md.name;
}
@Override
public String getSortingRules()
{
return "";
}
@Override
public File getSource()
{
return null;
}
@Override
public String getVersion()
{
return md.version;
}
@Override
public boolean matches(Object mod)
{
return false;
}
@Override
public void setEnabledState(boolean enabled)
{
}
@Override
public boolean registerBus(EventBus bus, LoadController controller)
{
return false;
}
@Override
public ArtifactVersion getProcessedVersion()
{
if (processedVersion == null)
{
processedVersion = new DefaultArtifactVersion(getModId(), getVersion());
}
return processedVersion;
}
@Override
public boolean isImmutable()
{
return false;
}
@Override
public String getDisplayVersion()
{
return md.version;
}
@Override
public VersionRange acceptableMinecraftVersionRange()
{
return Loader.instance().getMinecraftModContainer().getStaticVersionRange();
}
@Override
public Certificate getSigningCertificate()
{
return null;
}
@Override
public String toString()
{
return md != null ? getModId() : "Dummy Container ("+label+") @" + System.identityHashCode(this);
}
@Override
public Map<String, String> getCustomModProperties()
{
return EMPTY_PROPERTIES;
}
@Override
public Class<?> getCustomResourcePackClass()
{
return null;
}
@Override
public Map<String, String> getSharedModDescriptor()
{
return null;
}
@Override
public Disableable canBeDisabled()
{
return Disableable.NEVER;
}
@Override
public String getGuiClassName()
{
return null;
}
@Override
public List<String> getOwnedPackages()
{
return ImmutableList.of();
}
@Override
public boolean shouldLoadInEnvironment()
{
return true;
}
}
@@ -0,0 +1,40 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.io.File;
import java.util.Map.Entry;
import net.minecraftforge.fml.common.EnhancedRuntimeException.WrappedPrintStream;
import com.google.common.collect.SetMultimap;
public class DuplicateModsFoundException extends LoaderException {
private static final long serialVersionUID = 1L;
public SetMultimap<ModContainer,File> dupes;
public DuplicateModsFoundException(SetMultimap<ModContainer, File> dupes) {
this.dupes = dupes;
}
@Override
protected void printStackTrace(WrappedPrintStream stream)
{
stream.println("Duplicate Mods:");
for (Entry<ModContainer, File> e : dupes.entries())
{
stream.println(String.format("\t%s : %s", e.getKey().getModId(), e.getValue().getAbsolutePath()));
}
stream.println("");
}
}
@@ -0,0 +1,89 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2016 Forge Dev LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
package net.minecraftforge.fml.common;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* RuntimeException that gives subclasses the simple opportunity to write extra data when printing the stack trace.
* Mainly a helper class as printsStackTrace has multiple signatures.
*/
public abstract class EnhancedRuntimeException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public EnhancedRuntimeException() { super(); }
public EnhancedRuntimeException(String message) { super(message); }
public EnhancedRuntimeException(String message, Throwable cause) { super(message, cause); }
public EnhancedRuntimeException(Throwable cause) { super(cause); }
@Override
public String getMessage()
{
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
if (stack.length > 2 && stack[2].getClassName().startsWith("org.apache.logging.log4j."))
{
// This is a bit of a hack to force ourselves to be able to give a extended description when log4j prints this out.
// Sadly this text is displayed AFTER the initial exception line, and before the stack trace. But as the intention
// is to print this to the end user this is what we need to do.
final StringWriter buf = new StringWriter();
String msg = super.getMessage();
if (msg != null)
buf.append(msg);
buf.append('\n');
this.printStackTrace(new WrappedPrintStream()
{
@Override
public void println(String line)
{
buf.append(line).append('\n');
}
});
return buf.toString();
}
return super.getMessage();
}
@Override
public void printStackTrace(final PrintWriter s)
{
printStackTrace(new WrappedPrintStream()
{
@Override
public void println(String line)
{
s.println(line);
}
});
super.printStackTrace(s);
}
public void printStackTrace(final PrintStream s)
{
printStackTrace(new WrappedPrintStream()
{
@Override
public void println(String line)
{
s.println(line);
}
});
super.printStackTrace(s);
}
protected abstract void printStackTrace(WrappedPrintStream stream);
public static abstract class WrappedPrintStream
{
public abstract void println(String line);
}
}
@@ -0,0 +1,771 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.network.EnumConnectionState;
import net.minecraft.network.INetHandler;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.handshake.client.C00Handshake;
import net.minecraft.network.login.server.S00PacketDisconnect;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IThreadListener;
import net.minecraft.world.World;
import net.minecraft.world.storage.SaveHandler;
import net.minecraft.world.storage.WorldInfo;
import net.minecraftforge.fml.common.eventhandler.EventBus;
import net.minecraftforge.fml.common.gameevent.InputEvent;
import net.minecraftforge.fml.common.gameevent.PlayerEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent;
import net.minecraftforge.fml.common.gameevent.TickEvent.Phase;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.relauncher.CoreModManager;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.server.FMLServerHandler;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.Lists;
import com.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* The main class for non-obfuscated hook handling code
*
* Anything that doesn't require obfuscated or client/server specific code should
* go in this handler
*
* It also contains a reference to the sided handler instance that is valid
* allowing for common code to access specific properties from the obfuscated world
* without a direct dependency
*
* @author cpw
*
*/
public class FMLCommonHandler
{
/**
* The singleton
*/
private static final FMLCommonHandler INSTANCE = new FMLCommonHandler();
/**
* The delegate for side specific data and functions
*/
private IFMLSidedHandler sidedDelegate;
private Class<?> forge;
private boolean noForge;
private List<String> brandings;
private List<String> brandingsNoMC;
private List<ICrashCallable> crashCallables = Lists.newArrayList(Loader.instance().getCallableCrashInformation());
private Set<SaveHandler> handlerSet = Sets.newSetFromMap(new MapMaker().weakKeys().<SaveHandler,Boolean>makeMap());
private WeakReference<SaveHandler> handlerToCheck;
private EventBus eventBus = new EventBus();
private volatile CountDownLatch exitLatch = null;
private FMLCommonHandler()
{
registerCrashCallable(new ICrashCallable()
{
public String call() throws Exception
{
StringBuilder builder = new StringBuilder();
Joiner joiner = Joiner.on("\n ");
for(String coreMod : CoreModManager.getTransformers().keySet())
{
builder.append("\n" + coreMod + "\n ").append(joiner.join(CoreModManager.getTransformers().get(coreMod)));
}
return builder.toString();
}
public String getLabel()
{
return "Loaded coremods (and transformers)";
}
});
}
/**
* The FML event bus. Subscribe here for FML related events
*
* @return the event bus
*/
public EventBus bus()
{
return eventBus;
}
public void beginLoading(IFMLSidedHandler handler)
{
sidedDelegate = handler;
FMLLog.log("MinecraftForge", Level.INFO, "Attempting early MinecraftForge initialization");
callForgeMethod("initialize");
callForgeMethod("registerCrashCallable");
FMLLog.log("MinecraftForge", Level.INFO, "Completed early MinecraftForge initialization");
}
/**
* @return the instance
*/
public static FMLCommonHandler instance()
{
return INSTANCE;
}
/**
* Find the container that associates with the supplied mod object
* @param mod
*/
public ModContainer findContainerFor(Object mod)
{
if (mod instanceof String)
{
return Loader.instance().getIndexedModList().get(mod);
}
else
{
return Loader.instance().getReversedModObjectList().get(mod);
}
}
/**
* Get the forge mod loader logging instance (goes to the forgemodloader log file)
* @return The log instance for the FML log file
*/
public Logger getFMLLogger()
{
return FMLLog.getLogger();
}
public Side getSide()
{
return sidedDelegate.getSide();
}
/**
* Return the effective side for the context in the game. This is dependent
* on thread analysis to try and determine whether the code is running in the
* server or not. Use at your own risk
*/
public Side getEffectiveSide()
{
Thread thr = Thread.currentThread();
if (thr.getName().equals("Server thread") || thr.getName().startsWith("Netty Server IO"))
{
return Side.SERVER;
}
return Side.CLIENT;
}
/**
* Raise an exception
*/
public void raiseException(Throwable exception, String message, boolean stopGame)
{
FMLLog.log(Level.ERROR, exception, "Something raised an exception. The message was '%s'. 'stopGame' is %b", message, stopGame);
if (stopGame)
{
getSidedDelegate().haltGame(message,exception);
}
}
private Class<?> findMinecraftForge()
{
if (forge==null && !noForge)
{
try {
forge = Class.forName("net.minecraftforge.common.MinecraftForge");
} catch (Exception ex) {
noForge = true;
}
}
return forge;
}
private Object callForgeMethod(String method)
{
if (noForge)
return null;
try
{
return findMinecraftForge().getMethod(method).invoke(null);
}
catch (Exception e)
{
// No Forge installation
return null;
}
}
public void computeBranding()
{
if (brandings == null)
{
Builder<String> brd = ImmutableList.<String>builder();
brd.add(Loader.instance().getMCVersionString());
brd.add(Loader.instance().getMCPVersionString());
brd.add("FML v"+Loader.instance().getFMLVersionString());
String forgeBranding = (String) callForgeMethod("getBrandingVersion");
if (!Strings.isNullOrEmpty(forgeBranding))
{
brd.add(forgeBranding);
}
if (sidedDelegate!=null)
{
brd.addAll(sidedDelegate.getAdditionalBrandingInformation());
}
if (Loader.instance().getFMLBrandingProperties().containsKey("fmlbranding"))
{
brd.add(Loader.instance().getFMLBrandingProperties().get("fmlbranding"));
}
int tModCount = Loader.instance().getModList().size();
int aModCount = Loader.instance().getActiveModList().size();
brd.add(String.format("%d mod%s loaded, %d mod%s active", tModCount, tModCount!=1 ? "s" :"", aModCount, aModCount!=1 ? "s" :"" ));
brandings = brd.build();
brandingsNoMC = brandings.subList(1, brandings.size());
}
}
public List<String> getBrandings(boolean includeMC)
{
if (brandings == null)
{
computeBranding();
}
return includeMC ? ImmutableList.copyOf(brandings) : ImmutableList.copyOf(brandingsNoMC);
}
public IFMLSidedHandler getSidedDelegate()
{
return sidedDelegate;
}
public void onPostServerTick()
{
bus().post(new TickEvent.ServerTickEvent(Phase.END));
}
/**
* Every tick just after world and other ticks occur
*/
public void onPostWorldTick(World world)
{
bus().post(new TickEvent.WorldTickEvent(Side.SERVER, Phase.END, world));
}
public void onPreServerTick()
{
bus().post(new TickEvent.ServerTickEvent(Phase.START));
}
/**
* Every tick just before world and other ticks occur
*/
public void onPreWorldTick(World world)
{
bus().post(new TickEvent.WorldTickEvent(Side.SERVER, Phase.START, world));
}
public boolean handleServerAboutToStart(MinecraftServer server)
{
return Loader.instance().serverAboutToStart(server);
}
public boolean handleServerStarting(MinecraftServer server)
{
return Loader.instance().serverStarting(server);
}
public void handleServerStarted()
{
Loader.instance().serverStarted();
sidedDelegate.allowLogins();
}
public void handleServerStopping()
{
Loader.instance().serverStopping();
}
public File getSavesDirectory() {
return sidedDelegate.getSavesDirectory();
}
public MinecraftServer getMinecraftServerInstance()
{
return sidedDelegate.getServer();
}
public void showGuiScreen(Object clientGuiElement)
{
sidedDelegate.showGuiScreen(clientGuiElement);
}
public void queryUser(StartupQuery query) throws InterruptedException
{
sidedDelegate.queryUser(query);
}
public void onServerStart(MinecraftServer dedicatedServer)
{
FMLServerHandler.instance();
sidedDelegate.beginServerLoading(dedicatedServer);
}
public void onServerStarted()
{
sidedDelegate.finishServerLoading();
}
public void onPreClientTick()
{
bus().post(new TickEvent.ClientTickEvent(Phase.START));
}
public void onPostClientTick()
{
bus().post(new TickEvent.ClientTickEvent(Phase.END));
}
public void onRenderTickStart(float timer)
{
bus().post(new TickEvent.RenderTickEvent(Phase.START, timer));
}
public void onRenderTickEnd(float timer)
{
bus().post(new TickEvent.RenderTickEvent(Phase.END, timer));
}
public void onPlayerPreTick(EntityPlayer player)
{
bus().post(new TickEvent.PlayerTickEvent(Phase.START, player));
}
public void onPlayerPostTick(EntityPlayer player)
{
bus().post(new TickEvent.PlayerTickEvent(Phase.END, player));
}
public void registerCrashCallable(ICrashCallable callable)
{
crashCallables.add(callable);
}
public void enhanceCrashReport(CrashReport crashReport, CrashReportCategory category)
{
for (ICrashCallable call: crashCallables)
{
category.addCrashSectionCallable(call.getLabel(), call);
}
}
public void handleWorldDataSave(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
{
for (ModContainer mc : Loader.instance().getModList())
{
if (mc instanceof InjectedModContainer)
{
WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
if (wac != null)
{
NBTTagCompound dataForWriting = wac.getDataForWriting(handler, worldInfo);
tagCompound.setTag(mc.getModId(), dataForWriting);
}
}
}
}
public void handleWorldDataLoad(SaveHandler handler, WorldInfo worldInfo, NBTTagCompound tagCompound)
{
if (getEffectiveSide()!=Side.SERVER)
{
return;
}
if (handlerSet.contains(handler))
{
return;
}
handlerSet.add(handler);
handlerToCheck = new WeakReference<SaveHandler>(handler); // for confirmBackupLevelDatUse
Map<String,NBTBase> additionalProperties = Maps.newHashMap();
worldInfo.setAdditionalProperties(additionalProperties);
for (ModContainer mc : Loader.instance().getModList())
{
if (mc instanceof InjectedModContainer)
{
WorldAccessContainer wac = ((InjectedModContainer)mc).getWrappedWorldAccessContainer();
if (wac != null)
{
wac.readData(handler, worldInfo, additionalProperties, tagCompound.getCompoundTag(mc.getModId()));
}
}
}
}
public void confirmBackupLevelDatUse(SaveHandler handler)
{
if (handlerToCheck == null || handlerToCheck.get() != handler) {
// only run if the save has been initially loaded
handlerToCheck = null;
return;
}
String text = "Forge Mod Loader detected that the backup level.dat is being used.\n\n" +
"This may happen due to a bug or corruption, continuing can damage\n" +
"your world beyond repair or lose data / progress.\n\n" +
"It's recommended to create a world backup before continuing.";
boolean confirmed = StartupQuery.confirm(text);
if (!confirmed) StartupQuery.abort();
}
public boolean shouldServerBeKilledQuietly()
{
if (sidedDelegate == null)
{
return false;
}
return sidedDelegate.shouldServerShouldBeKilledQuietly();
}
/**
* Make handleExit() wait for handleServerStopped().
*
* For internal use only!
*/
public void expectServerStopped()
{
exitLatch = new CountDownLatch(1);
}
/**
* Delayed System.exit() until the server is actually stopped/done saving.
*
* For internal use only!
*
* @param retVal Exit code for System.exit()
*/
public void handleExit(int retVal)
{
CountDownLatch latch = exitLatch;
if (latch != null)
{
try
{
FMLLog.info("Waiting for the server to terminate/save.");
if (!latch.await(10, TimeUnit.SECONDS))
{
FMLLog.warning("The server didn't stop within 10 seconds, exiting anyway.");
}
else
{
FMLLog.info("Server terminated.");
}
}
catch (InterruptedException e)
{
FMLLog.warning("Interrupted wait, exiting.");
}
}
System.exit(retVal);
}
public void handleServerStopped()
{
sidedDelegate.serverStopped();
MinecraftServer server = getMinecraftServerInstance();
Loader.instance().serverStopped();
// FORCE the internal server to stop: hello optifine workaround!
if (server!=null) ObfuscationReflectionHelper.setPrivateValue(MinecraftServer.class, server, false, "field_71316"+"_v", "u", "serverStopped");
// allow any pending exit to continue, clear exitLatch
CountDownLatch latch = exitLatch;
if (latch != null)
{
latch.countDown();
exitLatch = null;
}
}
public String getModName()
{
List<String> modNames = Lists.newArrayListWithExpectedSize(3);
modNames.add("fml");
if (!noForge)
{
modNames.add("forge");
}
if (Loader.instance().getFMLBrandingProperties().containsKey("snooperbranding"))
{
modNames.add(Loader.instance().getFMLBrandingProperties().get("snooperbranding"));
}
return Joiner.on(',').join(modNames);
}
public void addModToResourcePack(ModContainer container)
{
sidedDelegate.addModAsResource(container);
}
public String getCurrentLanguage()
{
return sidedDelegate.getCurrentLanguage();
}
public void bootstrap()
{
}
public NetworkManager getClientToServerNetworkManager()
{
return sidedDelegate.getClientToServerNetworkManager();
}
public void fireMouseInput()
{
bus().post(new InputEvent.MouseInputEvent());
}
public void fireKeyInput()
{
bus().post(new InputEvent.KeyInputEvent());
}
public void firePlayerChangedDimensionEvent(EntityPlayer player, int fromDim, int toDim)
{
bus().post(new PlayerEvent.PlayerChangedDimensionEvent(player, fromDim, toDim));
}
public void firePlayerLoggedIn(EntityPlayer player)
{
bus().post(new PlayerEvent.PlayerLoggedInEvent(player));
}
public void firePlayerLoggedOut(EntityPlayer player)
{
bus().post(new PlayerEvent.PlayerLoggedOutEvent(player));
}
public void firePlayerRespawnEvent(EntityPlayer player)
{
bus().post(new PlayerEvent.PlayerRespawnEvent(player));
}
public void firePlayerItemPickupEvent(EntityPlayer player, EntityItem item)
{
bus().post(new PlayerEvent.ItemPickupEvent(player, item));
}
public void firePlayerCraftingEvent(EntityPlayer player, ItemStack crafted, IInventory craftMatrix)
{
bus().post(new PlayerEvent.ItemCraftedEvent(player, crafted, craftMatrix));
}
public void firePlayerSmeltedEvent(EntityPlayer player, ItemStack smelted)
{
bus().post(new PlayerEvent.ItemSmeltedEvent(player, smelted));
}
public INetHandler getClientPlayHandler()
{
return sidedDelegate.getClientPlayHandler();
}
public void fireNetRegistrationEvent(NetworkManager manager, Set<String> channelSet, String channel, Side side)
{
sidedDelegate.fireNetRegistrationEvent(bus(), manager, channelSet, channel, side);
}
public boolean shouldAllowPlayerLogins()
{
return sidedDelegate.shouldAllowPlayerLogins();
}
/**
* Process initial Handshake packet, kicks players from the server if they are connecting while we are starting up.
* Also verifies the client has the FML marker.
*
* @param packet Handshake Packet
* @param manager Network connection
* @return True to allow connection, otherwise False.
*/
public boolean handleServerHandshake(C00Handshake packet, NetworkManager manager)
{
if (!shouldAllowPlayerLogins())
{
ChatComponentText text = new ChatComponentText("Server is still starting! Please wait before reconnecting.");
FMLLog.info("Disconnecting Player: " + text.getUnformattedText());
manager.sendPacket(new S00PacketDisconnect(text));
manager.closeChannel(text);
return false;
}
if (packet.getRequestedState() == EnumConnectionState.LOGIN && (!NetworkRegistry.INSTANCE.isVanillaAccepted(Side.CLIENT) && !packet.hasFMLMarker()))
{
manager.setConnectionState(EnumConnectionState.LOGIN);
ChatComponentText text = new ChatComponentText("This server requires FML/Forge to be installed. Contact your server admin for more details.");
FMLLog.info("Disconnecting Player: " + text.getUnformattedText());
manager.sendPacket(new S00PacketDisconnect(text));
manager.closeChannel(text);
return false;
}
manager.channel().attr(NetworkRegistry.FML_MARKER).set(packet.hasFMLMarker());
return true;
}
public void processWindowMessages()
{
if (sidedDelegate == null) return;
sidedDelegate.processWindowMessages();
}
/**
* Used to exit from java, with system exit preventions in place. Will be tidy about it and just log a message,
* unless debugging is enabled
*
* @param exitCode The exit code
* @param hardExit Perform a halt instead of an exit (only use when the world is unsavable) - read the warnings at {@link Runtime#halt(int)}
*/
public void exitJava(int exitCode, boolean hardExit)
{
FMLLog.log(Level.INFO, "Java has been asked to exit (code %d) by %s.", exitCode, Thread.currentThread().getStackTrace()[1]);
if (hardExit)
{
FMLLog.log(Level.INFO, "This is an abortive exit and could cause world corruption or other things");
}
if (Boolean.parseBoolean(System.getProperty("fml.debugExit", "false")))
{
FMLLog.log(Level.INFO, new Throwable(), "Exit trace");
}
else
{
FMLLog.log(Level.INFO, "If this was an unexpected exit, use -Dfml.debugExit=true as a JVM argument to find out where it was called");
}
if (hardExit)
{
Runtime.getRuntime().halt(exitCode);
}
else
{
Runtime.getRuntime().exit(exitCode);
}
}
public IThreadListener getWorldThread(INetHandler net)
{
return sidedDelegate.getWorldThread(net);
}
public static void callFuture(FutureTask task)
{
try
{
task.run();
task.get(); // Forces the exception to be thrown if any
}
catch (InterruptedException e)
{
FMLLog.log(Level.FATAL, e, "Exception caught executing FutureTask: " + e.toString());
}
catch (ExecutionException e)
{
FMLLog.log(Level.FATAL, e, "Exception caught executing FutureTask: " + e.toString());
}
}
/**
* Loads a lang file, first searching for a marker to enable the 'extended' format {escape charaters}
* If the marker is not found it simply returns and let the vanilla code load things.
* The Marker is 'PARSE_ESCAPES' by itself on a line starting with '#' as such:
* #PARSE_ESCAPES
*
* @param table The Map to load each key/value pair into.
* @param inputstream Input stream containing the lang file.
* @return A new InputStream that vanilla uses to load normal Lang files, Null if this is a 'enhanced' file and loading is done.
*/
public InputStream loadLanguage(Map<String, String> table, InputStream inputstream) throws IOException
{
byte[] data = IOUtils.toByteArray(inputstream);
boolean isEnhanced = false;
for (String line : IOUtils.readLines(new ByteArrayInputStream(data), Charsets.UTF_8))
{
if (!line.isEmpty() && line.charAt(0) == '#')
{
line = line.substring(1).trim();
if (line.equals("PARSE_ESCAPES"))
{
isEnhanced = true;
break;
}
}
}
if (!isEnhanced)
return new ByteArrayInputStream(data);
Properties props = new Properties();
props.load(new InputStreamReader(new ByteArrayInputStream(data), Charsets.UTF_8));
for (Entry e : props.entrySet())
{
table.put((String)e.getKey(), (String)e.getValue());
}
props.clear();
return null;
}
public String stripSpecialChars(String message)
{
return sidedDelegate != null ? sidedDelegate.stripSpecialChars(message) : message;
}
}
@@ -0,0 +1,375 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.io.File;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagIntArray;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.world.storage.SaveHandler;
import net.minecraft.world.storage.WorldInfo;
import net.minecraftforge.fml.client.FMLFileResourcePack;
import net.minecraftforge.fml.client.FMLFolderResourcePack;
import net.minecraftforge.fml.common.asm.FMLSanityChecker;
import net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.network.NetworkCheckHandler;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.network.internal.FMLNetworkHandler;
import net.minecraftforge.fml.common.registry.GameData;
import net.minecraftforge.fml.common.registry.VillagerRegistry;
import net.minecraftforge.fml.relauncher.Side;
import org.apache.logging.log4j.Level;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
/**
* @author cpw
*
*/
public class FMLContainer extends DummyModContainer implements WorldAccessContainer
{
public FMLContainer()
{
super(new ModMetadata());
ModMetadata meta = getMetadata();
meta.modId="FML";
meta.name="Forge Mod Loader";
meta.version=Loader.instance().getFMLVersionString();
meta.credits="Made possible with help from many people";
meta.authorList=Arrays.asList("cpw", "LexManos", "Player");
meta.description="The Forge Mod Loader provides the ability for systems to load mods " +
"from the file system. It also provides key capabilities for mods to be able " +
"to cooperate and provide a good modding environment. ";
meta.url="https://github.com/MinecraftForge/FML/wiki";
meta.updateUrl="https://github.com/MinecraftForge/FML/wiki";
meta.screenshots=new String[0];
meta.logoFile="";
}
@Override
public boolean registerBus(EventBus bus, LoadController controller)
{
bus.register(this);
return true;
}
@Subscribe
public void modConstruction(FMLConstructionEvent evt)
{
NetworkRegistry.INSTANCE.register(this, this.getClass(), null, evt.getASMHarvestedData());
FMLNetworkHandler.registerChannel(this, evt.getSide());
}
@Subscribe
public void modPreinitialization(FMLPreInitializationEvent evt)
{
// Initialize the villager registry
VillagerRegistry.instance();
}
@NetworkCheckHandler
public boolean checkModLists(Map<String,String> modList, Side side)
{
return Loader.instance().checkRemoteModList(modList,side);
}
@Override
public NBTTagCompound getDataForWriting(SaveHandler handler, WorldInfo info)
{
NBTTagCompound fmlData = new NBTTagCompound();
NBTTagList modList = new NBTTagList();
for (ModContainer mc : Loader.instance().getActiveModList())
{
NBTTagCompound mod = new NBTTagCompound();
mod.setString("ModId", mc.getModId());
mod.setString("ModVersion", mc.getVersion());
modList.appendTag(mod);
}
fmlData.setTag("ModList", modList);
NBTTagCompound registries = new NBTTagCompound();
fmlData.setTag("Registries", registries);
FMLLog.fine("Gathering id map for writing to world save %s", info.getWorldName());
GameData.GameDataSnapshot dataSnapshot = GameData.takeSnapshot();
for (Map.Entry<String, GameData.GameDataSnapshot.Entry> e : dataSnapshot.entries.entrySet())
{
NBTTagCompound data = new NBTTagCompound();
registries.setTag(e.getKey(), data);
NBTTagList ids = new NBTTagList();
for (Entry<String, Integer> item : e.getValue().ids.entrySet())
{
NBTTagCompound tag = new NBTTagCompound();
tag.setString("K", item.getKey());
tag.setInteger("V", item.getValue());
ids.appendTag(tag);
}
data.setTag("ids", ids);
NBTTagList aliases = new NBTTagList();
for (Entry<String, String> entry : e.getValue().aliases.entrySet())
{
NBTTagCompound tag = new NBTTagCompound();
tag.setString("K", entry.getKey());
tag.setString("V", entry.getValue());
aliases.appendTag(tag);
}
data.setTag("aliases", aliases);
NBTTagList subs = new NBTTagList();
for (String entry : e.getValue().substitutions)
{
NBTTagCompound tag = new NBTTagCompound();
tag.setString("K", entry);
subs.appendTag(tag);
}
data.setTag("substitutions", subs);
int[] blocked = new int[e.getValue().blocked.size()];
int idx = 0;
for (Integer i : e.getValue().blocked)
{
blocked[idx++] = i;
}
data.setIntArray("blocked", blocked);
}
return fmlData;
}
@Override
public void readData(SaveHandler handler, WorldInfo info, Map<String, NBTBase> propertyMap, NBTTagCompound tag)
{
if (tag.hasKey("ModList"))
{
NBTTagList modList = tag.getTagList("ModList", (byte)10);
for (int i = 0; i < modList.tagCount(); i++)
{
NBTTagCompound mod = modList.getCompoundTagAt(i);
String modId = mod.getString("ModId");
String modVersion = mod.getString("ModVersion");
ModContainer container = Loader.instance().getIndexedModList().get(modId);
if (container == null)
{
FMLLog.log("fml.ModTracker", Level.ERROR, "This world was saved with mod %s which appears to be missing, things may not work well", modId);
continue;
}
if (!modVersion.equals(container.getVersion()))
{
FMLLog.log("fml.ModTracker", Level.INFO, "This world was saved with mod %s version %s and it is now at version %s, things may not work well", modId, modVersion, container.getVersion());
}
}
}
List<String> failedElements = null;
if (tag.hasKey("ModItemData")) // Pre 1.7
{
GameData.GameDataSnapshot snapshot = new GameData.GameDataSnapshot();
GameData.GameDataSnapshot.Entry items = new GameData.GameDataSnapshot.Entry();
snapshot.entries.put("fml:blocks", new GameData.GameDataSnapshot.Entry());
snapshot.entries.put("fml:items", items);
FMLLog.info("Attempting to convert old world data to new system. This may be trouble!");
NBTTagList modList = tag.getTagList("ModItemData", (byte)10);
for (int i = 0; i < modList.tagCount(); i++)
{
NBTTagCompound data = modList.getCompoundTagAt(i);
String forcedModId = data.hasKey("ForcedModId") ? data.getString("ForcedModId") : null;
String forcedName = data.hasKey("ForcedName") ? data.getString("ForcedName") : null;
if (forcedName == null)
{
FMLLog.warning("Found unlabelled item in world save, this may cause problems. The item type %s:%d will not be present", data.getString("ItemType"), data.getInteger("ordinal"));
}
else
{
// all entries are Items, blocks were only saved through their ItemBlock
String itemLabel = String.format("%s:%s", forcedModId != null ? forcedModId : data.getString("ModId"), forcedName);
items.ids.put(itemLabel, data.getInteger("ItemId"));
}
}
failedElements = GameData.injectSnapshot(snapshot, true, true);
}
else if (tag.hasKey("ItemData")) // 1.7
{
GameData.GameDataSnapshot snapshot = new GameData.GameDataSnapshot();
GameData.GameDataSnapshot.Entry blocks = new GameData.GameDataSnapshot.Entry();
GameData.GameDataSnapshot.Entry items = new GameData.GameDataSnapshot.Entry();
snapshot.entries.put("fml:blocks", blocks);
snapshot.entries.put("fml:items", items);
NBTTagList list = tag.getTagList("ItemData", 10);
for (int i = 0; i < list.tagCount(); i++)
{
NBTTagCompound e = list.getCompoundTagAt(i);
String name = e.getString("K");
if (name.charAt(0) == '\u0001')
blocks.ids.put(name.substring(1), e.getInteger("V"));
else if (name.charAt(0) == '\u0002')
items.ids.put(name.substring(1), e.getInteger("V"));
}
Set<Integer> blockedIds = new HashSet<Integer>();
if (!tag.hasKey("BlockedItemIds")) // no blocked id info -> old 1.7 save
{
// old early 1.7 save potentially affected by the registry mapping bug
// fix the ids the best we can...
GameData.fixBrokenIds(blocks, items, blockedIds);
}
else
{
for (int id : tag.getIntArray("BlockedItemIds"))
{
blockedIds.add(id);
}
}
blocks.blocked.addAll(blockedIds);
items.blocked.addAll(blockedIds);
list = tag.getTagList("BlockAliases", 10);
for (int i = 0; i < list.tagCount(); i++)
{
NBTTagCompound dataTag = list.getCompoundTagAt(i);
blocks.aliases.put(dataTag.getString("K"), dataTag.getString("V"));
}
if (tag.hasKey("BlockSubstitutions", 9))
{
list = tag.getTagList("BlockSubstitutions", 10);
for (int i = 0; i < list.tagCount(); i++)
{
NBTTagCompound dataTag = list.getCompoundTagAt(i);
blocks.substitutions.add(dataTag.getString("K"));
}
}
list = tag.getTagList("ItemAliases", 10);
for (int i = 0; i < list.tagCount(); i++)
{
NBTTagCompound dataTag = list.getCompoundTagAt(i);
items.aliases.put(dataTag.getString("K"), dataTag.getString("V"));
}
if (tag.hasKey("ItemSubstitutions", 9))
{
list = tag.getTagList("ItemSubstitutions", 10);
for (int i = 0; i < list.tagCount(); i++)
{
NBTTagCompound dataTag = list.getCompoundTagAt(i);
items.substitutions.add(dataTag.getString("K"));
}
}
failedElements = GameData.injectSnapshot(snapshot, true, true);
}
else if (tag.hasKey("Registries")) // 1.8, genericed out the 'registries' list
{
GameData.GameDataSnapshot snapshot = new GameData.GameDataSnapshot();
NBTTagCompound regs = tag.getCompoundTag("Registries");
for (String key : (Set<String>)regs.getKeySet())
{
GameData.GameDataSnapshot.Entry entry = new GameData.GameDataSnapshot.Entry();
snapshot.entries.put(key, entry);
NBTTagList list = regs.getCompoundTag(key).getTagList("ids", 10);
for (int x = 0; x < list.tagCount(); x++)
{
NBTTagCompound e = list.getCompoundTagAt(x);
entry.ids.put(e.getString("K"), e.getInteger("V"));
}
list = regs.getCompoundTag(key).getTagList("aliases", 10);
for (int x = 0; x < list.tagCount(); x++)
{
NBTTagCompound e = list.getCompoundTagAt(x);
entry.aliases.put(e.getString("K"), e.getString("V"));
}
list = regs.getCompoundTag(key).getTagList("substitutions", 10);
for (int x = 0; x < list.tagCount(); x++)
{
NBTTagCompound e = list.getCompoundTagAt(x);
entry.substitutions.add(e.getString("K"));
}
int[] blocked = regs.getCompoundTag(key).getIntArray("blocked");
for (int i : blocked)
{
entry.blocked.add(i);
}
}
failedElements = GameData.injectSnapshot(snapshot, true, true);
}
if (failedElements != null && !failedElements.isEmpty())
{
String text = "Forge Mod Loader could not load this save.\n\n" +
"There are "+failedElements.size()+" unassigned blocks and items in this save.\n" +
"You will not be able to load until they are present again.\n\n" +
"Missing Blocks/Items:\n";
for (String s : failedElements) text += s + "\n";
StartupQuery.notify(text);
StartupQuery.abort();
}
}
@Override
public Certificate getSigningCertificate()
{
Certificate[] certificates = getClass().getProtectionDomain().getCodeSource().getCertificates();
return certificates != null ? certificates[0] : null;
}
@Override
public File getSource()
{
return FMLSanityChecker.fmlLocation;
}
@Override
public Class<?> getCustomResourcePackClass()
{
return getSource().isDirectory() ? FMLFolderResourcePack.class : FMLFileResourcePack.class;
}
@Override
public String getGuiClassName()
{
return "net.minecraftforge.fml.client.FMLConfigGuiFactory";
}
@Override
public Object getMod()
{
return this;
}
}
@@ -0,0 +1,6 @@
package net.minecraftforge.fml.common;
public interface FMLContainerHolder
{
ModContainer getFMLContainer();
}
@@ -0,0 +1,84 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
@SuppressWarnings("static-access")
public class FMLLog
{
private static net.minecraftforge.fml.relauncher.FMLRelaunchLog coreLog = net.minecraftforge.fml.relauncher.FMLRelaunchLog.log;
public static void log(String targetLog, Level level, String format, Object... data)
{
coreLog.log(targetLog, level, format, data);
}
public static void log(Level level, String format, Object... data)
{
coreLog.log(level, format, data);
}
public static void log(String targetLog, Level level, Throwable ex, String format, Object... data)
{
coreLog.log(targetLog, level, ex, format, data);
}
public static void log(Level level, Throwable ex, String format, Object... data)
{
coreLog.log(level, ex, format, data);
}
public static void severe(String format, Object... data)
{
log(Level.ERROR, format, data);
}
public static void bigWarning(String format, Object... data)
{
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
log(Level.WARN, "****************************************");
log(Level.WARN, "* "+format, data);
for (int i = 2; i < 8 && i < trace.length; i++)
{
log(Level.WARN, "* at %s%s", trace[i].toString(), i == 7 ? "..." : "");
}
log(Level.WARN, "****************************************");
}
public static void warning(String format, Object... data)
{
log(Level.WARN, format, data);
}
public static void info(String format, Object... data)
{
log(Level.INFO, format, data);
}
public static void fine(String format, Object... data)
{
log(Level.DEBUG, format, data);
}
public static void finer(String format, Object... data)
{
log(Level.TRACE, format, data);
}
public static Logger getLogger()
{
return coreLog.getLogger();
}
}
@@ -0,0 +1,666 @@
/*
* The FML Forge Mod Loader suite. Copyright (C) 2012 cpw
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fml.common;
import java.io.File;
import java.io.FileInputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import net.minecraftforge.fml.common.Mod.Instance;
import net.minecraftforge.fml.common.Mod.Metadata;
import net.minecraftforge.fml.common.asm.transformers.BlamingTransformer;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.ModCandidate;
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData;
import net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.event.FMLEvent;
import net.minecraftforge.fml.common.event.FMLFingerprintViolationEvent;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionParser;
import net.minecraftforge.fml.common.versioning.VersionRange;
import net.minecraftforge.fml.relauncher.Side;
import org.apache.logging.log4j.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
public class FMLModContainer implements ModContainer
{
private Object modInstance;
private File source;
private ModMetadata modMetadata;
private String className;
private Map<String, Object> descriptor;
private boolean enabled = true;
private String internalVersion;
private boolean overridesMetadata;
private EventBus eventBus;
private LoadController controller;
private DefaultArtifactVersion processedVersion;
private String annotationDependencies;
private VersionRange minecraftAccepted;
private boolean fingerprintNotPresent;
private Set<String> sourceFingerprints;
private Certificate certificate;
private String modLanguage;
private ILanguageAdapter languageAdapter;
private Disableable disableability;
private ListMultimap<Class<? extends FMLEvent>,Method> eventMethods;
private Map<String, String> customModProperties;
private ModCandidate candidate;
public FMLModContainer(String className, ModCandidate container, Map<String,Object> modDescriptor)
{
this.className = className;
this.source = container.getModContainer();
this.candidate = container;
this.descriptor = modDescriptor;
this.eventMethods = ArrayListMultimap.create();
this.modLanguage = (String) modDescriptor.get("modLanguage");
String languageAdapterType = (String)modDescriptor.get("modLanguageAdapter");
if (Strings.isNullOrEmpty(languageAdapterType))
{
this.languageAdapter = "scala".equals(modLanguage) ? new ILanguageAdapter.ScalaAdapter() : new ILanguageAdapter.JavaAdapter();
}
else
{
try
{
this.languageAdapter = (ILanguageAdapter)Class.forName(languageAdapterType, true, Loader.instance().getModClassLoader()).newInstance();
FMLLog.finer("Using custom language adapter %s (type %s) for %s (modid %s)", this.languageAdapter, languageAdapterType, this.className, getModId());
}
catch (Exception ex)
{
FMLLog.log(Level.ERROR, ex, "Error constructing custom mod language adapter %s (referenced by %s) (modid: %s)", languageAdapterType, this.className, getModId());
throw new LoaderException(ex);
}
}
}
private ILanguageAdapter getLanguageAdapter()
{
return languageAdapter;
}
@Override
public String getModId()
{
return (String) descriptor.get("modid");
}
@Override
public String getName()
{
return modMetadata.name;
}
@Override
public String getVersion()
{
return internalVersion;
}
@Override
public File getSource()
{
return source;
}
@Override
public ModMetadata getMetadata()
{
return modMetadata;
}
@Override
public void bindMetadata(MetadataCollection mc)
{
modMetadata = mc.getMetadataForId(getModId(), descriptor);
if (descriptor.containsKey("useMetadata"))
{
overridesMetadata = !((Boolean)descriptor.get("useMetadata")).booleanValue();
}
if (overridesMetadata || !modMetadata.useDependencyInformation)
{
Set<ArtifactVersion> requirements = Sets.newHashSet();
List<ArtifactVersion> dependencies = Lists.newArrayList();
List<ArtifactVersion> dependants = Lists.newArrayList();
annotationDependencies = (String) descriptor.get("dependencies");
Loader.instance().computeDependencies(annotationDependencies, requirements, dependencies, dependants);
dependants.addAll(Loader.instance().getInjectedBefore(getModId()));
dependencies.addAll(Loader.instance().getInjectedAfter(getModId()));
modMetadata.requiredMods = requirements;
modMetadata.dependencies = dependencies;
modMetadata.dependants = dependants;
FMLLog.log(getModId(), Level.TRACE, "Parsed dependency info : %s %s %s", requirements, dependencies, dependants);
}
else
{
FMLLog.log(getModId(), Level.TRACE, "Using mcmod dependency info : %s %s %s", modMetadata.requiredMods, modMetadata.dependencies, modMetadata.dependants);
}
if (Strings.isNullOrEmpty(modMetadata.name))
{
FMLLog.log(getModId(), Level.INFO,"Mod %s is missing the required element 'name'. Substituting %s", getModId(), getModId());
modMetadata.name = getModId();
}
internalVersion = (String) descriptor.get("version");
if (Strings.isNullOrEmpty(internalVersion))
{
Properties versionProps = searchForVersionProperties();
if (versionProps != null)
{
internalVersion = versionProps.getProperty(getModId()+".version");
FMLLog.log(getModId(), Level.DEBUG, "Found version %s for mod %s in version.properties, using", internalVersion, getModId());
}
}
if (Strings.isNullOrEmpty(internalVersion) && !Strings.isNullOrEmpty(modMetadata.version))
{
FMLLog.log(getModId(), Level.WARN, "Mod %s is missing the required element 'version' and a version.properties file could not be found. Falling back to metadata version %s", getModId(), modMetadata.version);
internalVersion = modMetadata.version;
}
if (Strings.isNullOrEmpty(internalVersion))
{
FMLLog.log(getModId(), Level.WARN, "Mod %s is missing the required element 'version' and no fallback can be found. Substituting '1.0'.", getModId());
modMetadata.version = internalVersion = "1.0";
}
String mcVersionString = (String) descriptor.get("acceptedMinecraftVersions");
if (!Strings.isNullOrEmpty(mcVersionString))
{
minecraftAccepted = VersionParser.parseRange(mcVersionString);
}
else
{
minecraftAccepted = Loader.instance().getMinecraftModContainer().getStaticVersionRange();
}
}
public Properties searchForVersionProperties()
{
try
{
FMLLog.log(getModId(), Level.DEBUG,"Attempting to load the file version.properties from %s to locate a version number for %s", getSource().getName(), getModId());
Properties version = null;
if (getSource().isFile())
{
ZipFile source = new ZipFile(getSource());
ZipEntry versionFile = source.getEntry("version.properties");
if (versionFile!=null)
{
version = new Properties();
version.load(source.getInputStream(versionFile));
}
source.close();
}
else if (getSource().isDirectory())
{
File propsFile = new File(getSource(),"version.properties");
if (propsFile.exists() && propsFile.isFile())
{
version = new Properties();
FileInputStream fis = new FileInputStream(propsFile);
version.load(fis);
fis.close();
}
}
return version;
}
catch (Exception e)
{
Throwables.propagateIfPossible(e);
FMLLog.log(getModId(), Level.TRACE, "Failed to find a usable version.properties file");
return null;
}
}
@Override
public void setEnabledState(boolean enabled)
{
this.enabled = enabled;
}
@Override
public Set<ArtifactVersion> getRequirements()
{
return modMetadata.requiredMods;
}
@Override
public List<ArtifactVersion> getDependencies()
{
return modMetadata.dependencies;
}
@Override
public List<ArtifactVersion> getDependants()
{
return modMetadata.dependants;
}
@Override
public String getSortingRules()
{
return ((overridesMetadata || !modMetadata.useDependencyInformation) ? Strings.nullToEmpty(annotationDependencies) : modMetadata.printableSortingRules());
}
@Override
public boolean matches(Object mod)
{
return mod == modInstance;
}
@Override
public Object getMod()
{
return modInstance;
}
@Override
public boolean registerBus(EventBus bus, LoadController controller)
{
if (this.enabled)
{
FMLLog.log(getModId(), Level.DEBUG, "Enabling mod %s", getModId());
this.eventBus = bus;
this.controller = controller;
eventBus.register(this);
return true;
}
else
{
return false;
}
}
@SuppressWarnings("unchecked")
private Method gatherAnnotations(Class<?> clazz) throws Exception
{
Method factoryMethod = null;
for (Method m : clazz.getDeclaredMethods())
{
for (Annotation a : m.getAnnotations())
{
if (a.annotationType().equals(Mod.EventHandler.class))
{
if (m.getParameterTypes().length == 1 && FMLEvent.class.isAssignableFrom(m.getParameterTypes()[0]))
{
m.setAccessible(true);
eventMethods.put((Class<? extends FMLEvent>) m.getParameterTypes()[0],m);
}
else
{
FMLLog.log(getModId(), Level.ERROR,"The mod %s appears to have an invalid event annotation %s. This annotation can only apply to methods with recognized event arguments - it will not be called", getModId(), a.annotationType().getSimpleName());
}
}
else if (a.annotationType().equals(Mod.InstanceFactory.class))
{
if (Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 && factoryMethod == null)
{
m.setAccessible(true);
factoryMethod = m;
}
else if (!(Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0))
{
FMLLog.log(getModId(), Level.ERROR, "The InstanceFactory annotation can only apply to a static method, taking zero arguments - it will be ignored on %s(%s)", m.getName(), Arrays.asList(m.getParameterTypes()));
}
else if (factoryMethod != null)
{
FMLLog.log(getModId(), Level.ERROR, "The InstanceFactory annotation can only be used once, the application to %s(%s) will be ignored", m.getName(), Arrays.asList(m.getParameterTypes()));
}
}
}
}
return factoryMethod;
}
private void processFieldAnnotations(ASMDataTable asmDataTable) throws Exception
{
SetMultimap<String, ASMData> annotations = asmDataTable.getAnnotationsFor(this);
parseSimpleFieldAnnotation(annotations, Instance.class.getName(), new Function<ModContainer, Object>()
{
@Override
public Object apply(ModContainer mc)
{
return mc.getMod();
}
});
parseSimpleFieldAnnotation(annotations, Metadata.class.getName(), new Function<ModContainer, Object>()
{
@Override
public Object apply(ModContainer mc)
{
return mc.getMetadata();
}
});
}
private void parseSimpleFieldAnnotation(SetMultimap<String, ASMData> annotations, String annotationClassName, Function<ModContainer, Object> retreiver) throws IllegalAccessException
{
String[] annName = annotationClassName.split("\\.");
String annotationName = annName[annName.length - 1];
for (ASMData targets : annotations.get(annotationClassName))
{
String targetMod = (String) targets.getAnnotationInfo().get("value");
Field f = null;
Object injectedMod = null;
ModContainer mc = this;
boolean isStatic = false;
Class<?> clz = modInstance.getClass();
if (!Strings.isNullOrEmpty(targetMod))
{
if (Loader.isModLoaded(targetMod))
{
mc = Loader.instance().getIndexedModList().get(targetMod);
}
else
{
mc = null;
}
}
if (mc != null)
{
try
{
clz = Class.forName(targets.getClassName(), true, Loader.instance().getModClassLoader());
f = clz.getDeclaredField(targets.getObjectName());
f.setAccessible(true);
isStatic = Modifier.isStatic(f.getModifiers());
injectedMod = retreiver.apply(mc);
}
catch (Exception e)
{
Throwables.propagateIfPossible(e);
FMLLog.log(getModId(), Level.WARN, e, "Attempting to load @%s in class %s for %s and failing", annotationName, targets.getClassName(), mc.getModId());
}
}
if (f != null)
{
Object target = null;
if (!isStatic)
{
target = modInstance;
if (!modInstance.getClass().equals(clz))
{
FMLLog.log(getModId(), Level.WARN, "Unable to inject @%s in non-static field %s.%s for %s as it is NOT the primary mod instance", annotationName, targets.getClassName(), targets.getObjectName(), mc.getModId());
continue;
}
}
f.set(target, injectedMod);
}
}
}
@Subscribe
public void constructMod(FMLConstructionEvent event)
{
try
{
BlamingTransformer.addClasses(getModId(), candidate.getClassList());
ModClassLoader modClassLoader = event.getModClassLoader();
modClassLoader.addFile(source);
modClassLoader.clearNegativeCacheFor(candidate.getClassList());
Class<?> clazz = Class.forName(className, true, modClassLoader);
Certificate[] certificates = clazz.getProtectionDomain().getCodeSource().getCertificates();
int len = 0;
if (certificates != null)
{
len = certificates.length;
}
Builder<String> certBuilder = ImmutableList.<String>builder();
for (int i = 0; i < len; i++)
{
certBuilder.add(CertificateHelper.getFingerprint(certificates[i]));
}
ImmutableList<String> certList = certBuilder.build();
sourceFingerprints = ImmutableSet.copyOf(certList);
String expectedFingerprint = (String) descriptor.get("certificateFingerprint");
fingerprintNotPresent = true;
if (expectedFingerprint != null && !expectedFingerprint.isEmpty())
{
if (!sourceFingerprints.contains(expectedFingerprint))
{
Level warnLevel = Level.ERROR;
if (source.isDirectory())
{
warnLevel = Level.TRACE;
}
FMLLog.log(getModId(), warnLevel, "The mod %s is expecting signature %s for source %s, however there is no signature matching that description", getModId(), expectedFingerprint, source.getName());
}
else
{
certificate = certificates[certList.indexOf(expectedFingerprint)];
fingerprintNotPresent = false;
}
}
@SuppressWarnings("unchecked")
List<Map<String,Object>> props = (List<Map<String, Object>>) descriptor.get("customProperties");
if (props != null)
{
com.google.common.collect.ImmutableMap.Builder<String, String> builder = ImmutableMap.<String,String>builder();
for (Map<String, Object> p : props)
{
builder.put((String)p.get("k"),(String)p.get("v"));
}
customModProperties = builder.build();
}
else
{
customModProperties = EMPTY_PROPERTIES;
}
Boolean hasDisableableFlag = (Boolean) descriptor.get("canBeDeactivated");
boolean hasReverseDepends = !event.getReverseDependencies().get(getModId()).isEmpty();
if (hasDisableableFlag != null && hasDisableableFlag)
{
disableability = hasReverseDepends ? Disableable.DEPENDENCIES : Disableable.YES;
}
else
{
disableability = hasReverseDepends ? Disableable.DEPENDENCIES : Disableable.RESTART;
}
Method factoryMethod = gatherAnnotations(clazz);
modInstance = getLanguageAdapter().getNewInstance(this,clazz, modClassLoader, factoryMethod);
NetworkRegistry.INSTANCE.register(this, clazz, (String)(descriptor.containsKey("acceptableRemoteVersions") ? descriptor.get("acceptableRemoteVersions") : null), event.getASMHarvestedData());
if (fingerprintNotPresent)
{
eventBus.post(new FMLFingerprintViolationEvent(source.isDirectory(), source, ImmutableSet.copyOf(this.sourceFingerprints), expectedFingerprint));
}
ProxyInjector.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide(), getLanguageAdapter());
processFieldAnnotations(event.getASMHarvestedData());
}
catch (Throwable e)
{
controller.errorOccurred(this, e);
}
}
@Subscribe
public void handleModStateEvent(FMLEvent event)
{
if (!eventMethods.containsKey(event.getClass()))
{
return;
}
try
{
for (Method m : eventMethods.get(event.getClass()))
{
m.invoke(modInstance, event);
}
}
catch (Throwable t)
{
controller.errorOccurred(this, t);
}
}
@Override
public ArtifactVersion getProcessedVersion()
{
if (processedVersion == null)
{
processedVersion = new DefaultArtifactVersion(getModId(), getVersion());
}
return processedVersion;
}
@Override
public boolean isImmutable()
{
return false;
}
@Override
public String getDisplayVersion()
{
return modMetadata.version;
}
@Override
public VersionRange acceptableMinecraftVersionRange()
{
return minecraftAccepted;
}
@Override
public Certificate getSigningCertificate()
{
return certificate;
}
@Override
public String toString()
{
return "FMLMod:"+getModId()+"{"+getVersion()+"}";
}
@Override
public Map<String, String> getCustomModProperties()
{
return customModProperties;
}
@Override
public Class<?> getCustomResourcePackClass()
{
try
{
return getSource().isDirectory() ? Class.forName("net.minecraftforge.fml.client.FMLFolderResourcePack", true, getClass().getClassLoader()) : Class.forName("net.minecraftforge.fml.client.FMLFileResourcePack",true, getClass().getClassLoader());
}
catch (ClassNotFoundException e)
{
return null;
}
}
@Override
public Map<String, String> getSharedModDescriptor()
{
Map<String,String> descriptor = Maps.newHashMap();
descriptor.put("modsystem", "FML");
descriptor.put("id", getModId());
descriptor.put("version",getDisplayVersion());
descriptor.put("name", getName());
descriptor.put("url", modMetadata.url);
descriptor.put("authors", modMetadata.getAuthorList());
descriptor.put("description", modMetadata.description);
return descriptor;
}
@Override
public Disableable canBeDisabled()
{
return disableability;
}
@Override
public String getGuiClassName()
{
return (String) descriptor.get("guiFactory");
}
@Override
public List<String> getOwnedPackages()
{
return candidate.getContainedPackages();
}
private boolean isTrue(Boolean value)
{
if (value == null) return false;
return value.booleanValue();
}
@Override
public boolean shouldLoadInEnvironment()
{
boolean clientSideOnly = isTrue((Boolean)descriptor.get("clientSideOnly"));
boolean serverSideOnly = isTrue((Boolean)descriptor.get("serverSideOnly"));
if (clientSideOnly && serverSideOnly)
throw new RuntimeException("Mod annotation claims to be both client and server side only!");
Side side = FMLCommonHandler.instance().getSide();
if (clientSideOnly && side != Side.CLIENT)
{
FMLLog.info("Disabling mod %s it is client side only.", getModId());
return false;
}
if (serverSideOnly && side != Side.SERVER)
{
FMLLog.info("Disabling mod %s it is server side only.", getModId());
return false;
}
return true;
}
}
@@ -0,0 +1,34 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import org.objectweb.asm.tree.ClassNode;
public interface IASMHook {
/**
* Inject the {@link Mod} class node into this instance. This allows retrieval from custom
* attributes or other artifacts in your mod class
*
* @param modClassNode The mod class
* @return optionally some code generated classes that will be injected into the classloader
*/
ClassNode[] inject(ClassNode modClassNode);
/**
* Allow mods to manipulate classes loaded from this {@link Mod}'s jar file. The {@link Mod}
* class is always guaranteed to be called first.
* The node state should be changed in place.
*
* @param node The class being loaded
*/
void modifyClass(String className, ClassNode node);
}
@@ -0,0 +1,20 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.util.concurrent.Callable;
public interface ICrashCallable extends Callable<String>
{
String getLabel();
}
@@ -0,0 +1,18 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
public interface IFMLHandledException
{
}
@@ -0,0 +1,69 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.io.File;
import java.util.List;
import java.util.Set;
import net.minecraft.network.INetHandler;
import net.minecraft.network.NetworkManager;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.IThreadListener;
import net.minecraftforge.fml.common.eventhandler.EventBus;
import net.minecraftforge.fml.relauncher.Side;
public interface IFMLSidedHandler
{
List<String> getAdditionalBrandingInformation();
Side getSide();
void haltGame(String message, Throwable exception);
void showGuiScreen(Object clientGuiElement);
void queryUser(StartupQuery query) throws InterruptedException;
void beginServerLoading(MinecraftServer server);
void finishServerLoading();
File getSavesDirectory();
MinecraftServer getServer();
boolean shouldServerShouldBeKilledQuietly();
void addModAsResource(ModContainer container);
String getCurrentLanguage();
void serverStopped();
NetworkManager getClientToServerNetworkManager();
INetHandler getClientPlayHandler();
void fireNetRegistrationEvent(EventBus bus, NetworkManager manager, Set<String> channelSet, String channel, Side side);
boolean shouldAllowPlayerLogins();
void allowLogins();
IThreadListener getWorldThread(INetHandler net);
void processWindowMessages();
String stripSpecialChars(String message);
}
@@ -0,0 +1,20 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import net.minecraft.item.ItemStack;
public interface IFuelHandler
{
int getBurnTime(ItemStack fuel);
}
@@ -0,0 +1,197 @@
package net.minecraftforge.fml.common;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.minecraftforge.fml.relauncher.Side;
import org.apache.logging.log4j.Level;
public interface ILanguageAdapter {
public Object getNewInstance(FMLModContainer container, Class<?> objectClass, ClassLoader classLoader, Method factoryMarkedAnnotation) throws Exception;
public boolean supportsStatics();
public void setProxy(Field target, Class<?> proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException;
public void setInternalProxies(ModContainer mod, Side side, ClassLoader loader);
public static class ScalaAdapter implements ILanguageAdapter {
@Override
public Object getNewInstance(FMLModContainer container, Class<?> scalaObjectClass, ClassLoader classLoader, Method factoryMarkedAnnotation) throws Exception
{
Class<?> sObjectClass = Class.forName(scalaObjectClass.getName()+"$",true,classLoader);
return sObjectClass.getField("MODULE$").get(null);
}
@Override
public boolean supportsStatics()
{
return false;
}
@Override
public void setProxy(Field target, Class<?> proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException, SecurityException
{
try
{
// Get the actual singleton class. The two variants are from
// whether the @SidedProxy is declared in a the class block
// of the object directly, or in the object block, i.e.
// whether it's:
// class ModName {
// @SidedProxy ...
// }
// object ModName extends ModName {}
// which leads to us getting the outer class, or
// object ModName {
// @SidedProxy ...
// }
// which leads to us getting the inner class.
if (!proxyTarget.getName().endsWith("$"))
{
// Get internal class generated by Scala.
proxyTarget = Class.forName(proxyTarget.getName() + "$", true, proxyTarget.getClassLoader());
}
}
catch (ClassNotFoundException e)
{
// Not a singleton, look for @Instance field as a fallback.
FMLLog.log(Level.INFO, e, "An error occured trying to load a proxy into %s.%s. Did you declare your mod as 'class' instead of 'object'?", proxyTarget.getSimpleName(), target.getName());
return;
}
// Get the instance via the MODULE$ field which is
// automatically generated by the Scala compiler for
// singletons.
Object targetInstance = proxyTarget.getField("MODULE$").get(null);
try
{
// Find setter function. We do it this way because we can't
// necessarily use proxyTarget.getMethod(proxy.getClass()), as
// it might be a subclass and not the exact parameter type.
// All fields are private in Scala, wrapped by a getter and
// setter named <fieldname> and <fieldname>_$eq. To those
// familiar with scala.reflect.BeanProperty: these will always
// be there, set<Fieldname> and get<Fieldname> will always
// only be generated *additionally*.
final String setterName = target.getName() + "_$eq";
for (Method setter : proxyTarget.getMethods())
{
Class<?>[] setterParameters = setter.getParameterTypes();
if (setterName.equals(setter.getName()) &&
// Some more validation.
setterParameters.length == 1 &&
setterParameters[0].isAssignableFrom(proxy.getClass()))
{
// Here goes nothing...
setter.invoke(targetInstance, proxy);
return;
}
}
}
catch (InvocationTargetException e)
{
FMLLog.log(Level.ERROR, e, "An error occured trying to load a proxy into %s.%s", proxyTarget.getSimpleName(), target.getName());
throw new LoaderException(e);
}
// If we come here we could not find a setter for this proxy.
FMLLog.severe("Failed loading proxy into %s.%s, could not find setter function. Did you declare the field with 'val' instead of 'var'?", proxyTarget.getSimpleName(), target.getName());
throw new LoaderException(String.format("Failed loading proxy into %s.%s, could not find setter function. Did you declare the field with 'val' instead of 'var'?", proxyTarget.getSimpleName(), target.getName()));
}
@Override
public void setInternalProxies(ModContainer mod, Side side, ClassLoader loader)
{
// For Scala mods, we want to enable authors to write them like so:
// object ModName {
// @SidedProxy(...)
// var proxy: ModProxy = null
// }
// For this to work, we have to search inside the inner class Scala
// generates for singletons, which is in called ModName$. These are
// not automatically handled, because the mod discovery code ignores
// internal classes.
// Note that it is alternatively possible to write this like so:
// class ModName {
// @SidedProxy(...)
// var proxy: ModProxy = null
// }
// object ModName extends ModName { ... }
// which will fall back to the normal injection code which calls
// setProxy in turn.
// Get the actual mod implementation, which will be the inner class
// if we have a singleton.
Class<?> proxyTarget = mod.getMod().getClass();
if (proxyTarget.getName().endsWith("$"))
{
// So we have a singleton class, check if there are targets.
for (Field target : proxyTarget.getDeclaredFields())
{
// This will not turn up anything if the alternative
// approach was taken (manually declaring the class).
// So we don't initialize the field twice.
if (target.getAnnotation(SidedProxy.class) != null)
{
String targetType = side.isClient() ? target.getAnnotation(SidedProxy.class).clientSide() : target.getAnnotation(SidedProxy.class).serverSide();
try
{
Object proxy = Class.forName(targetType, true, loader).newInstance();
if (!target.getType().isAssignableFrom(proxy.getClass()))
{
FMLLog.severe("Attempted to load a proxy type %s into %s.%s, but the types don't match", targetType, proxyTarget.getSimpleName(), target.getName());
throw new LoaderException(String.format("Attempted to load a proxy type %s into %s.%s, but the types don't match", targetType, proxyTarget.getSimpleName(), target.getName()));
}
setProxy(target, proxyTarget, proxy);
}
catch (Exception e) {
FMLLog.log(Level.ERROR, e, "An error occured trying to load a proxy into %s.%s", proxyTarget.getSimpleName(), target.getName());
throw new LoaderException(e);
}
}
}
}
else
{
FMLLog.finer("Mod does not appear to be a singleton.");
}
}
}
public static class JavaAdapter implements ILanguageAdapter {
@Override
public Object getNewInstance(FMLModContainer container, Class<?> objectClass, ClassLoader classLoader, Method factoryMarkedMethod) throws Exception
{
if (factoryMarkedMethod != null)
{
return factoryMarkedMethod.invoke(null);
}
else
{
return objectClass.newInstance();
}
}
@Override
public boolean supportsStatics()
{
return true;
}
@Override
public void setProxy(Field target, Class<?> proxyTarget, Object proxy) throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
SecurityException
{
target.set(null, proxy);
}
@Override
public void setInternalProxies(ModContainer mod, Side side, ClassLoader loader)
{
// Nothing to do here.
}
}
}
@@ -0,0 +1,41 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.util.Random;
import net.minecraft.world.World;
import net.minecraft.world.chunk.IChunkProvider;
/**
* This is called back during world generation.
*
* @author cpw
*
*/
public interface IWorldGenerator
{
/**
* Generate some world
*
* @param random the chunk specific {@link Random}.
* @param chunkX the chunk X coordinate of this chunk.
* @param chunkZ the chunk Z coordinate of this chunk.
* @param world : additionalData[0] The minecraft {@link World} we're generating for.
* @param chunkGenerator : additionalData[1] The {@link IChunkProvider} that is generating.
* @param chunkProvider : additionalData[2] {@link IChunkProvider} that is requesting the world generation.
*
*/
public void generate(Random random, int chunkX, int chunkZ, World world, IChunkProvider chunkGenerator, IChunkProvider chunkProvider);
}
@@ -0,0 +1,210 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.io.File;
import java.security.cert.Certificate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionRange;
import com.google.common.eventbus.EventBus;
public class InjectedModContainer implements ModContainer
{
private File source;
public final ModContainer wrappedContainer;
public InjectedModContainer(ModContainer mc, File source)
{
this.source = source != null ? source : new File("minecraft.jar");
this.wrappedContainer = mc;
}
@Override
public String getModId()
{
return wrappedContainer.getModId();
}
@Override
public String getName()
{
return wrappedContainer.getName();
}
@Override
public String getVersion()
{
return wrappedContainer.getVersion();
}
@Override
public File getSource()
{
return source;
}
@Override
public ModMetadata getMetadata()
{
return wrappedContainer.getMetadata();
}
@Override
public void bindMetadata(MetadataCollection mc)
{
wrappedContainer.bindMetadata(mc);
}
@Override
public void setEnabledState(boolean enabled)
{
wrappedContainer.setEnabledState(enabled);
}
@Override
public Set<ArtifactVersion> getRequirements()
{
return wrappedContainer.getRequirements();
}
@Override
public List<ArtifactVersion> getDependencies()
{
return wrappedContainer.getDependencies();
}
@Override
public List<ArtifactVersion> getDependants()
{
return wrappedContainer.getDependants();
}
@Override
public String getSortingRules()
{
return wrappedContainer.getSortingRules();
}
@Override
public boolean registerBus(EventBus bus, LoadController controller)
{
return wrappedContainer.registerBus(bus, controller);
}
@Override
public boolean matches(Object mod)
{
return wrappedContainer.matches(mod);
}
@Override
public Object getMod()
{
return wrappedContainer.getMod();
}
@Override
public ArtifactVersion getProcessedVersion()
{
return wrappedContainer.getProcessedVersion();
}
@Override
public boolean isImmutable()
{
return true;
}
@Override
public String getDisplayVersion()
{
return wrappedContainer.getDisplayVersion();
}
@Override
public VersionRange acceptableMinecraftVersionRange()
{
return wrappedContainer.acceptableMinecraftVersionRange();
}
public WorldAccessContainer getWrappedWorldAccessContainer()
{
if (wrappedContainer instanceof WorldAccessContainer)
{
return (WorldAccessContainer) wrappedContainer;
}
else
{
return null;
}
}
@Override
public Certificate getSigningCertificate()
{
return wrappedContainer.getSigningCertificate();
}
@Override
public String toString()
{
return "Wrapped{"+wrappedContainer.toString()+"}";
}
@Override
public Map<String, String> getCustomModProperties()
{
return wrappedContainer.getCustomModProperties();
}
@Override
public Class<?> getCustomResourcePackClass()
{
return wrappedContainer.getCustomResourcePackClass();
}
@Override
public Map<String, String> getSharedModDescriptor()
{
return wrappedContainer.getSharedModDescriptor();
}
@Override
public Disableable canBeDisabled()
{
return wrappedContainer.canBeDisabled();
}
@Override
public String getGuiClassName()
{
return wrappedContainer.getGuiClassName();
}
@Override
public List<String> getOwnedPackages()
{
return wrappedContainer.getOwnedPackages();
}
@Override
public boolean shouldLoadInEnvironment()
{
return true;
}
}
@@ -0,0 +1,367 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import net.minecraftforge.fml.common.LoaderState.ModState;
import net.minecraftforge.fml.common.ProgressManager.ProgressBar;
import net.minecraftforge.fml.common.event.FMLEvent;
import net.minecraftforge.fml.common.event.FMLLoadEvent;
import net.minecraftforge.fml.common.event.FMLModDisabledEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.event.FMLStateEvent;
import net.minecraftforge.fml.common.functions.ArtifactVersionNameFunction;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.ThreadContext;
import com.google.common.base.Joiner;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.BiMap;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
public class LoadController
{
private Loader loader;
private EventBus masterChannel;
private ImmutableMap<String,EventBus> eventChannels;
private LoaderState state;
private Multimap<String, ModState> modStates = ArrayListMultimap.create();
private Multimap<String, Throwable> errors = ArrayListMultimap.create();
private List<ModContainer> activeModList = Lists.newArrayList();
private ModContainer activeContainer;
private BiMap<ModContainer, Object> modObjectList;
private ListMultimap<String, ModContainer> packageOwners;
public LoadController(Loader loader)
{
this.loader = loader;
this.masterChannel = new EventBus("FMLMainChannel");
this.masterChannel.register(this);
state = LoaderState.NOINIT;
packageOwners = ArrayListMultimap.create();
}
void disableMod(ModContainer mod)
{
HashMap<String, EventBus> temporary = Maps.newHashMap(eventChannels);
String modId = mod.getModId();
EventBus bus = temporary.remove(modId);
bus.post(new FMLModDisabledEvent());
if (errors.get(modId).isEmpty())
{
eventChannels = ImmutableMap.copyOf(temporary);
modStates.put(modId, ModState.DISABLED);
modObjectList.remove(mod);
activeModList.remove(mod);
}
}
@Subscribe
public void buildModList(FMLLoadEvent event)
{
Builder<String, EventBus> eventBus = ImmutableMap.builder();
for (ModContainer mod : loader.getModList())
{
//Create mod logger, and make the EventBus logger a child of it.
EventBus bus = new EventBus(mod.getModId());
boolean isActive = mod.registerBus(bus, this);
if (isActive)
{
activeModList.add(mod);
modStates.put(mod.getModId(), ModState.UNLOADED);
eventBus.put(mod.getModId(), bus);
FMLCommonHandler.instance().addModToResourcePack(mod);
}
else
{
FMLLog.log(mod.getModId(), Level.WARN, "Mod %s has been disabled through configuration", mod.getModId());
modStates.put(mod.getModId(), ModState.UNLOADED);
modStates.put(mod.getModId(), ModState.DISABLED);
}
}
eventChannels = eventBus.build();
}
public void distributeStateMessage(LoaderState state, Object... eventData)
{
if (state.hasEvent())
{
masterChannel.post(state.getEvent(eventData));
}
}
public void transition(LoaderState desiredState, boolean forceState)
{
LoaderState oldState = state;
state = state.transition(!errors.isEmpty());
if (state != desiredState && !forceState)
{
Throwable toThrow = null;
FMLLog.severe("Fatal errors were detected during the transition from %s to %s. Loading cannot continue", oldState, desiredState);
StringBuilder sb = new StringBuilder();
printModStates(sb);
FMLLog.severe("%s", sb.toString());
if (errors.size()>0)
{
FMLLog.severe("The following problems were captured during this phase");
for (Entry<String, Throwable> error : errors.entries())
{
FMLLog.log(Level.ERROR, error.getValue(), "Caught exception from %s", error.getKey());
if (error.getValue() instanceof IFMLHandledException)
{
toThrow = error.getValue();
}
else if (toThrow == null)
{
toThrow = error.getValue();
}
}
}
else
{
FMLLog.severe("The ForgeModLoader state engine has become corrupted. Probably, a state was missed by and invalid modification to a base class" +
"ForgeModLoader depends on. This is a critical error and not recoverable. Investigate any modifications to base classes outside of" +
"ForgeModLoader, especially Optifine, to see if there are fixes available.");
throw new RuntimeException("The ForgeModLoader state engine is invalid");
}
if (toThrow != null && toThrow instanceof RuntimeException)
{
throw (RuntimeException)toThrow;
}
else
{
throw new LoaderException(toThrow);
}
}
else if (state != desiredState && forceState)
{
FMLLog.info("The state engine was in incorrect state %s and forced into state %s. Errors may have been discarded.", state, desiredState);
forceState(desiredState);
}
}
public ModContainer activeContainer()
{
return activeContainer != null ? activeContainer : findActiveContainerFromStack();
}
@Subscribe
public void propogateStateMessage(FMLEvent stateEvent)
{
if (stateEvent instanceof FMLPreInitializationEvent)
{
modObjectList = buildModObjectList();
}
ProgressBar bar = ProgressManager.push(stateEvent.description(), activeModList.size(), true);
for (ModContainer mc : activeModList)
{
bar.step(mc.getName());
sendEventToModContainer(stateEvent, mc);
}
ProgressManager.pop(bar);
}
private void sendEventToModContainer(FMLEvent stateEvent, ModContainer mc)
{
String modId = mc.getModId();
Collection<String> requirements = Collections2.transform(mc.getRequirements(),new ArtifactVersionNameFunction());
for (ArtifactVersion av : mc.getDependencies())
{
if (av.getLabel()!= null && requirements.contains(av.getLabel()) && modStates.containsEntry(av.getLabel(),ModState.ERRORED))
{
FMLLog.log(modId, Level.ERROR, "Skipping event %s and marking errored mod %s since required dependency %s has errored", stateEvent.getEventType(), modId, av.getLabel());
modStates.put(modId, ModState.ERRORED);
return;
}
}
activeContainer = mc;
stateEvent.applyModContainer(activeContainer());
ThreadContext.put("mod", modId);
FMLLog.log(modId, Level.TRACE, "Sending event %s to mod %s", stateEvent.getEventType(), modId);
eventChannels.get(modId).post(stateEvent);
FMLLog.log(modId, Level.TRACE, "Sent event %s to mod %s", stateEvent.getEventType(), modId);
ThreadContext.remove("mod");
activeContainer = null;
if (stateEvent instanceof FMLStateEvent)
{
if (!errors.containsKey(modId))
{
modStates.put(modId, ((FMLStateEvent)stateEvent).getModState());
}
else
{
modStates.put(modId, ModState.ERRORED);
}
}
}
public ImmutableBiMap<ModContainer, Object> buildModObjectList()
{
ImmutableBiMap.Builder<ModContainer, Object> builder = ImmutableBiMap.<ModContainer, Object>builder();
for (ModContainer mc : activeModList)
{
if (!mc.isImmutable() && mc.getMod()!=null)
{
builder.put(mc, mc.getMod());
List<String> packages = mc.getOwnedPackages();
for (String pkg : packages)
{
packageOwners.put(pkg, mc);
}
}
if (mc.getMod()==null && !mc.isImmutable() && state!=LoaderState.CONSTRUCTING)
{
FMLLog.severe("There is a severe problem with %s - it appears not to have constructed correctly", mc.getModId());
if (state != LoaderState.CONSTRUCTING)
{
this.errorOccurred(mc, new RuntimeException());
}
}
}
return builder.build();
}
public void errorOccurred(ModContainer modContainer, Throwable exception)
{
if (exception instanceof InvocationTargetException)
{
errors.put(modContainer.getModId(), ((InvocationTargetException)exception).getCause());
}
else
{
errors.put(modContainer.getModId(), exception);
}
}
public void printModStates(StringBuilder ret)
{
ret.append("\n\tStates:");
for (ModState state : ModState.values())
ret.append(" '").append(state.getMarker()).append("' = ").append(state.toString());
for (ModContainer mc : loader.getModList())
{
ret.append("\n\t");
for (ModState state : modStates.get(mc.getModId()))
ret.append(state.getMarker());
ret.append("\t").append(mc.getModId()).append("{").append(mc.getVersion()).append("} [").append(mc.getName()).append("] (").append(mc.getSource().getName()).append(") ");
}
}
public List<ModContainer> getActiveModList()
{
return activeModList;
}
public ModState getModState(ModContainer selectedMod)
{
return Iterables.getLast(modStates.get(selectedMod.getModId()), ModState.AVAILABLE);
}
public void distributeStateMessage(Class<?> customEvent)
{
try
{
masterChannel.post(customEvent.newInstance());
}
catch (Exception e)
{
FMLLog.log(Level.ERROR, e, "An unexpected exception");
throw new LoaderException(e);
}
}
public BiMap<ModContainer, Object> getModObjectList()
{
if (modObjectList == null)
{
FMLLog.severe("Detected an attempt by a mod %s to perform game activity during mod construction. This is a serious programming error.", activeContainer);
return buildModObjectList();
}
return ImmutableBiMap.copyOf(modObjectList);
}
public boolean isInState(LoaderState state)
{
return this.state == state;
}
boolean hasReachedState(LoaderState state) {
return this.state.ordinal()>=state.ordinal() && this.state!=LoaderState.ERRORED;
}
void forceState(LoaderState newState)
{
this.state = newState;
}
private ModContainer findActiveContainerFromStack()
{
for (Class<?> c : getCallingStack())
{
int idx = c.getName().lastIndexOf('.');
if (idx == -1)
{
continue;
}
String pkg = c.getName().substring(0,idx);
if (packageOwners.containsKey(pkg))
{
return packageOwners.get(pkg).get(0);
}
}
return null;
}
private FMLSecurityManager accessibleManager = new FMLSecurityManager();
class FMLSecurityManager extends SecurityManager
{
Class<?>[] getStackClasses()
{
return getClassContext();
}
}
Class<?>[] getCallingStack()
{
return accessibleManager.getStackClasses();
}
LoaderState getState()
{
return state;
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,36 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
public class LoaderException extends EnhancedRuntimeException
{
/**
*
*/
private static final long serialVersionUID = -5675297950958861378L;
public LoaderException(Throwable wrapped)
{
super(wrapped);
}
public LoaderException()
{
}
public LoaderException(String message)
{
super(message);
}
@Override protected void printStackTrace(WrappedPrintStream stream){}
}
@@ -0,0 +1,127 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import net.minecraftforge.fml.common.event.FMLConstructionEvent;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLLoadCompleteEvent;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.event.FMLServerAboutToStartEvent;
import net.minecraftforge.fml.common.event.FMLServerStartedEvent;
import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
import net.minecraftforge.fml.common.event.FMLServerStoppedEvent;
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent;
import net.minecraftforge.fml.common.event.FMLStateEvent;
import com.google.common.base.Throwables;
/**
* The state enum used to help track state progression for the loader
* @author cpw
*
*/
public enum LoaderState
{
NOINIT("Uninitialized",null),
LOADING("Loading",null),
CONSTRUCTING("Constructing mods",FMLConstructionEvent.class),
PREINITIALIZATION("Pre-initializing mods", FMLPreInitializationEvent.class),
INITIALIZATION("Initializing mods", FMLInitializationEvent.class),
POSTINITIALIZATION("Post-initializing mods", FMLPostInitializationEvent.class),
AVAILABLE("Mod loading complete", FMLLoadCompleteEvent.class),
SERVER_ABOUT_TO_START("Server about to start", FMLServerAboutToStartEvent.class),
SERVER_STARTING("Server starting", FMLServerStartingEvent.class),
SERVER_STARTED("Server started", FMLServerStartedEvent.class),
SERVER_STOPPING("Server stopping", FMLServerStoppingEvent.class),
SERVER_STOPPED("Server stopped", FMLServerStoppedEvent.class),
ERRORED("Mod Loading errored",null);
private Class<? extends FMLStateEvent> eventClass;
private String name;
private LoaderState(String name, Class<? extends FMLStateEvent> event)
{
this.name = name;
this.eventClass = event;
}
public LoaderState transition(boolean errored)
{
if (errored)
{
return ERRORED;
}
// stopping -> available
if (this == SERVER_STOPPED)
{
return AVAILABLE;
}
return values()[ordinal() < values().length ? ordinal()+1 : ordinal()];
}
public boolean hasEvent()
{
return eventClass != null;
}
public FMLStateEvent getEvent(Object... eventData)
{
try
{
return eventClass.getConstructor(Object[].class).newInstance((Object)eventData);
}
catch (Exception e)
{
throw Throwables.propagate(e);
}
}
public LoaderState requiredState()
{
if (this == NOINIT) return NOINIT;
return LoaderState.values()[this.ordinal()-1];
}
public enum ModState
{
UNLOADED ("Unloaded", "U"),
LOADED ("Loaded", "L"),
CONSTRUCTED ("Constructed", "C"),
PREINITIALIZED ("Pre-initialized", "H"),
INITIALIZED ("Initialized", "I"),
POSTINITIALIZED("Post-initialized", "J"),
AVAILABLE ("Available", "A"),
DISABLED ("Disabled", "D"),
ERRORED ("Errored", "E");
private String label;
private String marker;
private ModState(String label, String marker)
{
this.label = label;
this.marker = marker;
}
@Override
public String toString()
{
return this.label;
}
public String getMarker()
{
return this.marker;
}
}
}
@@ -0,0 +1,32 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import com.google.common.eventbus.EventBus;
public class MCPDummyContainer extends DummyModContainer {
public MCPDummyContainer(ModMetadata metadata) {
super(metadata);
}
@Override
public boolean registerBus(EventBus bus, LoadController controller) {
return true;
}
@Override
public Disableable canBeDisabled()
{
return Disableable.YES;
}
}
@@ -0,0 +1,125 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionParser;
import org.apache.logging.log4j.Level;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class MetadataCollection
{
private String modListVersion;
private ModMetadata[] modList;
private Map<String, ModMetadata> metadatas = Maps.newHashMap();
public static MetadataCollection from(InputStream inputStream, String sourceName)
{
if (inputStream == null)
{
return new MetadataCollection();
}
InputStreamReader reader = new InputStreamReader(inputStream);
try
{
MetadataCollection collection;
Gson gson = new GsonBuilder().registerTypeAdapter(ArtifactVersion.class, new ArtifactVersionAdapter()).create();
JsonParser parser = new JsonParser();
JsonElement rootElement = parser.parse(reader);
if (rootElement.isJsonArray())
{
collection = new MetadataCollection();
JsonArray jsonList = rootElement.getAsJsonArray();
collection.modList = new ModMetadata[jsonList.size()];
int i = 0;
for (JsonElement mod : jsonList)
{
collection.modList[i++]=gson.fromJson(mod, ModMetadata.class);
}
}
else
{
collection = gson.fromJson(rootElement, MetadataCollection.class);
}
collection.parseModMetadataList();
return collection;
}
catch (JsonParseException e)
{
FMLLog.log(Level.ERROR, e, "The mcmod.info file in %s cannot be parsed as valid JSON. It will be ignored", sourceName);
return new MetadataCollection();
}
catch (Exception e)
{
throw Throwables.propagate(e);
}
}
private void parseModMetadataList()
{
for (ModMetadata modMetadata : modList)
{
metadatas.put(modMetadata.modId, modMetadata);
}
}
public ModMetadata getMetadataForId(String modId, Map<String, Object> extraData)
{
if (!metadatas.containsKey(modId))
{
ModMetadata dummy = new ModMetadata();
dummy.modId = modId;
dummy.name = (String) extraData.get("name");
dummy.version = (String) extraData.get("version");
dummy.autogenerated = true;
metadatas.put(modId, dummy);
}
return metadatas.get(modId);
}
public static class ArtifactVersionAdapter extends TypeAdapter<ArtifactVersion>
{
@Override
public void write(JsonWriter out, ArtifactVersion value) throws IOException
{
// no op - we never write these out
}
@Override
public ArtifactVersion read(JsonReader in) throws IOException
{
return VersionParser.parseVersionReference(in.nextString());
}
}
}
@@ -0,0 +1,56 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.security.cert.Certificate;
import net.minecraftforge.fml.common.versioning.VersionParser;
import net.minecraftforge.fml.common.versioning.VersionRange;
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
import net.minecraftforge.fml.relauncher.Side;
public class MinecraftDummyContainer extends DummyModContainer
{
private VersionRange staticRange;
public MinecraftDummyContainer(String actualMCVersion)
{
super(new ModMetadata());
getMetadata().modId = "Minecraft";
getMetadata().name = "Minecraft";
getMetadata().version = actualMCVersion;
staticRange = VersionParser.parseRange("["+actualMCVersion+"]");
}
public VersionRange getStaticVersionRange()
{
return staticRange;
}
@Override
public Certificate getSigningCertificate()
{
if (FMLLaunchHandler.side() != Side.CLIENT)
return null;
try
{
Class<?> cbr = Class.forName("net.minecraft.client.ClientBrandRetriever", false, getClass().getClassLoader());
Certificate[] certificates = cbr.getProtectionDomain().getCodeSource().getCertificates();
return certificates != null ? certificates[0] : null;
}
catch (Exception e){} // Errors don't matter just return null.
return null;
}
}
@@ -0,0 +1,41 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.util.Set;
import net.minecraftforge.fml.common.EnhancedRuntimeException.WrappedPrintStream;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
public class MissingModsException extends EnhancedRuntimeException
{
private static final long serialVersionUID = 1L;
public final Set<ArtifactVersion> missingMods;
public MissingModsException(Set<ArtifactVersion> missingMods, String id, String name)
{
super(String.format("Mod %s (%s) requires %s", id, name, missingMods));
this.missingMods = missingMods;
}
@Override
protected void printStackTrace(WrappedPrintStream stream)
{
stream.println("Missing Mods:");
for (ArtifactVersion v : missingMods)
{
stream.println(String.format("\t%s : %s", v.getLabel(), v.getRangeString()));
}
stream.println("");
}
}
@@ -0,0 +1,308 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import net.minecraftforge.fml.client.IModGuiFactory;
import net.minecraftforge.fml.common.event.FMLEvent;
import net.minecraftforge.fml.common.event.FMLFingerprintViolationEvent;
import net.minecraftforge.fml.common.event.FMLInitializationEvent;
import net.minecraftforge.fml.common.event.FMLInterModComms;
import net.minecraftforge.fml.common.event.FMLPostInitializationEvent;
import net.minecraftforge.fml.common.event.FMLPreInitializationEvent;
import net.minecraftforge.fml.common.event.FMLServerAboutToStartEvent;
import net.minecraftforge.fml.common.event.FMLServerStartedEvent;
import net.minecraftforge.fml.common.event.FMLServerStartingEvent;
import net.minecraftforge.fml.common.event.FMLServerStoppedEvent;
import net.minecraftforge.fml.common.event.FMLServerStoppingEvent;
import net.minecraftforge.fml.common.event.FMLInterModComms.IMCEvent;
import net.minecraftforge.fml.common.network.NetworkCheckHandler;
import net.minecraftforge.fml.common.registry.GameRegistry;
/**
* This defines a Mod to FML.
* Any class found with this annotation applied will be loaded as a Mod. The instance that is loaded will
* represent the mod to other Mods in the system. It will be sent various subclasses of {@link FMLEvent}
* at pre-defined times during the loading of the game, based on where you have applied the {@link EventHandler}
* annotation.
*
* <p>This is a simple example of a Mod. It has the modId of "MyModId", the name of "My example mod", it is
* version 1.0, and depends on FML being loaded.
* <pre>{@code
* package mymod;
* // Declare that this is a mod with modId "MyModId", name "My example mod", version "1.0" and dependency on FML.
* {@literal @}Mod(modId="MyModId",name="My example mod",version="1.0",dependencies="required-after:FML")
* public class MyMod {
* // Populate this field with the instance of the mod created by FML
* {@literal @}Instance("MyModId")
* public MyMod instance;
*
* // Mark this method for receiving an {@link FMLEvent} (in this case, it's the {@link FMLPreInitializationEvent})
* {@literal @}EventHandler public void preInit(FMLPreInitializationEvent event)
* {
* // Do stuff in pre-init phase (read config, create blocks and items, register them)
* }
* }
* }
* </pre>
*
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Mod
{
/**
* The unique mod identifier for this mod
*/
String modid();
/**
* A user friendly name for the mod
*/
String name() default "";
/**
* A version string for this mod
*/
String version() default "";
/**
* A simple dependency string for this mod (see modloader's "priorities" string specification)
*/
String dependencies() default "";
/**
* Whether to use the mcmod.info metadata by default for this mod.
* If true, settings in the mcmod.info file will override settings in these annotations.
*/
boolean useMetadata() default false;
/**
* If true, this mod will not be loaded on the Dedicated Server environment.
* Will crash if both serverSideOnly and clientSideOnly are set to true.
*/
boolean clientSideOnly() default false;
/**
* If true, this mod will not be loaded on the Client environment.
* Will crash if both serverSideOnly and clientSideOnly are set to true.
*/
boolean serverSideOnly() default false;
/**
* The acceptable range of minecraft versions that this mod will load and run in
* The default ("empty string") indicates that the currently RUNNING minecraft version is acceptable.
* This means ANY version that the end user adds the mod to. Modders PLEASS set this.
* FML will refuse to run with an error if the minecraft version is not in this range across all mods.
* @return A version range as specified by the maven version range specification or the empty string
*/
String acceptedMinecraftVersions() default "";
/**
* A replacement for the no-longer-existing "versionRange" of NetworkMod. Specify a remote version range
* that this mod will accept as valid. Defaults to nothing, which is interpreted as "only this version".
* Another special value is '*' which means accept all versions.
*
* This is ignored if there is a {@link NetworkCheckHandler} annotation on a method in this class.
*
* @return A version range as specified by the maven version range specification or the empty string
*/
String acceptableRemoteVersions() default "";
/**
* A version range specifying compatible save version information. If your mod follows good version numbering
* practice <a href="http://semver.org/">Like this (http://semver.org/)</a> then this should be sufficient.
*
* Advanced users can specify a {@link SaveInspectionHandler} instead.
* @return A version range as specified by the maven version range specification or the empty string
*/
String acceptableSaveVersions() default "";
/**
* An optional bukkit plugin that will be injected into the bukkit plugin framework if
* this mod is loaded into the FML framework and the bukkit coremod is present.
* Instances of the bukkit plugin can be obtained via the {@link BukkitPluginRef} annotation on fields.
*
* This may be implemented by a bukkit integration. It is not provided with vanilla FML or MinecraftForge.
*
* @return The name of the plugin to load for this mod
*/
String bukkitPlugin() default "";
/**
* Specifying this field allows for a mod to expect a signed jar with a fingerprint matching this value.
* The fingerprint should be SHA-1 encoded, lowercase with ':' removed. An empty value indicates that
* the mod is not expecting to be signed.
*
* Any incorrectness of the fingerprint, be it missing or wrong, will result in the {@link FMLFingerprintViolationEvent}
* event firing <i>prior to any other event on the mod</i>.
*
* @return A certificate fingerprint that is expected for this mod.
*/
String certificateFingerprint() default "";
/**
* The language the mod is authored in. This will be used to control certain compatibility behaviours for this mod.
* Valid values are currently "java", "scala"
*
* @return The language the mod is authored in
*/
String modLanguage() default "java";
/**
* The language adapter to be used to load this mod. This overrides the value of modLanguage. The class must have a
* public zero variable constructor and implement {@link ILanguageAdapter} just like the Java and Scala adapters.
*
* A class with an invalid constructor or that doesn't implement {@link ILanguageAdapter} will throw an exception and
* halt loading.
*
* @return The full class name of the language adapter
*/
String modLanguageAdapter() default "";
/**
* NOT YET IMPLEMENTED. </br>
* An optional ASM hook class, that can be used to apply ASM to classes loaded from this mod. It is also given
* the ASM tree of the class declaring {@link Mod} to do with what it will.
*
* @return The name of a class to be loaded and executed. Must implement {@link IASMHook}.
*/
@Deprecated
String asmHookClass() default "";
/**
* If your mod doesn't have a runtime persistent effect on the state of the game, and can be disabled without side effects
* (minimap mods, graphical tweak mods) then you can set true here and receive the FMLDeactivationEvent to perform deactivation
* tasks.
* This does not affect administrative disabling through the system property fml.modStates or the config file fmlModState.properties.
* The mod will only be deactivated outside of a running game world - FML will never allow mod deactivation whilst a game server
* is running.
*
* @return if this mod can be deactivated whilst the game is open.
*/
boolean canBeDeactivated() default false;
/**
* An optional GUI factory for this mod. This is the name of a class implementing {@link IModGuiFactory} that will be instantiated
* on the client side, and will have certain configuration/options guis requested from it.
*
* @return The name of a class implementing {@link IModGuiFactory}
*/
String guiFactory() default "";
/**
* A list of custom properties for this mod. Completely up to the mod author if/when they
* want to put anything in here.
* @return an optional list of custom properties
*/
CustomProperty[] customProperties() default {};
/**
* A custom key => value property pair for use with {@link Mod#customProperties()}
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface CustomProperty
{
/**
* A key. Should be unique.
* @return A key
*/
String k();
/**
* A value. Can be anything.
* @return A value
*/
String v();
}
/**
* Marks the associated method as handling an FML lifecycle event.
* The method must have a single parameter, one of the following types. This annotation
* replaces the multiple different annotations that previously were used.
*
* Current event classes. This first section is standard lifecycle events. They are dispatched
* at various phases as the game starts. Each event should have information useful to that
* phase of the lifecycle. They are fired in this order.
*
* These suggestions are mostly just suggestions on what to do in each event.
* <ul>
* <li> {@link FMLPreInitializationEvent} : Run before anything else. Read your config, create blocks,
* items, etc, and register them with the {@link GameRegistry}.</li>
* <li> {@link FMLInitializationEvent} : Do your mod setup. Build whatever data structures you care about. Register recipes,
* send {@link FMLInterModComms} messages to other mods.</li>
* <li> {@link FMLPostInitializationEvent} : Handle interaction with other mods, complete your setup based on this.</li>
* </ul>
* <p>These are the server lifecycle events. They are fired whenever a server is running, or about to run. Each time a server
* starts they will be fired in this sequence.
* <ul>
* <li> {@link FMLServerAboutToStartEvent} : Use if you need to handle something before the server has even been created.</li>
* <li> {@link FMLServerStartingEvent} : Do stuff you need to do to set up the server. register commands, tweak the server.</li>
* <li> {@link FMLServerStartedEvent} : Do what you need to with the running server.</li>
* <li> {@link FMLServerStoppingEvent} : Do what you need to before the server has started it's shutdown sequence.</li>
* <li> {@link FMLServerStoppedEvent} : Do whatever cleanup you need once the server has shutdown. Generally only useful
* on the integrated server.</li>
* </ul>
* The second set of events are more specialized, for receiving notification of specific
* information.
* <ul>
* <li> {@link FMLFingerprintViolationEvent} : Sent just before {@link FMLPreInitializationEvent}
* if something is wrong with your mod signature</li>
* <li> {@link IMCEvent} : Sent just after {@link FMLInitializationEvent} if you have IMC messages waiting
* from other mods</li>
* </ul>
*
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface EventHandler{}
/**
* Populate the annotated field with the mod instance based on the specified ModId. This can be used
* to retrieve instances of other mods.
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Instance {
/**
* The mod object to inject into this field
*/
String value() default "";
}
/**
* Populate the annotated field with the mod's metadata.
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Metadata {
/**
* The mod id specifying the metadata to load here
*/
String value() default "";
}
/**
* Mod instance factory method. Should return an instance of the mod. Applies only to static methods on the same class as {@link Mod}.
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface InstanceFactory {
}
}
@@ -0,0 +1,232 @@
package net.minecraftforge.fml.common;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraftforge.fml.common.asm.transformers.ModAPITransformer;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.ModCandidate;
import net.minecraftforge.fml.common.discovery.ModDiscoverer;
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData;
import net.minecraftforge.fml.common.functions.ModIdFunction;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionParser;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
public class ModAPIManager {
public static final ModAPIManager INSTANCE = new ModAPIManager();
private ModAPITransformer transformer;
private ASMDataTable dataTable;
private Map<String,APIContainer> apiContainers;
private static class APIContainer extends DummyModContainer {
private List<ArtifactVersion> referredMods;
private ArtifactVersion ownerMod;
private ArtifactVersion ourVersion;
private String providedAPI;
private File source;
private String version;
private Set<String> currentReferents;
private Set<String> packages;
private boolean selfReferenced;
public APIContainer(String providedAPI, String apiVersion, File source, ArtifactVersion ownerMod)
{
this.providedAPI = providedAPI;
this.version = apiVersion;
this.ownerMod = ownerMod;
this.ourVersion = new DefaultArtifactVersion(providedAPI, apiVersion);
this.referredMods = Lists.newArrayList();
this.source = source;
this.currentReferents = Sets.newHashSet();
this.packages = Sets.newHashSet();
}
@Override
public File getSource()
{
return source;
}
@Override
public String getVersion()
{
return version;
}
@Override
public String getName()
{
return "API: "+providedAPI;
}
@Override
public String getModId()
{
return providedAPI;
}
@Override
public List<ArtifactVersion> getDependants()
{
return referredMods;
}
@Override
public List<ArtifactVersion> getDependencies()
{
return selfReferenced ? ImmutableList.<ArtifactVersion>of() : ImmutableList.of(ownerMod);
}
@Override
public ArtifactVersion getProcessedVersion()
{
return ourVersion;
}
public void validate(String providedAPI, String apiOwner, String apiVersion)
{
// TODO Compare this annotation data to the one we first found. Maybe barf if there is inconsistency?
}
@Override
public String toString()
{
return "APIContainer{"+providedAPI+":"+version+"}";
}
public void addAPIReference(String embedded)
{
if (currentReferents.add(embedded))
{
referredMods.add(VersionParser.parseVersionReference(embedded));
}
}
public void addOwnedPackage(String apiPackage)
{
packages.add(apiPackage);
}
public void addAPIReferences(List<String> candidateIds)
{
for (String modId : candidateIds)
{
addAPIReference(modId);
}
}
void markSelfReferenced()
{
selfReferenced = true;
}
}
public void registerDataTableAndParseAPI(ASMDataTable dataTable)
{
this.dataTable = dataTable;
Set<ASMData> apiList = dataTable.getAll("net.minecraftforge.fml.common.API");
apiContainers = Maps.newHashMap();
for (ASMData data : apiList)
{
Map<String, Object> annotationInfo = data.getAnnotationInfo();
String apiPackage = data.getClassName().substring(0,data.getClassName().indexOf(".package-info"));
String providedAPI = (String) annotationInfo.get("provides");
String apiOwner = (String) annotationInfo.get("owner");
String apiVersion = (String) annotationInfo.get("apiVersion");
APIContainer container = apiContainers.get(providedAPI);
if (container == null)
{
container = new APIContainer(providedAPI, apiVersion, data.getCandidate().getModContainer(), VersionParser.parseVersionReference(apiOwner));
apiContainers.put(providedAPI, container);
}
else
{
container.validate(providedAPI, apiOwner, apiVersion);
}
container.addOwnedPackage(apiPackage);
for (ModContainer mc : data.getCandidate().getContainedMods())
{
String embeddedIn = mc.getModId();
if (container.currentReferents.contains(embeddedIn))
{
continue;
}
FMLLog.fine("Found API %s (owned by %s providing %s) embedded in %s",apiPackage, apiOwner, providedAPI, embeddedIn);
if (!embeddedIn.equals(apiOwner))
{
container.addAPIReference(embeddedIn);
}
}
}
for (APIContainer container : apiContainers.values())
{
for (String pkg : container.packages)
{
Set<ModCandidate> candidates = dataTable.getCandidatesFor(pkg);
for (ModCandidate candidate : candidates)
{
List<String> candidateIds = Lists.transform(candidate.getContainedMods(), new ModIdFunction());
if (!candidateIds.contains(container.ownerMod.getLabel()) && !container.currentReferents.containsAll(candidateIds))
{
FMLLog.info("Found mod(s) %s containing declared API package %s (owned by %s) without associated API reference",candidateIds, pkg, container.ownerMod);
container.addAPIReferences(candidateIds);
}
}
}
if (apiContainers.containsKey(container.ownerMod.getLabel()))
{
ArtifactVersion owner = container.ownerMod;
do
{
APIContainer parent = apiContainers.get(owner.getLabel());
if (parent == container)
{
FMLLog.finer("APIContainer %s is it's own parent. skipping", owner);
container.markSelfReferenced();
break;
}
FMLLog.finer("Removing upstream parent %s from %s", parent.ownerMod.getLabel(), container);
container.currentReferents.remove(parent.ownerMod.getLabel());
container.referredMods.remove(parent.ownerMod);
owner = parent.ownerMod;
}
while (apiContainers.containsKey(owner.getLabel()));
}
FMLLog.fine("Creating API container dummy for API %s: owner: %s, dependents: %s", container.providedAPI, container.ownerMod, container.referredMods);
}
}
public void manageAPI(ModClassLoader modClassLoader, ModDiscoverer discoverer)
{
registerDataTableAndParseAPI(discoverer.getASMTable());
transformer = modClassLoader.addModAPITransformer(dataTable);
}
public void injectAPIModContainers(List<ModContainer> mods, Map<String, ModContainer> nameLookup)
{
mods.addAll(apiContainers.values());
nameLookup.putAll(apiContainers);
}
public void cleanupAPIContainers(List<ModContainer> mods)
{
mods.removeAll(apiContainers.values());
}
public boolean hasAPI(String modId)
{
return apiContainers.containsKey(modId);
}
public Iterable<? extends ModContainer> getAPIList()
{
return apiContainers.values();
}
}
@@ -0,0 +1,151 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.common.asm.transformers.ModAPITransformer;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import org.apache.logging.log4j.Level;
import com.google.common.collect.ImmutableList;
/**
* A simple delegating class loader used to load mods into the system
*
*
* @author cpw
*
*/
public class ModClassLoader extends URLClassLoader
{
private static final List<String> STANDARD_LIBRARIES = ImmutableList.of("jinput.jar", "lwjgl.jar", "lwjgl_util.jar", "rt.jar");
private LaunchClassLoader mainClassLoader;
public ModClassLoader(ClassLoader parent) {
super(new URL[0], null);
this.mainClassLoader = (LaunchClassLoader)parent;
}
public void addFile(File modFile) throws MalformedURLException
{
URL url = modFile.toURI().toURL();
mainClassLoader.addURL(url);
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException
{
return mainClassLoader.loadClass(name);
}
public File[] getParentSources() {
try
{
List<File> files=new ArrayList<File>();
for(URL url : mainClassLoader.getSources())
{
URI uri = url.toURI();
if(uri.getScheme().equals("file"))
{
files.add(new File(uri));
}
}
return files.toArray(new File[]{});
}
catch (URISyntaxException e)
{
FMLLog.log(Level.ERROR, e, "Unable to process our input to locate the minecraft code");
throw new LoaderException(e);
}
}
public List<String> getDefaultLibraries()
{
return STANDARD_LIBRARIES;
}
public boolean isDefaultLibrary(File file)
{
// Should really pull this from the json somehow, but we dont have that at runtime.
String name = file.getName();
if (!name.endsWith(".jar")) return false;
String[] prefixes =
{
"launchwrapper-",
"asm-all-",
"akka-actor_2.11-",
"config-",
"scala-",
"jopt-simple-",
"lzma-",
"realms-",
"httpclient-",
"httpcore-",
"vecmath-",
"trove4j-",
"icu4j-core-mojang-",
"codecjorbis-",
"codecwav-",
"libraryjavawound-",
"librarylwjglopenal-",
"soundsystem-",
"netty-all-",
"quava-",
"commons-lang3-",
"commons-compress-",
"commons-logging-",
"commons-io-",
"commons-codec-",
"jinput-",
"jutils-",
"gson-",
"authlib-",
"log4j-api-",
"log4j-core-",
"lwjgl-",
"lwjgl_util-",
"twitch-"
};
for (String s : prefixes)
{
if (name.startsWith(s)) return true;
}
return false;
}
public void clearNegativeCacheFor(Set<String> classList)
{
mainClassLoader.clearNegativeEntries(classList);
}
public ModAPITransformer addModAPITransformer(ASMDataTable dataTable)
{
mainClassLoader.registerTransformer("net.minecraftforge.fml.common.asm.transformers.ModAPITransformer");
List<IClassTransformer> transformers = mainClassLoader.getTransformers();
ModAPITransformer modAPI = (ModAPITransformer) transformers.get(transformers.size()-1);
modAPI.initTable(dataTable);
return modAPI;
}
}
@@ -0,0 +1,152 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.io.File;
import java.security.cert.Certificate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.VersionRange;
import com.google.common.collect.ImmutableMap;
import com.google.common.eventbus.EventBus;
/**
* The container that wraps around mods in the system.
* <p>
* The philosophy is that individual mod implementation technologies should not
* impact the actual loading and management of mod code. This interface provides
* a mechanism by which we can wrap actual mod code so that the loader and other
* facilities can treat mods at arms length.
* </p>
*
* @author cpw
*
*/
public interface ModContainer
{
public static enum Disableable {
YES, RESTART, NEVER, DEPENDENCIES;
}
/**
* The globally unique modid for this mod
*/
String getModId();
/**
* A human readable name
*/
String getName();
/**
* A human readable version identifier
*/
String getVersion();
/**
* The location on the file system which this mod came from
*/
File getSource();
/**
* The metadata for this mod
*/
ModMetadata getMetadata();
/**
* Attach this mod to it's metadata from the supplied metadata collection
*/
void bindMetadata(MetadataCollection mc);
/**
* Set the enabled/disabled state of this mod
*/
void setEnabledState(boolean enabled);
/**
* A list of the modids that this mod requires loaded prior to loading
*/
Set<ArtifactVersion> getRequirements();
/**
* A list of modids that should be loaded prior to this one. The special
* value <strong>*</strong> indicates to load <em>after</em> any other mod.
*/
List<ArtifactVersion> getDependencies();
/**
* A list of modids that should be loaded <em>after</em> this one. The
* special value <strong>*</strong> indicates to load <em>before</em> any
* other mod.
*/
List<ArtifactVersion> getDependants();
/**
* A representative string encapsulating the sorting preferences for this
* mod
*/
String getSortingRules();
/**
* Register the event bus for the mod and the controller for error handling
* Returns if this bus was successfully registered - disabled mods and other
* mods that don't need real events should return false and avoid further
* processing
*
* @param bus
* @param controller
*/
boolean registerBus(EventBus bus, LoadController controller);
/**
* Does this mod match the supplied mod
*
* @param mod
*/
boolean matches(Object mod);
/**
* Get the actual mod object
*/
Object getMod();
ArtifactVersion getProcessedVersion();
boolean isImmutable();
String getDisplayVersion();
VersionRange acceptableMinecraftVersionRange();
Certificate getSigningCertificate();
public static final Map<String,String> EMPTY_PROPERTIES = ImmutableMap.of();
Map<String,String> getCustomModProperties();
public Class<?> getCustomResourcePackClass();
Map<String, String> getSharedModDescriptor();
Disableable canBeDisabled();
String getGuiClassName();
List<String> getOwnedPackages();
boolean shouldLoadInEnvironment();
}
@@ -0,0 +1,95 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.io.File;
import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.regex.Pattern;
import net.minecraftforge.fml.common.discovery.ModCandidate;
import net.minecraftforge.fml.common.discovery.asm.ASMModParser;
import net.minecraftforge.fml.common.discovery.asm.ModAnnotation;
import org.apache.logging.log4j.Level;
import org.objectweb.asm.Type;
import com.google.common.base.Throwables;
import com.google.common.collect.Maps;
public class ModContainerFactory
{
public static Map<Type, Constructor<? extends ModContainer>> modTypes = Maps.newHashMap();
private static Pattern modClass = Pattern.compile(".*(\\.|)(mod\\_[^\\s$]+)$");
private static ModContainerFactory INSTANCE = new ModContainerFactory();
private ModContainerFactory() {
// We always know about Mod type
registerContainerType(Type.getType(Mod.class), FMLModContainer.class);
}
public static ModContainerFactory instance() {
return INSTANCE;
}
public void registerContainerType(Type type, Class<? extends ModContainer> container)
{
try {
Constructor<? extends ModContainer> constructor = container.getConstructor(new Class<?>[] { String.class, ModCandidate.class, Map.class });
modTypes.put(type, constructor);
} catch (Exception e) {
FMLLog.log(Level.ERROR, e, "Critical error : cannot register mod container type %s, it has an invalid constructor");
Throwables.propagate(e);
}
}
public ModContainer build(ASMModParser modParser, File modSource, ModCandidate container)
{
String className = modParser.getASMType().getClassName();
if (modParser.isBaseMod(container.getRememberedBaseMods()) && modClass.matcher(className).find())
{
FMLLog.severe("Found a BaseMod type mod %s", className);
FMLLog.severe("This will not be loaded and will be ignored. ModLoader mechanisms are no longer available.");
}
else if (modClass.matcher(className).find())
{
FMLLog.fine("Identified a class %s following modloader naming convention but not directly a BaseMod or currently seen subclass", className);
container.rememberModCandidateType(modParser);
}
else if (modParser.isBaseMod(container.getRememberedBaseMods()))
{
FMLLog.fine("Found a basemod %s of non-standard naming format", className);
container.rememberBaseModType(className);
}
for (ModAnnotation ann : modParser.getAnnotations())
{
if (modTypes.containsKey(ann.getASMType()))
{
FMLLog.fine("Identified a mod of type %s (%s) - loading", ann.getASMType(), className);
try {
ModContainer ret = modTypes.get(ann.getASMType()).newInstance(className, container, ann.getValues());
if (!ret.shouldLoadInEnvironment())
{
FMLLog.fine("Skipping mod %s, container opted to not load.", className);
return null;
}
return ret;
} catch (Exception e) {
FMLLog.log(Level.ERROR, e, "Unable to construct %s container", ann.getASMType().getClassName());
return null;
}
}
}
return null;
}
}
@@ -0,0 +1,82 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.util.List;
import java.util.Set;
import net.minecraftforge.fml.common.functions.ModNameFunction;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gson.annotations.SerializedName;
/**
* @author cpw
*
*/
public class ModMetadata
{
@SerializedName("modid")
public String modId;
public String name;
public String description = "";
public String url = "";
public String updateUrl = "";
public String logoFile = "";
public String version = "";
public List<String> authorList = Lists.newArrayList();
public String credits = "";
public String parent = "";
public String[] screenshots;
// this field is not for use in the json
public transient ModContainer parentMod;
// this field is not for use in the json
public transient List<ModContainer> childMods = Lists.newArrayList();
public boolean useDependencyInformation;
public Set<ArtifactVersion> requiredMods = Sets.newHashSet();
public List<ArtifactVersion> dependencies = Lists.newArrayList();
public List<ArtifactVersion> dependants = Lists.newArrayList();
// this field is not for use in the json
public transient boolean autogenerated;
public ModMetadata()
{
}
public String getChildModCountString()
{
return String.format("%d child mod%s", childMods.size(), childMods.size() != 1 ? "s" : "");
}
public String getAuthorList()
{
return Joiner.on(", ").join(authorList);
}
public String getChildModList()
{
return Joiner.on(", ").join(Lists.transform(childMods, new ModNameFunction()));
}
public String printableSortingRules()
{
return "";
}
}
@@ -0,0 +1,105 @@
/*
* The FML Forge Mod Loader suite. Copyright (C) 2012 cpw
*
* This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fml.common;
import java.util.Arrays;
import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import net.minecraftforge.fml.relauncher.ReflectionHelper;
import net.minecraftforge.fml.relauncher.ReflectionHelper.UnableToAccessFieldException;
import net.minecraftforge.fml.relauncher.ReflectionHelper.UnableToFindFieldException;
import org.apache.logging.log4j.Level;
/**
* Some reflection helper code.
*
* @author cpw
*
*/
public class ObfuscationReflectionHelper
{
public static <T, E> T getPrivateValue(Class<? super E> classToAccess, E instance, int fieldIndex)
{
try
{
return ReflectionHelper.getPrivateValue(classToAccess, instance, fieldIndex);
}
catch (UnableToAccessFieldException e)
{
FMLLog.log(Level.ERROR, e, "There was a problem getting field index %d from %s", fieldIndex, classToAccess.getName());
throw e;
}
}
public static String[] remapFieldNames(String className, String... fieldNames)
{
String internalClassName = FMLDeobfuscatingRemapper.INSTANCE.unmap(className.replace('.', '/'));
String[] mappedNames = new String[fieldNames.length];
int i = 0;
for (String fName : fieldNames)
{
mappedNames[i++] = FMLDeobfuscatingRemapper.INSTANCE.mapFieldName(internalClassName, fName, null);
}
return mappedNames;
}
public static <T, E> T getPrivateValue(Class<? super E> classToAccess, E instance, String... fieldNames)
{
try
{
return ReflectionHelper.getPrivateValue(classToAccess, instance, remapFieldNames(classToAccess.getName(),fieldNames));
}
catch (UnableToFindFieldException e)
{
FMLLog.log(Level.ERROR,e,"Unable to locate any field %s on type %s", Arrays.toString(fieldNames), classToAccess.getName());
throw e;
}
catch (UnableToAccessFieldException e)
{
FMLLog.log(Level.ERROR, e, "Unable to access any field %s on type %s", Arrays.toString(fieldNames), classToAccess.getName());
throw e;
}
}
public static <T, E> void setPrivateValue(Class<? super T> classToAccess, T instance, E value, int fieldIndex)
{
try
{
ReflectionHelper.setPrivateValue(classToAccess, instance, value, fieldIndex);
}
catch (UnableToAccessFieldException e)
{
FMLLog.log(Level.ERROR, e, "There was a problem setting field index %d on type %s", fieldIndex, classToAccess.getName());
throw e;
}
}
public static <T, E> void setPrivateValue(Class<? super T> classToAccess, T instance, E value, String... fieldNames)
{
try
{
ReflectionHelper.setPrivateValue(classToAccess, instance, value, remapFieldNames(classToAccess.getName(), fieldNames));
}
catch (UnableToFindFieldException e)
{
FMLLog.log(Level.ERROR, e, "Unable to locate any field %s on type %s", Arrays.toString(fieldNames), classToAccess.getName());
throw e;
}
catch (UnableToAccessFieldException e)
{
FMLLog.log(Level.ERROR, e, "Unable to set any field %s on type %s", Arrays.toString(fieldNames), classToAccess.getName());
throw e;
}
}
}
@@ -0,0 +1,76 @@
package net.minecraftforge.fml.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Classes annotated with this will have the named interface or method removed from the runtime definition of the class
* if the modid specified is missing.
*
* @author cpw
*
*/
public final class Optional {
/**
* Not constructable
*/
private Optional() {}
/**
* Mark a list of interfaces as removable
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface InterfaceList {
/**
* Mark a list of interfaces for optional removal.
* @return
*/
public Interface[] value();
}
/**
* Used to remove optional interfaces
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Interface {
/**
* The fully qualified name of the interface to be stripped
* @return the interface name
*/
public String iface();
/**
* The modid that is required to be present for stripping NOT to occur
* @return the modid
*/
public String modid();
/**
* Strip references to this interface in method declarations? (Useful to kill synthetic methods from scala f.e.)
*
* @return if references should be stripped
*/
public boolean striprefs() default false;
}
/**
* Used to remove optional methods
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Method {
/**
* The modid that is required to be present for stripping NOT to occur
* @return the modid
*/
public String modid();
}
}
@@ -0,0 +1,134 @@
package net.minecraftforge.fml.common;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import com.google.common.base.Joiner;
/**
* @deprecated not a stable API, will break, don't use this yet
*/
@Deprecated
public class ProgressManager
{
private static final List<ProgressBar> bars = new CopyOnWriteArrayList<ProgressBar>();
/**
* @deprecated not a stable API, will break, don't use this yet
*/
@Deprecated
public static ProgressBar push(String title, int steps)
{
return push(title, steps, false);
}
/**
* @deprecated not a stable API, will break, don't use this yet
*/
@Deprecated
public static ProgressBar push(String title, int steps, boolean timeEachStep)
{
ProgressBar bar = new ProgressBar(title, steps);
bars.add(bar);
if (timeEachStep)
{
bar.timeEachStep();
}
FMLCommonHandler.instance().processWindowMessages();
return bar;
}
/**
* @deprecated not a stable API, will break, don't use this yet
*/
@Deprecated
public static void pop(ProgressBar bar)
{
if(bar.getSteps() != bar.getStep()) throw new IllegalStateException("can't pop unfinished ProgressBar " + bar.getTitle());
bars.remove(bar);
if (bar.getSteps() != 0)
{
long newTime = System.nanoTime();
if (bar.timeEachStep)
FMLLog.fine("Bar Step: %s - %s took %.3fs", bar.getTitle(), bar.getMessage(), ((float)(newTime - bar.lastTime) / 1000000 / 1000));
if (bar.getSteps() == 1)
FMLLog.fine("Bar Finished: %s - %s took %.3fs", bar.getTitle(), bar.getMessage(), ((float)(newTime - bar.startTime) / 1000000 / 1000));
else
FMLLog.fine("Bar Finished: %s took %.3fs", bar.getTitle(), ((float)(newTime - bar.startTime) / 1000000 / 1000));
}
FMLCommonHandler.instance().processWindowMessages();
}
/*
* Internal use only.
*/
public static Iterator<ProgressBar> barIterator()
{
return bars.iterator();
}
/**
* @deprecated not a stable API, will break, don't use this yet
*/
@Deprecated
public static class ProgressBar
{
private final String title;
private final int steps;
private volatile int step = 0;
private volatile String message = "";
private boolean timeEachStep = false;
private long startTime = System.nanoTime();
private long lastTime = startTime;
private ProgressBar(String title, int steps)
{
this.title = title;
this.steps = steps;
}
public void step(Class<?> classToName, String... extra)
{
step(ClassNameUtils.shortName(classToName)+Joiner.on(' ').join(extra));
}
public void step(String message)
{
if(step >= steps) throw new IllegalStateException("too much steps for ProgressBar " + title);
if (timeEachStep && step != 0)
{
long newTime = System.nanoTime();
FMLLog.fine("Bar Step: %s - %s took %.3fs", getTitle(), getMessage(), ((float)(newTime - lastTime) / 1000000 / 1000));
lastTime = newTime;
}
step++;
this.message = FMLCommonHandler.instance().stripSpecialChars(message);
FMLCommonHandler.instance().processWindowMessages();
}
public String getTitle()
{
return title;
}
public int getSteps()
{
return steps;
}
public int getStep()
{
return step;
}
public String getMessage()
{
return message;
}
public void timeEachStep()
{
this.timeEachStep = true;
}
}
}
@@ -0,0 +1,84 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Set;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData;
import net.minecraftforge.fml.relauncher.Side;
import org.apache.logging.log4j.Level;
import com.google.common.base.Strings;
/**
* @author cpw
*
*/
public class ProxyInjector
{
public static void inject(ModContainer mod, ASMDataTable data, Side side, ILanguageAdapter languageAdapter)
{
FMLLog.fine("Attempting to inject @SidedProxy classes into %s", mod.getModId());
Set<ASMData> targets = data.getAnnotationsFor(mod).get(SidedProxy.class.getName());
ClassLoader mcl = Loader.instance().getModClassLoader();
for (ASMData targ : targets)
{
try
{
Class<?> proxyTarget = Class.forName(targ.getClassName(), true, mcl);
Field target = proxyTarget.getDeclaredField(targ.getObjectName());
if (target == null)
{
// Impossible?
FMLLog.severe("Attempted to load a proxy type into %s.%s but the field was not found", targ.getClassName(), targ.getObjectName());
throw new LoaderException(String.format("Attempted to load a proxy type into %s.%s but the field was not found", targ.getClassName(), targ.getObjectName()));
}
target.setAccessible(true);
SidedProxy annotation = target.getAnnotation(SidedProxy.class);
if (!Strings.isNullOrEmpty(annotation.modId()) && !annotation.modId().equals(mod.getModId()))
{
FMLLog.fine("Skipping proxy injection for %s.%s since it is not for mod %s", targ.getClassName(), targ.getObjectName(), mod.getModId());
continue;
}
String targetType = side.isClient() ? annotation.clientSide() : annotation.serverSide();
Object proxy=Class.forName(targetType, true, mcl).newInstance();
if (languageAdapter.supportsStatics() && (target.getModifiers() & Modifier.STATIC) == 0 )
{
FMLLog.severe("Attempted to load a proxy type %s into %s.%s, but the field is not static", targetType, targ.getClassName(), targ.getObjectName());
throw new LoaderException(String.format("Attempted to load a proxy type %s into %s.%s, but the field is not static", targetType, targ.getClassName(), targ.getObjectName()));
}
if (!target.getType().isAssignableFrom(proxy.getClass()))
{
FMLLog.severe("Attempted to load a proxy type %s into %s.%s, but the types don't match", targetType, targ.getClassName(), targ.getObjectName());
throw new LoaderException(String.format("Attempted to load a proxy type %s into %s.%s, but the types don't match", targetType, targ.getClassName(), targ.getObjectName()));
}
languageAdapter.setProxy(target, proxyTarget, proxy);
}
catch (Exception e)
{
FMLLog.log(Level.ERROR, e, "An error occured trying to load a proxy into %s.%s", targ.getAnnotationInfo(), targ.getClassName(), targ.getObjectName());
throw new LoaderException(e);
}
}
// Allow language specific proxy injection.
languageAdapter.setInternalProxies(mod, side, mcl);
}
}
@@ -0,0 +1,20 @@
package net.minecraftforge.fml.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* A method annotated with this on the {@link Mod} will be called whenever a local save is listed in
* the save games list.
*
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface SaveInspectionHandler
{
}
@@ -0,0 +1,68 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Sided proxies are loaded based on the specific environment they find themselves loaded into.
* They are used to ensure that client-specific code (such as GUIs) is only loaded into the game
* on the client side.
* It is applied to static fields of a class, anywhere in your mod code. FML will scan
* and load any classes with this annotation at mod construction time.
*
* <p>
* This example will load a CommonProxy on the server side, and a ClientProxy on the client side.
*
* <pre>{@code
* public class MySidedProxyHolder {
* {@literal @}SidedProxy(modId="MyModId",clientSide="mymod.ClientProxy", serverSide="mymod.CommonProxy")
* public static CommonProxy proxy;
* }
*
* public class CommonProxy {
* // Common or server stuff here that needs to be overridden on the client
* }
*
* public class ClientProxy extends CommonProxy {
* // Override common stuff with client specific stuff here
* }
* }
* </pre>
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SidedProxy
{
/**
* The name of the client side class to load and populate
*/
String clientSide() default "";
/**
* The name of the server side class to load and populate
*/
String serverSide() default "";
/**
* The (optional) name of a mod to load this proxy for. This will help ensure correct behaviour when loading a combined
* scala/java mod package. It is almost never going to be required, unless you ship both Scala and Java {@link Mod} content
* in a single jar.
*/
String modId() default "";
}
@@ -0,0 +1,160 @@
package net.minecraftforge.fml.common;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import net.minecraft.server.MinecraftServer;
public class StartupQuery {
// internal class/functionality, do not use
public static boolean confirm(String text)
{
StartupQuery query = new StartupQuery(text, new AtomicBoolean());
query.execute();
return query.getResult();
}
public static void notify(String text)
{
StartupQuery query = new StartupQuery(text, null);
query.execute();
}
public static void abort()
{
MinecraftServer server = FMLCommonHandler.instance().getMinecraftServerInstance();
if (server != null) server.initiateShutdown();
aborted = true; // to abort loading and go back to the main menu
throw new AbortedException(); // to halt the server
}
public static void reset()
{
pending = null;
aborted = false;
}
public static boolean check()
{
if (pending != null)
{
try
{
FMLCommonHandler.instance().queryUser(pending);
}
catch (InterruptedException e)
{
FMLLog.warning("query interrupted");
abort();
}
pending = null;
}
return !aborted;
}
private static volatile StartupQuery pending;
private static volatile boolean aborted = false;
private StartupQuery(String text, AtomicBoolean result)
{
this.text = text;
this.result = result;
}
public Boolean getResult()
{
return result == null ? null : result.get();
}
public void setResult(boolean result)
{
this.result.set(result);
}
public String getText()
{
return text;
}
public boolean isSynchronous()
{
return synchronous;
}
public void finish()
{
signal.countDown();
}
private void execute()
{
String prop = System.getProperty("fml.queryResult");
if (result != null && prop != null)
{
FMLLog.info("Using fml.queryResult %s to answer the following query:\n%s", prop, text);
if (prop.equalsIgnoreCase("confirm"))
{
setResult(true);
return;
}
else if (prop.equalsIgnoreCase("cancel"))
{
setResult(false);
return;
}
FMLLog.warning("Invalid value for fml.queryResult: %s, expected confirm or cancel", prop);
}
synchronous = false;
pending = this; // let the other thread start the query
// from the integrated server thread: the client will eventually check pending and execute the query
// from the client thread: synchronous execution
// dedicated server: command handling in mc is synchronous, execute the server-side query directly
if (FMLCommonHandler.instance().getSide().isServer() ||
FMLCommonHandler.instance().getEffectiveSide().isClient())
{
synchronous = true;
check();
}
try
{
signal.await();
reset();
}
catch (InterruptedException e)
{
FMLLog.warning("query interrupted");
abort();
}
}
private String text;
private AtomicBoolean result;
private CountDownLatch signal = new CountDownLatch(1);
private volatile boolean synchronous;
/**
* Exception not being caught by the crash report generation logic.
*/
public static class AbortedException extends RuntimeException
{
private static final long serialVersionUID = -5933665223696833921L;
private AbortedException()
{
super();
}
}
}
@@ -0,0 +1,50 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
package net.minecraftforge.fml.common;
import java.io.PrintStream;
import org.apache.logging.log4j.Logger;
/**
* PrintStream which redirects it's output to a given logger.
*
* @author Arkan
*/
public class TracingPrintStream extends PrintStream {
private Logger logger;
private int BASE_DEPTH = 3;
public TracingPrintStream(Logger logger, PrintStream original) {
super(original);
this.logger = logger;
}
@Override
public void println(Object o) {
logger.info(getPrefix() + o);
}
@Override
public void println(String s) {
logger.info(getPrefix() + s);
}
private String getPrefix() {
StackTraceElement[] elems = Thread.currentThread().getStackTrace();
StackTraceElement elem = elems[BASE_DEPTH]; // The caller is always at BASE_DEPTH, including this call.
if (elem.getClassName().startsWith("kotlin.io.")) {
elem = elems[BASE_DEPTH + 2]; // Kotlins IoPackage masks origins 2 deeper in the stack.
}
return "[" + elem.getClassName() + ":" + elem.getMethodName() + ":" + elem.getLineNumber() + "]: ";
}
}
@@ -0,0 +1,25 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
import java.util.Map;
import net.minecraft.nbt.*;
import net.minecraft.world.storage.*;
public interface WorldAccessContainer
{
public NBTTagCompound getDataForWriting(SaveHandler handler, WorldInfo info);
public void readData(SaveHandler handler, WorldInfo info, Map<String,NBTBase> propertyMap, NBTTagCompound tag);
}
@@ -0,0 +1,26 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common;
public class WrongMinecraftVersionException extends RuntimeException
{
private static final long serialVersionUID = 1L;
public ModContainer mod;
public WrongMinecraftVersionException(ModContainer mod)
{
super(String.format("Wrong Minecraft version for %s", mod.getModId()));
this.mod = mod;
}
}
@@ -0,0 +1,92 @@
package net.minecraftforge.fml.common;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.Deque;
import java.util.LinkedList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.logging.log4j.Level;
import com.google.common.io.Files;
/**
* Copied from http://stackoverflow.com/questions/1399126/java-util-zip-recreating-directory-structure
* because the code looked very tidy and neat. Thanks, McDowell!
*
* @author McDowell
*
*/
public class ZipperUtil {
public static void zip(File directory, File zipfile) throws IOException
{
URI base = directory.toURI();
Deque<File> queue = new LinkedList<File>();
queue.push(directory);
OutputStream out = new FileOutputStream(zipfile);
Closeable res = null;
try
{
ZipOutputStream zout = new ZipOutputStream(out);
res = zout;
while (!queue.isEmpty())
{
directory = queue.pop();
for (File kid : directory.listFiles())
{
String name = base.relativize(kid.toURI()).getPath();
if (kid.isDirectory())
{
queue.push(kid);
name = name.endsWith("/") ? name : name + "/";
zout.putNextEntry(new ZipEntry(name));
} else
{
zout.putNextEntry(new ZipEntry(name));
Files.copy(kid, zout);
zout.closeEntry();
}
}
}
} finally
{
res.close();
}
}
public static void backupWorld() throws IOException
{
String dirName = FMLCommonHandler.instance().getMinecraftServerInstance().getFolderName();
backupWorld(dirName);
}
@Deprecated
public static void backupWorld(String dirName, String saveName) throws IOException
{
backupWorld(dirName);
}
public static void backupWorld(String dirName) throws IOException
{
File dstFolder = FMLCommonHandler.instance().getSavesDirectory();
File zip = new File(dstFolder, String.format("%s-%2$tY%2$tm%2$td-%2$tH%2$tM%2$tS.zip", dirName, System.currentTimeMillis()));
try
{
ZipperUtil.zip(new File(dstFolder, dirName), zip);
}
catch (IOException e)
{
FMLLog.log(Level.WARN, e, "World backup failed.");
throw e;
}
FMLLog.info("World backup created at %s.", zip.getCanonicalPath());
}
}
@@ -0,0 +1,256 @@
package net.minecraftforge.fml.common.asm;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.security.Permission;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.LaunchClassLoader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Maps;
public class ASMTransformerWrapper
{
private static final Map<String, String> wrapperModMap = Maps.newHashMap();
private static final Map<String, String> wrapperParentMap = Maps.newHashMap();
private static final LoadingCache<String, byte[]> wrapperCache = CacheBuilder.newBuilder()
.maximumSize(30)
.weakValues()
.build(new CacheLoader<String, byte[]>()
{
public byte[] load(String file) throws Exception
{
return makeWrapper(file);
}
});
private static final URL asmGenRoot;
private static boolean injected = false;
static
{
try
{
asmGenRoot = new URL("asmgen", null, -1, "/", new ASMGenHandler());
}
catch(MalformedURLException e)
{
throw new RuntimeException(e);
}
}
private static class ASMGenHandler extends URLStreamHandler
{
protected URLConnection openConnection(URL url) throws IOException
{
String file = url.getFile();
if(file.equals("/"))
{
return new URLConnection(url)
{
public void connect() throws IOException
{
throw new UnsupportedOperationException();
}
};
}
if(!file.startsWith("/")) throw new RuntimeException("Malformed URL: " + url);
file = file.substring(1);
if(wrapperModMap.containsKey(file))
{
return new ASMGenConnection(url, file);
}
return null;
}
}
private static class ASMGenConnection extends URLConnection
{
private final String file;
protected ASMGenConnection(URL url, String file)
{
super(url);
this.file = file;
}
public void connect() throws IOException
{
throw new UnsupportedOperationException();
}
@Override
public InputStream getInputStream()
{
return new ByteArrayInputStream(wrapperCache.getUnchecked(file));
}
@Override
public Permission getPermission()
{
return null;
}
}
public static String getTransformerWrapper(LaunchClassLoader launchLoader, String parentClass, String coreMod)
{
if(!injected)
{
injected = true;
launchLoader.addURL(asmGenRoot);
}
String name = getWrapperName(parentClass);
String fileName = name.replace('.', '/') + ".class";
wrapperModMap.put(fileName, coreMod);
wrapperParentMap.put(fileName, parentClass);
return name;
}
private static byte[] makeWrapper(String fileName)
{
if(!wrapperModMap.containsKey(fileName) || !wrapperParentMap.containsKey(fileName) || !fileName.endsWith(".class"))
{
throw new IllegalArgumentException("makeWrapper called with strange argument: " + fileName);
}
String name = fileName.substring(0, fileName.length() - ".class".length());
try
{
Type wrapper = Type.getType(TransformerWrapper.class);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
writer.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, name, null, wrapper.getInternalName(), null);
Method m = Method.getMethod("void <init> ()");
GeneratorAdapter mg = new GeneratorAdapter(Opcodes.ACC_PUBLIC, m, null, null, writer);
mg.loadThis();
mg.invokeConstructor(wrapper, m);
mg.returnValue();
mg.endMethod();
m = Method.getMethod("java.lang.String getParentClass ()");
mg = new GeneratorAdapter(Opcodes.ACC_PROTECTED, m, null, null, writer);
mg.push(wrapperParentMap.get(fileName));
mg.returnValue();
mg.endMethod();
m = Method.getMethod("java.lang.String getCoreMod ()");
mg = new GeneratorAdapter(Opcodes.ACC_PROTECTED, m, null, null, writer);
mg.push(wrapperModMap.get(fileName));
mg.returnValue();
mg.endMethod();
writer.visitEnd();
return writer.toByteArray();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
private static String getWrapperName(String parentClass)
{
return "$wrapper." + parentClass;
}
private static class WrapperVisitor extends ClassVisitor
{
private final String name;
private final String parentClass;
public WrapperVisitor(ClassVisitor cv, String name, String parentClass)
{
super(Opcodes.ASM5, cv);
this.name = name.replace('.', '/');
this.parentClass = parentClass;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
super.visit(version, access, this.name, signature, superName, interfaces);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value)
{
if(name.equals("parentClass"))
{
return super.visitField(access, name, desc, signature, parentClass);
}
return super.visitField(access, name, desc, signature, value);
}
}
public static abstract class TransformerWrapper implements IClassTransformer
{
private final IClassTransformer parent;
public TransformerWrapper()
{
try
{
this.parent = (IClassTransformer)this.getClass().getClassLoader().loadClass(getParentClass()).newInstance();
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
public byte[] transform(String name, String transformedName, byte[] basicClass)
{
try
{
return parent.transform(name, transformedName, basicClass);
}
catch(Throwable e)
{
throw new TransformerException("Exception in class transformer " + parent + " from coremod " + getCoreMod(), e);
}
}
@Override
public String toString()
{
return "TransformerWrapper(" + getParentClass() + ", " + getCoreMod() + ")";
}
protected abstract String getParentClass();
protected abstract String getCoreMod();
}
static class TransformerException extends RuntimeException
{
public TransformerException(String message, Throwable cause)
{
super(message, cause);
}
}
}
@@ -0,0 +1,185 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common.asm;
import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.security.CodeSource;
import java.security.cert.Certificate;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.logging.log4j.Level;
import net.minecraft.launchwrapper.LaunchClassLoader;
import net.minecraftforge.fml.common.CertificateHelper;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import net.minecraftforge.fml.common.patcher.ClassPatchManager;
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
import net.minecraftforge.fml.relauncher.FMLRelaunchLog;
import net.minecraftforge.fml.relauncher.IFMLCallHook;
import net.minecraftforge.fml.relauncher.Side;
import com.google.common.base.Charsets;
import com.google.common.io.ByteStreams;
public class FMLSanityChecker implements IFMLCallHook
{
private static final String FMLFINGERPRINT = "51:0A:FB:4C:AF:A4:A0:F2:F5:CF:C5:0E:B4:CC:3C:30:24:4A:E3:8E".toLowerCase().replace(":", "");
private static final String FORGEFINGERPRINT = "E3:C3:D5:0C:7C:98:6D:F7:4C:64:5C:0A:C5:46:39:74:1C:90:A5:57".toLowerCase().replace(":", "");
private static final String MCFINGERPRINT = "CD:99:95:96:56:F7:53:DC:28:D8:63:B4:67:69:F7:F8:FB:AE:FC:FC".toLowerCase().replace(":", "");
private LaunchClassLoader cl;
private boolean liveEnv;
public static File fmlLocation;
@Override
public Void call() throws Exception
{
CodeSource codeSource = getClass().getProtectionDomain().getCodeSource();
boolean goodFML = false;
boolean fmlIsJar = false;
if (codeSource.getLocation().getProtocol().equals("jar"))
{
fmlIsJar = true;
Certificate[] certificates = codeSource.getCertificates();
if (certificates!=null)
{
for (Certificate cert : certificates)
{
String fingerprint = CertificateHelper.getFingerprint(cert);
if (fingerprint.equals(FMLFINGERPRINT))
{
FMLRelaunchLog.info("Found valid fingerprint for FML. Certificate fingerprint %s", fingerprint);
goodFML = true;
}
else if (fingerprint.equals(FORGEFINGERPRINT))
{
FMLRelaunchLog.info("Found valid fingerprint for Minecraft Forge. Certificate fingerprint %s", fingerprint);
goodFML = true;
}
else
{
FMLRelaunchLog.severe("Found invalid fingerprint for FML: %s", fingerprint);
}
}
}
}
else
{
goodFML = true;
}
// Server is not signed, so assume it's good - a deobf env is dev time so it's good too
boolean goodMC = FMLLaunchHandler.side() == Side.SERVER || !liveEnv;
int certCount = 0;
try
{
Class<?> cbr = Class.forName("net.minecraft.client.ClientBrandRetriever",false, cl);
codeSource = cbr.getProtectionDomain().getCodeSource();
}
catch (Exception e)
{
// Probably a development environment, or the server (the server is not signed)
goodMC = true;
}
JarFile mcJarFile = null;
if (fmlIsJar && !goodMC && codeSource.getLocation().getProtocol().equals("jar"))
{
try
{
String mcPath = codeSource.getLocation().getPath().substring(5);
mcPath = mcPath.substring(0, mcPath.lastIndexOf('!'));
mcPath = URLDecoder.decode(mcPath, Charsets.UTF_8.name());
mcJarFile = new JarFile(mcPath,true);
mcJarFile.getManifest();
JarEntry cbrEntry = mcJarFile.getJarEntry("net/minecraft/client/ClientBrandRetriever.class");
ByteStreams.toByteArray(mcJarFile.getInputStream(cbrEntry));
Certificate[] certificates = cbrEntry.getCertificates();
certCount = certificates != null ? certificates.length : 0;
if (certificates!=null)
{
for (Certificate cert : certificates)
{
String fingerprint = CertificateHelper.getFingerprint(cert);
if (fingerprint.equals(MCFINGERPRINT))
{
FMLRelaunchLog.info("Found valid fingerprint for Minecraft. Certificate fingerprint %s", fingerprint);
goodMC = true;
}
}
}
}
catch (Throwable e)
{
FMLRelaunchLog.log(Level.ERROR, e, "A critical error occurred trying to read the minecraft jar file");
}
finally
{
if (mcJarFile != null)
{
try
{
mcJarFile.close();
}
catch (IOException ioe)
{
// Noise
}
}
}
}
else
{
goodMC = true;
}
if (!goodMC)
{
FMLRelaunchLog.severe("The minecraft jar %s appears to be corrupt! There has been CRITICAL TAMPERING WITH MINECRAFT, it is highly unlikely minecraft will work! STOP NOW, get a clean copy and try again!",codeSource.getLocation().getFile());
if (!Boolean.parseBoolean(System.getProperty("fml.ignoreInvalidMinecraftCertificates","false")))
{
FMLRelaunchLog.severe("For your safety, FML will not launch minecraft. You will need to fetch a clean version of the minecraft jar file");
FMLRelaunchLog.severe("Technical information: The class net.minecraft.client.ClientBrandRetriever should have been associated with the minecraft jar file, " +
"and should have returned us a valid, intact minecraft jar location. This did not work. Either you have modified the minecraft jar file (if so " +
"run the forge installer again), or you are using a base editing jar that is changing this class (and likely others too). If you REALLY " +
"want to run minecraft in this configuration, add the flag -Dfml.ignoreInvalidMinecraftCertificates=true to the 'JVM settings' in your launcher profile.");
FMLCommonHandler.instance().exitJava(1, false);
}
else
{
FMLRelaunchLog.severe("FML has been ordered to ignore the invalid or missing minecraft certificate. This is very likely to cause a problem!");
FMLRelaunchLog.severe("Technical information: ClientBrandRetriever was at %s, there were %d certificates for it", codeSource.getLocation(), certCount);
}
}
if (!goodFML)
{
FMLRelaunchLog.severe("FML appears to be missing any signature data. This is not a good thing");
}
return null;
}
@Override
public void injectData(Map<String, Object> data)
{
liveEnv = (Boolean)data.get("runtimeDeobfuscationEnabled");
cl = (LaunchClassLoader) data.get("classLoader");
File mcDir = (File)data.get("mcLocation");
fmlLocation = (File)data.get("coremodLocation");
ClassPatchManager.INSTANCE.setup(FMLLaunchHandler.side());
FMLDeobfuscatingRemapper.INSTANCE.setup(mcDir, cl, (String) data.get("deobfuscationFileName"));
}
}
@@ -0,0 +1,29 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common.asm;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Used to force certain classes to reobfuscate
* @author cpw
*
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ReobfuscationMarker {
}
@@ -0,0 +1,496 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common.asm.transformers;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraftforge.fml.relauncher.FMLRelaunchLog;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import com.google.common.base.Charsets;
import com.google.common.base.Splitter;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.io.CharSource;
import com.google.common.io.LineProcessor;
import com.google.common.io.Resources;
public class AccessTransformer implements IClassTransformer
{
private static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("fml.debugAccessTransformer", "false"));
class Modifier
{
public String name = "";
public String desc = "";
public int oldAccess = 0;
public int newAccess = 0;
public int targetAccess = 0;
public boolean changeFinal = false;
public boolean markFinal = false;
protected boolean modifyClassVisibility;
private void setTargetAccess(String name)
{
if (name.startsWith("public")) targetAccess = ACC_PUBLIC;
else if (name.startsWith("private")) targetAccess = ACC_PRIVATE;
else if (name.startsWith("protected")) targetAccess = ACC_PROTECTED;
if (name.endsWith("-f"))
{
changeFinal = true;
markFinal = false;
}
else if (name.endsWith("+f"))
{
changeFinal = true;
markFinal = true;
}
}
}
private Multimap<String, Modifier> modifiers = ArrayListMultimap.create();
public AccessTransformer() throws IOException
{
this("forge_at.cfg");
}
protected AccessTransformer(String rulesFile) throws IOException
{
readMapFile(rulesFile);
}
AccessTransformer(Class<? extends AccessTransformer> dummyClazz)
{
// This is a noop
}
void readMapFile(String rulesFile) throws IOException
{
File file = new File(rulesFile);
URL rulesResource;
if (file.exists())
{
rulesResource = file.toURI().toURL();
}
else
{
rulesResource = Resources.getResource(rulesFile);
}
processATFile(Resources.asCharSource(rulesResource, Charsets.UTF_8));
FMLRelaunchLog.fine("Loaded %d rules from AccessTransformer config file %s", modifiers.size(), rulesFile);
}
protected void processATFile(CharSource rulesResource) throws IOException
{
rulesResource.readLines(new LineProcessor<Void>()
{
@Override
public Void getResult()
{
return null;
}
@Override
public boolean processLine(String input) throws IOException
{
String line = Iterables.getFirst(Splitter.on('#').limit(2).split(input), "").trim();
if (line.length()==0)
{
return true;
}
List<String> parts = Lists.newArrayList(Splitter.on(" ").trimResults().split(line));
if (parts.size()>3)
{
throw new RuntimeException("Invalid config file line "+ input);
}
Modifier m = new Modifier();
m.setTargetAccess(parts.get(0));
if (parts.size() == 2)
{
m.modifyClassVisibility = true;
}
else
{
String nameReference = parts.get(2);
int parenIdx = nameReference.indexOf('(');
if (parenIdx>0)
{
m.desc = nameReference.substring(parenIdx);
m.name = nameReference.substring(0,parenIdx);
}
else
{
m.name = nameReference;
}
}
String className = parts.get(1).replace('/', '.');
modifiers.put(className, m);
if (DEBUG) System.out.printf("AT RULE: %s %s %s (type %s)\n", toBinary(m.targetAccess), m.name, m.desc, className);
return true;
}
});
}
@Override
public byte[] transform(String name, String transformedName, byte[] bytes)
{
if (bytes == null) { return null; }
if (DEBUG)
{
FMLRelaunchLog.fine("Considering all methods and fields on %s (%s)\n", transformedName, name);
}
if (!modifiers.containsKey(transformedName)) { return bytes; }
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(bytes);
classReader.accept(classNode, 0);
Collection<Modifier> mods = modifiers.get(transformedName);
for (Modifier m : mods)
{
if (m.modifyClassVisibility)
{
classNode.access = getFixedAccess(classNode.access, m);
if (DEBUG)
{
System.out.println(String.format("Class: %s %s -> %s", name, toBinary(m.oldAccess), toBinary(m.newAccess)));
}
continue;
}
if (m.desc.isEmpty())
{
for (FieldNode n : classNode.fields)
{
if (n.name.equals(m.name) || m.name.equals("*"))
{
n.access = getFixedAccess(n.access, m);
if (DEBUG)
{
System.out.println(String.format("Field: %s.%s %s -> %s", name, n.name, toBinary(m.oldAccess), toBinary(m.newAccess)));
}
if (!m.name.equals("*"))
{
break;
}
}
}
}
else
{
List<MethodNode> nowOverridable = Lists.newArrayList();
for (MethodNode n : classNode.methods)
{
if ((n.name.equals(m.name) && n.desc.equals(m.desc)) || m.name.equals("*"))
{
n.access = getFixedAccess(n.access, m);
// constructors always use INVOKESPECIAL
if (!n.name.equals("<init>"))
{
// if we changed from private to something else we need to replace all INVOKESPECIAL calls to this method with INVOKEVIRTUAL
// so that overridden methods will be called. Only need to scan this class, because obviously the method was private.
boolean wasPrivate = (m.oldAccess & ACC_PRIVATE) == ACC_PRIVATE;
boolean isNowPrivate = (m.newAccess & ACC_PRIVATE) == ACC_PRIVATE;
if (wasPrivate && !isNowPrivate)
{
nowOverridable.add(n);
}
}
if (DEBUG)
{
System.out.println(String.format("Method: %s.%s%s %s -> %s", name, n.name, n.desc, toBinary(m.oldAccess), toBinary(m.newAccess)));
}
if (!m.name.equals("*"))
{
break;
}
}
}
replaceInvokeSpecial(classNode, nowOverridable);
}
}
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classNode.accept(writer);
return writer.toByteArray();
}
private void replaceInvokeSpecial(ClassNode clazz, List<MethodNode> toReplace)
{
for (MethodNode method : clazz.methods)
{
for (Iterator<AbstractInsnNode> it = method.instructions.iterator(); it.hasNext();)
{
AbstractInsnNode insn = it.next();
if (insn.getOpcode() == INVOKESPECIAL)
{
MethodInsnNode mInsn = (MethodInsnNode) insn;
for (MethodNode n : toReplace)
{
if (n.name.equals(mInsn.name) && n.desc.equals(mInsn.desc))
{
mInsn.setOpcode(INVOKEVIRTUAL);
break;
}
}
}
}
}
}
private String toBinary(int num)
{
return String.format("%16s", Integer.toBinaryString(num)).replace(' ', '0');
}
private int getFixedAccess(int access, Modifier target)
{
target.oldAccess = access;
int t = target.targetAccess;
int ret = (access & ~7);
switch (access & 7)
{
case ACC_PRIVATE:
ret |= t;
break;
case 0: // default
ret |= (t != ACC_PRIVATE ? t : 0 /* default */);
break;
case ACC_PROTECTED:
ret |= (t != ACC_PRIVATE && t != 0 /* default */? t : ACC_PROTECTED);
break;
case ACC_PUBLIC:
ret |= (t != ACC_PRIVATE && t != 0 /* default */&& t != ACC_PROTECTED ? t : ACC_PUBLIC);
break;
default:
throw new RuntimeException("The fuck?");
}
// Clear the "final" marker on fields only if specified in control field
if (target.changeFinal)
{
if (target.markFinal)
{
ret |= ACC_FINAL;
}
else
{
ret &= ~ACC_FINAL;
}
}
target.newAccess = ret;
return ret;
}
public static void main(String[] args)
{
if (args.length < 2)
{
System.out.println("Usage: AccessTransformer <JarPath> <MapFile> [MapFile2]... ");
System.exit(1);
}
boolean hasTransformer = false;
AccessTransformer[] trans = new AccessTransformer[args.length - 1];
for (int x = 1; x < args.length; x++)
{
try
{
trans[x - 1] = new AccessTransformer(args[x]);
hasTransformer = true;
}
catch (IOException e)
{
System.out.println("Could not read Transformer Map: " + args[x]);
e.printStackTrace();
}
}
if (!hasTransformer)
{
System.out.println("Culd not find a valid transformer to perform");
System.exit(1);
}
File orig = new File(args[0]);
File temp = new File(args[0] + ".ATBack");
if (!orig.exists() && !temp.exists())
{
System.out.println("Could not find target jar: " + orig);
System.exit(1);
}
if (!orig.renameTo(temp))
{
System.out.println("Could not rename file: " + orig + " -> " + temp);
System.exit(1);
}
try
{
processJar(temp, orig, trans);
}
catch (IOException e)
{
e.printStackTrace();
System.exit(1);
}
if (!temp.delete())
{
System.out.println("Could not delete temp file: " + temp);
}
}
private static void processJar(File inFile, File outFile, AccessTransformer[] transformers) throws IOException
{
ZipInputStream inJar = null;
ZipOutputStream outJar = null;
try
{
try
{
inJar = new ZipInputStream(new BufferedInputStream(new FileInputStream(inFile)));
}
catch (FileNotFoundException e)
{
throw new FileNotFoundException("Could not open input file: " + e.getMessage());
}
try
{
outJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFile)));
}
catch (FileNotFoundException e)
{
throw new FileNotFoundException("Could not open output file: " + e.getMessage());
}
ZipEntry entry;
while ((entry = inJar.getNextEntry()) != null)
{
if (entry.isDirectory())
{
outJar.putNextEntry(entry);
continue;
}
byte[] data = new byte[4096];
ByteArrayOutputStream entryBuffer = new ByteArrayOutputStream();
int len;
do
{
len = inJar.read(data);
if (len > 0)
{
entryBuffer.write(data, 0, len);
}
}
while (len != -1);
byte[] entryData = entryBuffer.toByteArray();
String entryName = entry.getName();
if (entryName.endsWith(".class") && !entryName.startsWith("."))
{
ClassNode cls = new ClassNode();
ClassReader rdr = new ClassReader(entryData);
rdr.accept(cls, 0);
String name = cls.name.replace('/', '.').replace('\\', '.');
for (AccessTransformer trans : transformers)
{
entryData = trans.transform(name, name, entryData);
}
}
ZipEntry newEntry = new ZipEntry(entryName);
outJar.putNextEntry(newEntry);
outJar.write(entryData);
}
}
finally
{
if (outJar != null)
{
try
{
outJar.close();
}
catch (IOException e)
{
}
}
if (inJar != null)
{
try
{
inJar.close();
}
catch (IOException e)
{
}
}
}
}
Multimap<String, Modifier> getModifiers()
{
return modifiers;
}
boolean isEmpty()
{
return modifiers.isEmpty();
}
}
@@ -0,0 +1,119 @@
package net.minecraftforge.fml.common.asm.transformers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraftforge.fml.common.FMLLog;
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import com.google.common.collect.ImmutableSet;
public class BlamingTransformer implements IClassTransformer
{
private static final Map<String, String> classMap = new HashMap<String, String>();
private static final Set<String> naughtyMods = new HashSet<String>();
private static final Set<String> naughtyClasses = new TreeSet<String>();
private static final Set<String> orphanNaughtyClasses = new HashSet();
@Override
public byte[] transform(String name, String transformedName, byte[] bytes)
{
if (bytes == null) { return null; }
ClassReader classReader = new ClassReader(bytes);
VersionVisitor visitor = new VersionVisitor();
classReader.accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
return bytes;
}
public static void blame(String modId, String cls)
{
naughtyClasses.add(cls);
naughtyMods.add(modId);
FMLLog.severe("Unsupported class format in mod %s: class %s", modId, cls);
}
public static class VersionVisitor extends ClassVisitor
{
public VersionVisitor()
{
super(Opcodes.ASM5);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
if( (version == Opcodes.V1_8 && !SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_8)) ||
(version == Opcodes.V1_7 && !SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_7)) )
{
if(classMap.containsKey(name)) blame(classMap.get(name), name);
else orphanNaughtyClasses.add(name);
}
}
}
private static void checkPendingNaughty()
{
ImmutableSet.Builder<String> toRemove = ImmutableSet.builder();
for(String cls : orphanNaughtyClasses)
{
if(classMap.containsKey(cls))
{
String modId = classMap.get(cls);
blame(modId, cls);
toRemove.add(cls);
}
}
orphanNaughtyClasses.removeAll(toRemove.build());
}
public static void addClasses(String modId, Set<String> classList)
{
for(String cls : classList)
{
classMap.put(cls, modId);
}
checkPendingNaughty();
}
public static void onCrash(StringBuilder builder)
{
checkPendingNaughty();
if(!naughtyClasses.isEmpty())
{
builder.append("\n*** ATTENTION: detected classes with unsupported format ***\n");
builder.append("*** DO NOT SUBMIT THIS CRASH REPORT TO FORGE ***\n\n");
if(!naughtyMods.isEmpty())
{
builder.append("Contact authors of the following mods: \n");
for(String modId : naughtyMods)
{
builder.append(" ").append(modId).append("\n");
}
}
if(!orphanNaughtyClasses.isEmpty())
{
builder.append("Unidentified unsupported classes: \n");
for(String cls : orphanNaughtyClasses)
{
builder.append(" ").append(cls).append("\n");
}
}
builder.append('\n');
}
}
}
@@ -0,0 +1,52 @@
/*
* Forge Mod Loader
* Copyright (c) 2012-2013 cpw.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* cpw - implementation
*/
package net.minecraftforge.fml.common.asm.transformers;
import net.minecraft.launchwrapper.IClassNameTransformer;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import net.minecraftforge.fml.common.asm.transformers.deobf.FMLRemappingAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.RemappingClassAdapter;
public class DeobfuscationTransformer implements IClassTransformer, IClassNameTransformer {
@Override
public byte[] transform(String name, String transformedName, byte[] bytes)
{
if (bytes == null)
{
return null;
}
ClassReader classReader = new ClassReader(bytes);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
RemappingClassAdapter remapAdapter = new FMLRemappingAdapter(classWriter);
classReader.accept(remapAdapter, ClassReader.EXPAND_FRAMES);
return classWriter.toByteArray();
}
@Override
public String remapClassName(String name)
{
return FMLDeobfuscatingRemapper.INSTANCE.map(name.replace('.','/')).replace('/', '.');
}
@Override
public String unmapClassName(String name)
{
return FMLDeobfuscatingRemapper.INSTANCE.unmap(name.replace('.', '/')).replace('/','.');
}
}
@@ -0,0 +1,74 @@
package net.minecraftforge.fml.common.asm.transformers;
import java.lang.reflect.Modifier;
import java.util.List;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
public class EventSubscriberTransformer implements IClassTransformer
{
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass)
{
if (basicClass == null) return null;
ClassNode classNode = new ClassNode();
new ClassReader(basicClass).accept(classNode, 0);
boolean isSubscriber = false;
for (MethodNode methodNode : classNode.methods)
{
List<AnnotationNode> anns = methodNode.visibleAnnotations;
if (anns != null && Iterables.any(anns, SubscribeEventPredicate.INSTANCE))
{
if (Modifier.isPrivate(methodNode.access))
{
String msg = "Cannot apply @SubsribeEvent to private method %s/%s%s";
throw new RuntimeException(String.format(msg, classNode.name, methodNode.name, methodNode.desc));
}
methodNode.access = toPublic(methodNode.access);
isSubscriber = true;
}
}
if (isSubscriber)
{
classNode.access = toPublic(classNode.access);
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classNode.accept(writer);
return writer.toByteArray();
}
return basicClass;
}
private static int toPublic(int access)
{
return access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED) | Opcodes.ACC_PUBLIC;
}
private static class SubscribeEventPredicate implements Predicate<AnnotationNode>
{
static final SubscribeEventPredicate INSTANCE = new SubscribeEventPredicate();
@Override
public boolean apply(AnnotationNode input)
{
return input.desc.equals("Lnet/minecraftforge/fml/common/eventhandler/SubscribeEvent;");
}
}
}
@@ -0,0 +1,226 @@
package net.minecraftforge.fml.common.asm.transformers;
import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ASM5;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.F_SAME;
import static org.objectweb.asm.Opcodes.GETSTATIC;
import static org.objectweb.asm.Opcodes.IFNULL;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.PUTSTATIC;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.ICONST_1;
import static org.objectweb.asm.Type.VOID_TYPE;
import static org.objectweb.asm.Type.BOOLEAN_TYPE;
import static org.objectweb.asm.Type.getMethodDescriptor;
import java.util.List;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraftforge.fml.common.eventhandler.Event;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
public class EventSubscriptionTransformer implements IClassTransformer
{
public EventSubscriptionTransformer()
{
}
@Override
public byte[] transform(String name, String transformedName, byte[] bytes)
{
if (bytes == null || name.equals("net.minecraftforge.fml.common.eventhandler.Event") || name.startsWith("net.minecraft.") || name.indexOf('.') == -1)
{
return bytes;
}
ClassReader cr = new ClassReader(bytes);
ClassNode classNode = new ClassNode();
cr.accept(classNode, 0);
try
{
if (buildEvents(classNode))
{
ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
classNode.accept(cw);
return cw.toByteArray();
}
return bytes;
}
catch (ClassNotFoundException ex)
{
// Discard silently- it's just noise
}
catch (Exception e)
{
e.printStackTrace();
}
return bytes;
}
private boolean buildEvents(ClassNode classNode) throws Exception
{
// Yes, this recursively loads classes until we get this base class. THIS IS NOT A ISSUE. Coremods should handle re-entry just fine.
// If they do not this a COREMOD issue NOT a Forge/LaunchWrapper issue.
Class<?> parent = this.getClass().getClassLoader().loadClass(classNode.superName.replace('/', '.'));
if (!Event.class.isAssignableFrom(parent))
{
return false;
}
//Class<?> listenerListClazz = Class.forName("net.minecraftforge.fml.common.eventhandler.ListenerList", false, getClass().getClassLoader());
Type tList = Type.getType("Lnet/minecraftforge/fml/common/eventhandler/ListenerList;");
boolean edited = false;
boolean hasSetup = false;
boolean hasGetListenerList = false;
boolean hasDefaultCtr = false;
boolean hasCancelable = false;
boolean hasResult = false;
String voidDesc = Type.getMethodDescriptor(VOID_TYPE);
String boolDesc = Type.getMethodDescriptor(BOOLEAN_TYPE);
String listDesc = tList.getDescriptor();
String listDescM = Type.getMethodDescriptor(tList);
for (MethodNode method : (List<MethodNode>)classNode.methods)
{
if (method.name.equals("setup") && method.desc.equals(voidDesc) && (method.access & ACC_PROTECTED) == ACC_PROTECTED) hasSetup = true;
if ((method.access & ACC_PUBLIC) == ACC_PUBLIC)
{
if (method.name.equals("getListenerList") && method.desc.equals(listDescM)) hasGetListenerList = true;
if (method.name.equals("isCancelable") && method.desc.equals(boolDesc)) hasCancelable = true;
if (method.name.equals("hasResult") && method.desc.equals(boolDesc)) hasResult = true;
}
if (method.name.equals("<init>") && method.desc.equals(voidDesc)) hasDefaultCtr = true;
}
if (classNode.visibleAnnotations != null)
{
for (AnnotationNode node : classNode.visibleAnnotations)
{
if (!hasResult && node.desc.equals("Lnet/minecraftforge/fml/common/eventhandler/Event$HasResult;"))
{
/* Add:
* public boolean hasResult()
* {
* return true;
* }
*/
MethodNode method = new MethodNode(ACC_PUBLIC, "hasResult", boolDesc, null, null);
method.instructions.add(new InsnNode(ICONST_1));
method.instructions.add(new InsnNode(IRETURN));
classNode.methods.add(method);
edited = true;
}
else if (!hasCancelable && node.desc.equals("Lnet/minecraftforge/fml/common/eventhandler/Cancelable;"))
{
/* Add:
* public boolean isCancelable()
* {
* return true;
* }
*/
MethodNode method = new MethodNode(ACC_PUBLIC, "isCancelable", boolDesc, null, null);
method.instructions.add(new InsnNode(ICONST_1));
method.instructions.add(new InsnNode(IRETURN));
classNode.methods.add(method);
edited = true;
}
}
}
if (hasSetup)
{
if (!hasGetListenerList)
throw new RuntimeException("Event class defines setup() but does not define getListenerList! " + classNode.name);
else
return edited;
}
Type tSuper = Type.getType(classNode.superName);
//Add private static ListenerList LISTENER_LIST
classNode.fields.add(new FieldNode(ACC_PRIVATE | ACC_STATIC, "LISTENER_LIST", listDesc, null, null));
/*Add:
* public <init>()
* {
* super();
* }
*/
if (!hasDefaultCtr)
{
MethodNode method = new MethodNode(ACC_PUBLIC, "<init>", voidDesc, null, null);
method.instructions.add(new VarInsnNode(ALOAD, 0));
method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "<init>", voidDesc, false));
method.instructions.add(new InsnNode(RETURN));
classNode.methods.add(method);
}
/*Add:
* protected void setup()
* {
* super.setup();
* if (LISTENER_LIST != NULL)
* {
* return;
* }
* LISTENER_LIST = new ListenerList(super.getListenerList());
* }
*/
MethodNode method = new MethodNode(ACC_PROTECTED, "setup", voidDesc, null, null);
method.instructions.add(new VarInsnNode(ALOAD, 0));
method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "setup", voidDesc, false));
method.instructions.add(new FieldInsnNode(GETSTATIC, classNode.name, "LISTENER_LIST", listDesc));
LabelNode initLisitener = new LabelNode();
method.instructions.add(new JumpInsnNode(IFNULL, initLisitener));
method.instructions.add(new InsnNode(RETURN));
method.instructions.add(initLisitener);
method.instructions.add(new FrameNode(F_SAME, 0, null, 0, null));
method.instructions.add(new TypeInsnNode(NEW, tList.getInternalName()));
method.instructions.add(new InsnNode(DUP));
method.instructions.add(new VarInsnNode(ALOAD, 0));
method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tSuper.getInternalName(), "getListenerList", listDescM, false));
method.instructions.add(new MethodInsnNode(INVOKESPECIAL, tList.getInternalName(), "<init>", getMethodDescriptor(VOID_TYPE, tList), false));
method.instructions.add(new FieldInsnNode(PUTSTATIC, classNode.name, "LISTENER_LIST", listDesc));
method.instructions.add(new InsnNode(RETURN));
classNode.methods.add(method);
/*Add:
* public ListenerList getListenerList()
* {
* return this.LISTENER_LIST;
* }
*/
method = new MethodNode(ACC_PUBLIC, "getListenerList", listDescM, null, null);
method.instructions.add(new FieldInsnNode(GETSTATIC, classNode.name, "LISTENER_LIST", listDesc));
method.instructions.add(new InsnNode(ARETURN));
classNode.methods.add(method);
return true;
}
}
@@ -0,0 +1,85 @@
package net.minecraftforge.fml.common.asm.transformers;
import java.util.ListIterator;
import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
public class ItemStackTransformer implements IClassTransformer {
private static final String ITEM_TYPE = "Lnet/minecraft/item/Item;";
private static final String GETITEM_DESC = "()"+ ITEM_TYPE;
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (!"net.minecraft.item.ItemStack".equals(name))
return basicClass;
ClassNode classNode = new ClassNode();
ClassReader classReader = new ClassReader(basicClass);
classReader.accept(classNode, 0);
FieldNode itemField = null;
for (FieldNode f : classNode.fields)
{
if (ITEM_TYPE.equals(f.desc) && itemField == null)
{
itemField = f;
}
else if (ITEM_TYPE.equals(f.desc))
{
throw new RuntimeException("Error processing ItemStack - found a duplicate Item field");
}
}
if (itemField == null)
{
throw new RuntimeException("Error processing ItemStack - no Item field declared (is the code somehow obfuscated?)");
}
MethodNode getItemMethod = null;
for (MethodNode m: classNode.methods)
{
if (GETITEM_DESC.equals(m.desc) && getItemMethod == null)
{
getItemMethod = m;
}
else if (GETITEM_DESC.equals(m.desc))
{
throw new RuntimeException("Error processing ItemStack - duplicate getItem method found");
}
}
if (getItemMethod == null)
{
throw new RuntimeException("Error processing ItemStack - no getItem method found (is the code somehow obfuscated?)");
}
for (MethodNode m: classNode.methods)
{
for (ListIterator<AbstractInsnNode> it = m.instructions.iterator(); it.hasNext(); )
{
AbstractInsnNode insnNode = it.next();
if (insnNode.getType() == AbstractInsnNode.FIELD_INSN)
{
FieldInsnNode fi = (FieldInsnNode)insnNode;
if (itemField.name.equals(fi.name) && fi.getOpcode() == Opcodes.GETFIELD)
{
it.remove();
MethodInsnNode replace = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "net/minecraft/item/ItemStack",getItemMethod.name, getItemMethod.desc, false);
it.add(replace);
}
}
}
}
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classNode.accept(writer);
return writer.toByteArray();
}
}
Loaded 100 of 474 files, more files were not shown because too many files have changed in this diff. Show more