From d79000835348d76735b93a147d703549462c2334 Mon Sep 17 00:00:00 2001 From: Lex Manos Date: Wed, 4 Nov 2015 14:37:15 -0800 Subject: [PATCH] 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 --- .../client/gui/ForgeGuiFactory.java | 68 +++++++-- .../common/ForgeModContainer.java | 34 ++++- .../minecraftforge/common/ForgeVersion.java | 142 ++++++++++++++++-- .../minecraftforge/fml/client/GuiModList.java | 19 ++- .../fml/client/GuiSlotModList.java | 9 +- .../fml/common/DummyModContainer.java | 7 + .../fml/common/FMLModContainer.java | 22 +++ .../fml/common/InjectedModContainer.java | 7 + .../net/minecraftforge/fml/common/Mod.java | 16 +- .../fml/common/ModContainer.java | 3 + .../fml/common/ModMetadata.java | 5 + 11 files changed, 291 insertions(+), 41 deletions(-) diff --git a/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java b/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java index 1f3e18616..fcfee4e9e 100644 --- a/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java +++ b/src/main/java/net/minecraftforge/client/gui/ForgeGuiFactory.java @@ -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 list = new ArrayList(); 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 values = new HashMap(cfg.getValues()); + values.remove("Global"); + + Property global = ForgeModContainer.getConfig().get(VERSION_CHECK_CAT, "Global", true); + + List props = new ArrayList(); + + 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() + { + @Override + public int compare(Property o1, Property o2) + { + return o1.getName().compareTo(o2.getName()); + } + }); + + List list = new ArrayList(); + 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. diff --git a/src/main/java/net/minecraftforge/common/ForgeModContainer.java b/src/main/java/net/minecraftforge/common/ForgeModContainer.java index 710529534..3805d49f7 100644 --- a/src/main/java/net/minecraftforge/common/ForgeModContainer.java +++ b/src/main/java/net/minecraftforge/common/ForgeModContainer.java @@ -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(); + 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; + } } diff --git a/src/main/java/net/minecraftforge/common/ForgeVersion.java b/src/main/java/net/minecraftforge/common/ForgeVersion.java index bc5db2f84..71d594c54 100644 --- a/src/main/java/net/minecraftforge/common/ForgeVersion.java +++ b/src/main/java/net/minecraftforge/common/ForgeVersion.java @@ -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 changes; + public final String url; + + private CheckResult(Status status, ComparableVersion target, Map 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 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 json = new Gson().fromJson(data, Map.class); - //String homepage = (String)json.get("homepage"); Map promos = (Map)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 changes = new LinkedHashMap(); + Map tmp = (Map)json.get(MinecraftForge.MC_VERSION); + if (tmp != null) + { + List ordered = new ArrayList(); + 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 gatherMods() + { + Map ret = new HashMap(); + 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 results = new ConcurrentHashMap(); + 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; + } } diff --git a/src/main/java/net/minecraftforge/fml/client/GuiModList.java b/src/main/java/net/minecraftforge/fml/client/GuiModList.java index ba0085f0a..720e3e087 100644 --- a/src/main/java/net/minecraftforge/fml/client/GuiModList.java +++ b/src/main/java/net/minecraftforge/fml/client/GuiModList.java @@ -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 lines = new ArrayList(); - //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); } diff --git a/src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java b/src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java index 1d47fea2f..90198e1d2 100644 --- a/src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java +++ b/src/main/java/net/minecraftforge/fml/client/GuiSlotModList.java @@ -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; - }*/ + } } } - } diff --git a/src/main/java/net/minecraftforge/fml/common/DummyModContainer.java b/src/main/java/net/minecraftforge/fml/common/DummyModContainer.java index 88b171c36..d9e11b3af 100644 --- a/src/main/java/net/minecraftforge/fml/common/DummyModContainer.java +++ b/src/main/java/net/minecraftforge/fml/common/DummyModContainer.java @@ -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; + } } diff --git a/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java b/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java index 60ae2c6b2..05ea50593 100644 --- a/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java +++ b/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java @@ -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,Method> eventMethods; private Map customModProperties; private ModCandidate candidate; + private URL updateJSONUrl; public FMLModContainer(String className, ModCandidate container, Map 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; + } } diff --git a/src/main/java/net/minecraftforge/fml/common/InjectedModContainer.java b/src/main/java/net/minecraftforge/fml/common/InjectedModContainer.java index af91db153..783c28b2d 100644 --- a/src/main/java/net/minecraftforge/fml/common/InjectedModContainer.java +++ b/src/main/java/net/minecraftforge/fml/common/InjectedModContainer.java @@ -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(); + } } diff --git a/src/main/java/net/minecraftforge/fml/common/Mod.java b/src/main/java/net/minecraftforge/fml/common/Mod.java index a2ad32063..7513fdf68 100644 --- a/src/main/java/net/minecraftforge/fml/common/Mod.java +++ b/src/main/java/net/minecraftforge/fml/common/Mod.java @@ -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.
* 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. diff --git a/src/main/java/net/minecraftforge/fml/common/ModContainer.java b/src/main/java/net/minecraftforge/fml/common/ModContainer.java index dde8ddef5..883f8b95c 100644 --- a/src/main/java/net/minecraftforge/fml/common/ModContainer.java +++ b/src/main/java/net/minecraftforge/fml/common/ModContainer.java @@ -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 getOwnedPackages(); boolean shouldLoadInEnvironment(); + + URL getUpdateUrl(); } diff --git a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java index add4b37ae..f0173ca17 100644 --- a/src/main/java/net/minecraftforge/fml/common/ModMetadata.java +++ b/src/main/java/net/minecraftforge/fml/common/ModMetadata.java @@ -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 = "";