diff --git a/fml/src/main/java/cpw/mods/fml/common/DummyModContainer.java b/fml/src/main/java/cpw/mods/fml/common/DummyModContainer.java index da39335e87..f31a7a1e11 100644 --- a/fml/src/main/java/cpw/mods/fml/common/DummyModContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/DummyModContainer.java @@ -141,12 +141,6 @@ public class DummyModContainer implements ModContainer return false; } - @Override - public boolean isNetworkMod() - { - return false; - } - @Override public String getDisplayVersion() { @@ -186,4 +180,10 @@ public class DummyModContainer implements ModContainer { return null; } + + @Override + public Disableable canBeDisabled() + { + return Disableable.NEVER; + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/FMLModContainer.java b/fml/src/main/java/cpw/mods/fml/common/FMLModContainer.java index 10ca67d7df..3729f43ba0 100644 --- a/fml/src/main/java/cpw/mods/fml/common/FMLModContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/FMLModContainer.java @@ -54,6 +54,7 @@ import cpw.mods.fml.common.event.FMLEvent; import cpw.mods.fml.common.event.FMLFingerprintViolationEvent; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLInterModComms.IMCEvent; +import cpw.mods.fml.common.event.FMLModDisabledEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLServerAboutToStartEvent; @@ -80,7 +81,6 @@ public class FMLModContainer implements ModContainer private EventBus eventBus; private LoadController controller; private DefaultArtifactVersion processedVersion; - private boolean isNetworkMod; private String annotationDependencies; private VersionRange minecraftAccepted; @@ -89,6 +89,7 @@ public class FMLModContainer implements ModContainer private Certificate certificate; private String modLanguage; private ILanguageAdapter languageAdapter; + private Disableable disableability; private ListMultimap,Method> eventMethods; private Map customModProperties; private ModCandidate candidate; @@ -103,6 +104,7 @@ public class FMLModContainer implements ModContainer .add(FMLServerStoppedEvent.class) .add(IMCEvent.class) .add(FMLFingerprintViolationEvent.class) + .add(FMLModDisabledEvent.class) .build(); public FMLModContainer(String className, ModCandidate container, Map modDescriptor) @@ -491,7 +493,16 @@ public class FMLModContainer implements ModContainer 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()); @@ -544,12 +555,6 @@ public class FMLModContainer implements ModContainer return false; } - @Override - public boolean isNetworkMod() - { - return isNetworkMod; - } - @Override public String getDisplayVersion() { @@ -605,4 +610,10 @@ public class FMLModContainer implements ModContainer descriptor.put("description", modMetadata.description); return descriptor; } + + @Override + public Disableable canBeDisabled() + { + return disableability; + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/InjectedModContainer.java b/fml/src/main/java/cpw/mods/fml/common/InjectedModContainer.java index 88a61a96b3..b02e2545b0 100644 --- a/fml/src/main/java/cpw/mods/fml/common/InjectedModContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/InjectedModContainer.java @@ -109,11 +109,6 @@ public class InjectedModContainer implements ModContainer return wrappedContainer.getProcessedVersion(); } - @Override - public boolean isNetworkMod() - { - return wrappedContainer.isNetworkMod(); - } @Override public boolean isImmutable() { @@ -173,4 +168,10 @@ public class InjectedModContainer implements ModContainer { return wrappedContainer.getSharedModDescriptor(); } + + @Override + public Disableable canBeDisabled() + { + return wrappedContainer.canBeDisabled(); + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/LoadController.java b/fml/src/main/java/cpw/mods/fml/common/LoadController.java index 531c2cc33b..3fdf5d9553 100644 --- a/fml/src/main/java/cpw/mods/fml/common/LoadController.java +++ b/fml/src/main/java/cpw/mods/fml/common/LoadController.java @@ -14,6 +14,7 @@ package cpw.mods.fml.common; import java.lang.reflect.InvocationTargetException; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map.Entry; @@ -28,6 +29,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.Iterables; 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; @@ -35,6 +37,7 @@ import com.google.common.eventbus.Subscribe; import cpw.mods.fml.common.LoaderState.ModState; import cpw.mods.fml.common.event.FMLEvent; import cpw.mods.fml.common.event.FMLLoadEvent; +import cpw.mods.fml.common.event.FMLModDisabledEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.event.FMLStateEvent; import cpw.mods.fml.common.functions.ArtifactVersionNameFunction; @@ -63,6 +66,20 @@ public class LoadController } + void disableMod(ModContainer mod) + { + HashMap 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) { diff --git a/fml/src/main/java/cpw/mods/fml/common/Loader.java b/fml/src/main/java/cpw/mods/fml/common/Loader.java index bcb0cca6e3..912d30b77e 100644 --- a/fml/src/main/java/cpw/mods/fml/common/Loader.java +++ b/fml/src/main/java/cpw/mods/fml/common/Loader.java @@ -14,6 +14,7 @@ package cpw.mods.fml.common; import java.io.File; import java.io.FileReader; +import java.io.FileWriter; import java.io.IOException; import java.net.MalformedURLException; import java.util.Comparator; @@ -29,14 +30,17 @@ import com.google.common.base.CharMatcher; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Splitter; +import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultiset; import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Multimaps; import com.google.common.collect.Multiset.Entry; import com.google.common.collect.Multisets; import com.google.common.collect.Ordering; @@ -45,6 +49,7 @@ import com.google.common.collect.Sets; import com.google.common.collect.TreeMultimap; import cpw.mods.fml.common.LoaderState.ModState; +import cpw.mods.fml.common.ModContainer.Disableable; import cpw.mods.fml.common.discovery.ModDiscoverer; import cpw.mods.fml.common.event.FMLInterModComms; import cpw.mods.fml.common.event.FMLLoadEvent; @@ -121,6 +126,10 @@ public class Loader * A named list of mods */ private Map namedMods; + /** + * A reverse dependency graph for mods + */ + private ListMultimap reverseDependencies; /** * The canonical configuration directory */ @@ -133,6 +142,7 @@ public class Loader private static File minecraftDir; private static List injectedContainers; private ImmutableMap fmlBrandingProperties; + private File forcedModFile; public static Loader instance() { @@ -187,6 +197,7 @@ public class Loader modVersions.put(mod.getModId(), mod.getProcessedVersion()); } + ArrayListMultimap reqList = ArrayListMultimap.create(); for (ModContainer mod : getActiveModList()) { if (!mod.acceptableMinecraftVersionRange().containsVersion(minecraft.getProcessedVersion())) @@ -196,6 +207,7 @@ public class Loader } Map names = Maps.uniqueIndex(mod.getRequirements(), new ArtifactVersionNameFunction()); Set versionMissingMods = Sets.newHashSet(); + Set missingMods = Sets.difference(names.keySet(), modVersions.keySet()); if (!missingMods.isEmpty()) { @@ -206,6 +218,7 @@ public class Loader } throw new MissingModsException(versionMissingMods); } + reqList.putAll(mod.getModId(), names.keySet()); ImmutableList allDeps = ImmutableList.builder().addAll(mod.getDependants()).addAll(mod.getDependencies()).build(); for (ArtifactVersion v : allDeps) { @@ -226,6 +239,7 @@ public class Loader FMLLog.finer("All mod requirements are satisfied"); + reverseDependencies = Multimaps.invertFrom(reqList, ArrayListMultimap.create()); ModSorter sorter = new ModSorter(getActiveModList(), namedMods); try @@ -468,7 +482,7 @@ public class Loader } } modController.transition(LoaderState.CONSTRUCTING, false); - modController.distributeStateMessage(LoaderState.CONSTRUCTING, modClassLoader, disc.getASMTable()); + modController.distributeStateMessage(LoaderState.CONSTRUCTING, modClassLoader, disc.getASMTable(), reverseDependencies); FMLLog.fine("Mod signature data"); for (ModContainer mod : getActiveModList()) { @@ -493,7 +507,7 @@ public class Loader FMLLog.finer("System property request managing the state of %d mods", sysPropertyStateList.size()); Map modStates = Maps.newHashMap(); - File forcedModFile = new File(canonicalConfigDir, "fmlModState.properties"); + forcedModFile = new File(canonicalConfigDir, "fmlModState.properties"); Properties forcedModListProperties = new Properties(); if (forcedModFile.exists() && forcedModFile.isFile()) { @@ -837,4 +851,40 @@ public class Loader modController.propogateStateMessage(new FMLModIdMappingEvent(remaps)); } } + + public void runtimeDisableMod(String modId) + { + ModContainer mc = namedMods.get(modId); + Disableable disableable = mc.canBeDisabled(); + if (disableable == Disableable.NEVER) + { + FMLLog.info("Cannot disable mod %s - it is never allowed to be disabled", modId); + return; + } + if (disableable == Disableable.DEPENDENCIES) + { + FMLLog.info("Cannot disable mod %s - there are dependent mods that require its presence", modId); + return; + } + if (disableable == Disableable.YES) + { + FMLLog.info("Runtime disabling mod %s", modId); + modController.disableMod(mc); + List localmods = Lists.newArrayList(mods); + localmods.remove(mc); + mods = ImmutableList.copyOf(localmods); + } + + try + { + Properties props = new Properties(); + props.load(new FileReader(forcedModFile)); + props.put(modId, "false"); + props.store(new FileWriter(forcedModFile), null); + } + catch (Exception e) + { + FMLLog.log(Level.INFO, e, "An error occurred writing the fml mod states file, your disabled change won't persist"); + } + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/ModContainer.java b/fml/src/main/java/cpw/mods/fml/common/ModContainer.java index eaeb9c5834..47cc223d11 100644 --- a/fml/src/main/java/cpw/mods/fml/common/ModContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/ModContainer.java @@ -39,6 +39,9 @@ import cpw.mods.fml.common.versioning.VersionRange; public interface ModContainer { + public static enum Disableable { + YES, RESTART, NEVER, DEPENDENCIES; + } /** * The globally unique modid for this mod */ @@ -126,8 +129,6 @@ public interface ModContainer boolean isImmutable(); - boolean isNetworkMod(); - String getDisplayVersion(); VersionRange acceptableMinecraftVersionRange(); @@ -140,4 +141,6 @@ public interface ModContainer public Class getCustomResourcePackClass(); Map getSharedModDescriptor(); + + Disableable canBeDisabled(); } diff --git a/fml/src/main/java/cpw/mods/fml/common/event/FMLConstructionEvent.java b/fml/src/main/java/cpw/mods/fml/common/event/FMLConstructionEvent.java index b53b967694..a54898d545 100644 --- a/fml/src/main/java/cpw/mods/fml/common/event/FMLConstructionEvent.java +++ b/fml/src/main/java/cpw/mods/fml/common/event/FMLConstructionEvent.java @@ -5,13 +5,15 @@ * 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 cpw.mods.fml.common.event; +import com.google.common.collect.ListMultimap; + import cpw.mods.fml.common.LoaderState.ModState; import cpw.mods.fml.common.ModClassLoader; import cpw.mods.fml.common.discovery.ASMDataTable; @@ -20,11 +22,14 @@ public class FMLConstructionEvent extends FMLStateEvent { private ModClassLoader modClassLoader; private ASMDataTable asmData; + private ListMultimap reverseDependencies; + @SuppressWarnings("unchecked") public FMLConstructionEvent(Object... eventData) { this.modClassLoader = (ModClassLoader)eventData[0]; this.asmData = (ASMDataTable) eventData[1]; + this.reverseDependencies = (ListMultimap) eventData[2]; } public ModClassLoader getModClassLoader() @@ -42,4 +47,9 @@ public class FMLConstructionEvent extends FMLStateEvent { return asmData; } + + public ListMultimap getReverseDependencies() + { + return reverseDependencies; + } } diff --git a/fml/src/main/java/cpw/mods/fml/common/event/FMLModDisabledEvent.java b/fml/src/main/java/cpw/mods/fml/common/event/FMLModDisabledEvent.java new file mode 100644 index 0000000000..621bf16885 --- /dev/null +++ b/fml/src/main/java/cpw/mods/fml/common/event/FMLModDisabledEvent.java @@ -0,0 +1,4 @@ +package cpw.mods.fml.common.event; + +public class FMLModDisabledEvent extends FMLEvent { +}