Introduce a new centralized version checking system.

Using the @Mod annotation mods can opt-in to a centrally controlled update system.
This is PURELY a notification system and will NOT automatically download any updates.
The End User can control which mods check for updates and disabel the system entirely using the Forge Config and GUI.
Format for the json the URL must point to is described here: https://gist.github.com/LexManos/7aacb9aa991330523884
This commit is contained in:
Lex Manos 2015-11-04 14:37:15 -08:00
parent 479c7f8b54
commit d790008353
11 changed files with 291 additions and 41 deletions

View file

@ -6,42 +6,38 @@
package net.minecraftforge.client.gui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
import net.minecraftforge.common.ForgeChunkManager;
import net.minecraftforge.common.ForgeModContainer;
import net.minecraftforge.common.ForgeVersion;
import net.minecraftforge.common.config.ConfigCategory;
import net.minecraftforge.common.config.ConfigElement;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.common.config.Property;
import net.minecraftforge.fml.client.IModGuiFactory;
import net.minecraftforge.fml.client.IModGuiFactory.RuntimeOptionCategoryElement;
import net.minecraftforge.fml.client.IModGuiFactory.RuntimeOptionGuiHandler;
import net.minecraftforge.fml.client.config.ConfigGuiType;
import net.minecraftforge.fml.client.config.DummyConfigElement;
import net.minecraftforge.fml.client.config.DummyConfigElement.DummyCategoryElement;
import net.minecraftforge.fml.client.config.GuiButtonExt;
import net.minecraftforge.fml.client.config.GuiConfig;
import net.minecraftforge.fml.client.config.GuiConfigEntries;
import net.minecraftforge.fml.client.config.GuiConfigEntries.CategoryEntry;
import net.minecraftforge.fml.client.config.GuiConfigEntries.IConfigEntry;
import net.minecraftforge.fml.client.config.GuiConfigEntries.SelectValueEntry;
import net.minecraftforge.fml.client.config.GuiConfigEntries.BooleanEntry;
import net.minecraftforge.fml.client.config.HoverChecker;
import net.minecraftforge.fml.client.config.IConfigElement;
import net.minecraftforge.fml.client.config.GuiConfigEntries.ListEntryBase;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import static net.minecraftforge.common.ForgeModContainer.VERSION_CHECK_CAT;
/**
* This is the base GuiConfig screen class that all the other Forge-specific config screens will be called from.
@ -109,6 +105,7 @@ public class ForgeGuiFactory implements IModGuiFactory
List<IConfigElement> list = new ArrayList<IConfigElement>();
list.add(new DummyCategoryElement("forgeCfg", "forge.configgui.ctgy.forgeGeneralConfig", GeneralEntry.class));
list.add(new DummyCategoryElement("forgeChunkLoadingCfg", "forge.configgui.ctgy.forgeChunkLoadingConfig", ChunkLoaderEntry.class));
list.add(new DummyCategoryElement("forgeVersionCheckCfg", "forge.configgui.ctgy.VersionCheckConfig", VersionCheckEntry.class));
return list;
}
@ -166,6 +163,59 @@ public class ForgeGuiFactory implements IModGuiFactory
}
}
/**
* This custom list entry provides the Forge Version Checking Config entry on the Minecraft Forge Configuration screen.
* It extends the base Category entry class and defines the IConfigElement objects that will be used to build the child screen.
*/
public static class VersionCheckEntry extends CategoryEntry
{
public VersionCheckEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList, IConfigElement prop)
{
super(owningScreen, owningEntryList, prop);
}
@Override
protected GuiScreen buildChildScreen()
{
ConfigCategory cfg = ForgeModContainer.getConfig().getCategory(VERSION_CHECK_CAT);
Map<String, Property> values = new HashMap<String, Property>(cfg.getValues());
values.remove("Global");
Property global = ForgeModContainer.getConfig().get(VERSION_CHECK_CAT, "Global", true);
List<Property> props = new ArrayList<Property>();
for (ModContainer mod : ForgeVersion.gatherMods().keySet())
{
values.remove(mod.getModId());
props.add(ForgeModContainer.getConfig().get(VERSION_CHECK_CAT, mod.getModId(), true)); //Get or make the value in the config
}
props.addAll(values.values()); // Add any left overs from the config
Collections.sort(props, new Comparator<Property>()
{
@Override
public int compare(Property o1, Property o2)
{
return o1.getName().compareTo(o2.getName());
}
});
List<IConfigElement> list = new ArrayList<IConfigElement>();
list.add(new ConfigElement(global));
for (Property prop : props)
{
list.add(new ConfigElement(prop));
}
// This GuiConfig object specifies the configID of the object and as such will force-save when it is closed. The parent
// GuiConfig object's propertyList will also be refreshed to reflect the changes.
return new GuiConfig(this.owningScreen,
list,
this.owningScreen.modID, VERSION_CHECK_CAT, true, true,
GuiConfig.getAbridgedConfigPath(ForgeModContainer.getConfig().toString()));
}
}
/**
* This custom list entry provides the Mod Overrides entry on the Forge Chunk Loading config screen.
* It extends the base Category entry class and defines the IConfigElement objects that will be used to build the child screen.

View file

@ -12,13 +12,14 @@ import static net.minecraftforge.common.ForgeVersion.revisionVersion;
import static net.minecraftforge.common.config.Configuration.CATEGORY_GENERAL;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.world.storage.SaveHandler;
@ -58,6 +59,7 @@ import net.minecraftforge.fml.common.network.NetworkRegistry;
public class ForgeModContainer extends DummyModContainer implements WorldAccessContainer
{
public static final String VERSION_CHECK_CAT = "version_checking";
public static int clumpingThreshold = 64;
public static boolean removeErroringEntities = false;
public static boolean removeErroringTileEntities = false;
@ -73,6 +75,13 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
public static boolean forgeLightPipelineEnabled = true;
private static Configuration config;
private static ForgeModContainer INSTANCE;
public static ForgeModContainer getInstance()
{
return INSTANCE;
}
private URL updateJSONUrl = null;
public ForgeModContainer()
{
@ -82,7 +91,7 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
meta.name = "Minecraft Forge";
meta.version = String.format("%d.%d.%d.%d", majorVersion, minorVersion, revisionVersion, buildVersion);
meta.credits = "Made possible with help from many people";
meta.authorList = Arrays.asList("LexManos", "Eloraam", "Spacetoad");
meta.authorList = Arrays.asList("LexManos", "Cpw");
meta.description = "Minecraft Forge is a common open source API allowing a broad range of mods " +
"to work cooperatively together. It allows many mods to be created without " +
"them editing the main Minecraft code.";
@ -90,12 +99,17 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
meta.updateUrl = "http://MinecraftForge.net/forum/index.php/topic,5.0.html";
meta.screenshots = new String[0];
meta.logoFile = "/forge_logo.png";
try {
updateJSONUrl = new URL("http://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json");
} catch (MalformedURLException e) {}
config = null;
File cfgFile = new File(Loader.instance().getConfigDir(), "forge.cfg");
config = new Configuration(cfgFile);
syncConfig(true);
INSTANCE = this;
}
@Override
@ -230,6 +244,12 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
config.setCategoryPropertyOrder(CATEGORY_GENERAL, propOrder);
propOrder = new ArrayList<String>();
prop = config.get(VERSION_CHECK_CAT, "Global", true, "Enable the entire mod update check system. This only applies to mods using the Forge system.");
propOrder.add("Global");
config.setCategoryPropertyOrder(VERSION_CHECK_CAT, propOrder);
if (config.hasChanged())
{
config.save();
@ -254,6 +274,10 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
ForgeChunkManager.syncConfigDefaults();
ForgeChunkManager.loadConfiguration();
}
else if (VERSION_CHECK_CAT.equals(event.configID))
{
syncConfig(false);
}
}
}
@ -396,4 +420,10 @@ public class ForgeModContainer extends DummyModContainer implements WorldAccessC
Certificate[] certificates = getClass().getProtectionDomain().getCodeSource().getCertificates();
return certificates != null ? certificates[0] : null;
}
@Override
public URL getUpdateUrl()
{
return updateJSONUrl;
}
}

View file

@ -8,13 +8,25 @@ import static net.minecraftforge.common.ForgeVersion.Status.*;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.logging.log4j.Level;
import com.google.common.io.ByteStreams;
import com.google.gson.Gson;
import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.InjectedModContainer;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import net.minecraftforge.fml.common.versioning.ComparableVersion;
public class ForgeVersion
{
@ -55,12 +67,13 @@ public class ForgeVersion
public static Status getStatus()
{
return status;
return getResult(ForgeModContainer.getInstance()).status;
}
public static String getTarget()
{
return target;
CheckResult res = getResult(ForgeModContainer.getInstance());
return res.target != null ? res.target.toString() : null;
}
public static String getVersion()
@ -79,32 +92,75 @@ public class ForgeVersion
BETA_OUTDATED
}
public static class CheckResult
{
public final Status status;
public final ComparableVersion target;
public final Map<ComparableVersion, String> changes;
public final String url;
private CheckResult(Status status, ComparableVersion target, Map<ComparableVersion, String> changes, String url)
{
this.status = status;
this.target = target;
this.changes = changes == null ? null : Collections.unmodifiableMap(changes);
this.url = url;
}
}
public static void startVersionCheck()
{
new Thread("Forge Version Check")
{
@SuppressWarnings("unchecked")
@Override
public void run()
{
if (!ForgeModContainer.getConfig().get(ForgeModContainer.VERSION_CHECK_CAT, "Global", true).getBoolean())
{
FMLLog.log("ForgeVersionCheck", Level.INFO, "Global Forge version check system disabeld, no futher processing.");
return;
}
for (Entry<ModContainer, URL> entry : gatherMods().entrySet())
{
ModContainer mod = entry.getKey();
if (ForgeModContainer.getConfig().get(ForgeModContainer.VERSION_CHECK_CAT, mod.getModId(), true).getBoolean())
{
process(mod, entry.getValue());
}
else
{
FMLLog.log("ForgeVersionCheck", Level.INFO, "[%s] Skipped version check", mod.getModId());
}
}
}
private void process(ModContainer mod, URL url)
{
try
{
URL url = new URL("http://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json");
FMLLog.log("ForgeVersionCheck", Level.INFO, "[%s] Starting version check at %s", mod.getModId(), url.toString());
Status status = PENDING;
ComparableVersion target = null;
InputStream con = url.openStream();
String data = new String(ByteStreams.toByteArray(con));
con.close();
FMLLog.log("ForgeVersionCheck", Level.DEBUG, "[%s] Received version check data:\n%s", mod.getModId(), data);
Map<String, Object> json = new Gson().fromJson(data, Map.class);
//String homepage = (String)json.get("homepage");
Map<String, String> promos = (Map<String, String>)json.get("promos");
String display_url = (String)json.get("homepage");
String rec = promos.get(MinecraftForge.MC_VERSION + "-recommended");
String lat = promos.get(MinecraftForge.MC_VERSION + "-latest");
ArtifactVersion current = new DefaultArtifactVersion(getVersion());
ComparableVersion current = new ComparableVersion(mod.getVersion());
if (rec != null)
{
ArtifactVersion recommended = new DefaultArtifactVersion(rec);
ComparableVersion recommended = new ComparableVersion(rec);
int diff = recommended.compareTo(current);
if (diff == 0)
@ -114,39 +170,95 @@ public class ForgeVersion
status = AHEAD;
if (lat != null)
{
if (current.compareTo(new DefaultArtifactVersion(lat)) < 0)
ComparableVersion latest = new ComparableVersion(lat);
if (current.compareTo(latest) < 0)
{
status = OUTDATED;
target = lat;
target = latest;
}
}
}
else
{
status = OUTDATED;
target = rec;
target = recommended;
}
}
else if (lat != null)
{
if (current.compareTo(new DefaultArtifactVersion(lat)) < 0)
ComparableVersion latest = new ComparableVersion(lat);
if (current.compareTo(latest) < 0)
{
status = BETA_OUTDATED;
target = lat;
target = latest;
}
else
status = BETA;
}
else
status = BETA;
FMLLog.log("ForgeVersionCheck", Level.INFO, "[%s] Found status: %s Target: %s", mod.getModId(), status, target);
Map<ComparableVersion, String> changes = new LinkedHashMap<ComparableVersion, String>();
Map<String, String> tmp = (Map<String, String>)json.get(MinecraftForge.MC_VERSION);
if (tmp != null)
{
List<ComparableVersion> ordered = new ArrayList<ComparableVersion>();
for (String key : tmp.keySet())
{
ComparableVersion ver = new ComparableVersion(key);
if (ver.compareTo(current) > 0 && (target == null || ver.compareTo(target) < 1))
{
ordered.add(ver);
}
}
Collections.sort(ordered);
for (ComparableVersion ver : ordered)
{
changes.put(ver, tmp.get(ver.toString()));
}
}
if (mod instanceof InjectedModContainer)
mod = ((InjectedModContainer)mod).wrappedContainer;
results.put(mod, new CheckResult(status, target, changes, display_url));
}
catch (Exception e)
{
e.printStackTrace();
FMLLog.log("ForgeVersionCheck", Level.DEBUG, e, "Failed to process update information");
status = FAILED;
}
}
}.start();
}
// Gather a list of mods that have opted in to this update system by providing a URL.
// Small hack needed to support a interface change until we force a recompile.
public static Map<ModContainer, URL> gatherMods()
{
Map<ModContainer, URL> ret = new HashMap<ModContainer, URL>();
for (ModContainer mod : Loader.instance().getActiveModList())
{
URL url = null;
try {
url = mod.getUpdateUrl();
} catch (AbstractMethodError abs) { } //TODO: Remove this in 1.8.8+?
if (url != null)
ret.put(mod, url);
}
return ret;
}
private static Map<ModContainer, CheckResult> results = new ConcurrentHashMap<ModContainer, CheckResult>();
private static final CheckResult PENDING_CHECK = new CheckResult(PENDING, null, null, null);
public static CheckResult getResult(ModContainer mod)
{
if (mod instanceof InjectedModContainer)
mod = ((InjectedModContainer)mod).wrappedContainer;
CheckResult ret = results.get(mod);
return ret == null ? PENDING_CHECK : ret;
}
}

View file

@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
import javax.imageio.ImageIO;
@ -43,10 +44,14 @@ import net.minecraft.util.IChatComponent;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.StringUtils;
import net.minecraftforge.common.ForgeHooks;
import net.minecraftforge.common.ForgeVersion;
import net.minecraftforge.common.ForgeVersion.CheckResult;
import net.minecraftforge.common.ForgeVersion.Status;
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 net.minecraftforge.fml.common.versioning.ComparableVersion;
import static net.minecraft.util.EnumChatFormatting.*;
import org.apache.logging.log4j.Level;
@ -343,7 +348,7 @@ public class GuiModList extends GuiScreen
ResourceLocation logoPath = null;
Dimension logoDims = new Dimension(0, 0);
List<String> lines = new ArrayList<String>();
//CheckResult vercheck = ForgeVersion.getResult(selectedMod);
CheckResult vercheck = ForgeVersion.getResult(selectedMod);
String logoFile = selectedMod.getMetadata().logoFile;
if (!logoFile.isEmpty())
@ -408,8 +413,8 @@ public class GuiModList extends GuiScreen
else
lines.add("Child mods: " + selectedMod.getMetadata().getChildModList());
//if (vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED)
// lines.add("Update Avalible: " + (vercheck.url == null ? "" : vercheck.url));
if (vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED)
lines.add("Update Avalible: " + (vercheck.url == null ? "" : vercheck.url));
lines.add(null);
lines.add(selectedMod.getMetadata().description);
@ -419,15 +424,15 @@ public class GuiModList extends GuiScreen
lines.add(WHITE + selectedMod.getName());
lines.add(WHITE + "Version: " + selectedMod.getVersion());
lines.add(WHITE + "Mod State: " + Loader.instance().getModState(selectedMod));
//if (vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED)
// lines.add("Update Avalible: " + (vercheck.url == null ? "" : vercheck.url));
if (vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED)
lines.add("Update Avalible: " + (vercheck.url == null ? "" : vercheck.url));
lines.add(null);
lines.add(RED + "No mod information found");
lines.add(RED + "Ask your mod author to provide a mod mcmod.info file");
}
/*if ((vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED) && vercheck.changes.size() > 0)
if ((vercheck.status == Status.OUTDATED || vercheck.status == Status.BETA_OUTDATED) && vercheck.changes.size() > 0)
{
lines.add(null);
lines.add("Changes:");
@ -437,7 +442,7 @@ public class GuiModList extends GuiScreen
lines.add(entry.getValue());
lines.add(null);
}
}*/
}
modInfo = new Info(this.width - this.listWidth - 30, lines, logoPath, logoDims);
}

View file

@ -17,6 +17,8 @@ import java.util.ArrayList;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.StringUtils;
import net.minecraftforge.common.ForgeVersion;
import net.minecraftforge.common.ForgeVersion.CheckResult;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.LoaderState.ModState;
import net.minecraftforge.fml.common.ModContainer;
@ -79,7 +81,7 @@ public class GuiSlotModList extends GuiScrollingList
String name = StringUtils.stripControlCodes(mc.getName());
String version = StringUtils.stripControlCodes(mc.getDisplayVersion());
FontRenderer font = this.parent.getFontRenderer();
//CheckResult vercheck = ForgeVersion.getResult(mc);
CheckResult vercheck = ForgeVersion.getResult(mc);
if (Loader.instance().getModState(mc) == ModState.DISABLED)
{
@ -93,7 +95,7 @@ public class GuiSlotModList extends GuiScrollingList
font.drawString(font.trimStringToWidth(version, listWidth - 10), this.left + 3 , top + 12, 0xCCCCCC);
font.drawString(font.trimStringToWidth(mc.getMetadata() != null ? mc.getMetadata().getChildModCountString() : "Metadata not found", listWidth - 10), this.left + 3 , top + 22, 0xCCCCCC);
/*switch(vercheck.status) //TODO: Change to icons?
switch(vercheck.status) //TODO: Change to icons?
{
case BETA_OUTDATED:
case OUTDATED:
@ -105,8 +107,7 @@ public class GuiSlotModList extends GuiScrollingList
case PENDING:
case UP_TO_DATE:
break;
}*/
}
}
}
}

View file

@ -13,6 +13,7 @@
package net.minecraftforge.fml.common;
import java.io.File;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.List;
@ -206,4 +207,10 @@ public class DummyModContainer implements ModContainer
{
return true;
}
@Override
public URL getUpdateUrl()
{
return null;
}
}

View file

@ -18,6 +18,8 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.List;
@ -87,6 +89,7 @@ public class FMLModContainer implements ModContainer
private ListMultimap<Class<? extends FMLEvent>,Method> eventMethods;
private Map<String, String> customModProperties;
private ModCandidate candidate;
private URL updateJSONUrl;
public FMLModContainer(String className, ModCandidate container, Map<String,Object> modDescriptor)
{
@ -215,6 +218,19 @@ public class FMLModContainer implements ModContainer
{
minecraftAccepted = Loader.instance().getMinecraftModContainer().getStaticVersionRange();
}
String jsonURL = (String)descriptor.get("updateJSON");
if (!Strings.isNullOrEmpty(jsonURL))
{
try
{
this.updateJSONUrl = new URL(jsonURL);
}
catch (MalformedURLException e)
{
FMLLog.log(getModId(), Level.DEBUG, "Specified json URL invalid: %s", jsonURL);
}
}
}
public Properties searchForVersionProperties()
@ -663,4 +679,10 @@ public class FMLModContainer implements ModContainer
return true;
}
@Override
public URL getUpdateUrl()
{
return updateJSONUrl;
}
}

View file

@ -13,6 +13,7 @@
package net.minecraftforge.fml.common;
import java.io.File;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.List;
import java.util.Map;
@ -207,4 +208,10 @@ public class InjectedModContainer implements ModContainer
{
return true;
}
@Override
public URL getUpdateUrl()
{
return wrappedContainer.getUpdateUrl();
}
}

View file

@ -157,18 +157,18 @@ public @interface Mod
* @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
@ -198,6 +198,14 @@ public @interface Mod
* @return The name of a class implementing {@link IModGuiFactory}
*/
String guiFactory() default "";
/**
* An optional URL to a JSON file that will be checked once per launch to determine if there is an updated
* version of this mod and notify the end user. For more information see ForgeVersion.
* @return URL to update metadata json
*/
String updateJSON() default "";
/**
* A list of custom properties for this mod. Completely up to the mod author if/when they
* want to put anything in here.

View file

@ -13,6 +13,7 @@
package net.minecraftforge.fml.common;
import java.io.File;
import java.net.URL;
import java.security.cert.Certificate;
import java.util.List;
import java.util.Map;
@ -149,4 +150,6 @@ public interface ModContainer
List<String> getOwnedPackages();
boolean shouldLoadInEnvironment();
URL getUpdateUrl();
}

View file

@ -35,7 +35,12 @@ public class ModMetadata
public String description = "";
public String url = "";
@Deprecated //Never really used for anything and format is undefined. See updateJSON for replacement.
public String updateUrl = "";
/**
* URL to update json file. Format is defined here: https://gist.github.com/LexManos/7aacb9aa991330523884
*/
public String updateJSON = "";
public String logoFile = "";
public String version = "";