diff --git a/src/main/java/net/minecraftforge/fml/client/CustomModLoadingErrorDisplayException.java b/src/main/java/net/minecraftforge/fml/client/CustomModLoadingErrorDisplayException.java index 98684c22a..fd8b2ec75 100644 --- a/src/main/java/net/minecraftforge/fml/client/CustomModLoadingErrorDisplayException.java +++ b/src/main/java/net/minecraftforge/fml/client/CustomModLoadingErrorDisplayException.java @@ -21,6 +21,7 @@ package net.minecraftforge.fml.client; import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiErrorScreen; +import net.minecraft.client.gui.GuiScreen; import net.minecraftforge.fml.common.EnhancedRuntimeException; import net.minecraftforge.fml.common.IFMLHandledException; import net.minecraftforge.fml.relauncher.Side; @@ -37,7 +38,7 @@ import net.minecraftforge.fml.relauncher.SideOnly; * */ @SideOnly(Side.CLIENT) -public abstract class CustomModLoadingErrorDisplayException extends EnhancedRuntimeException implements IFMLHandledException +public abstract class CustomModLoadingErrorDisplayException extends EnhancedRuntimeException implements IFMLHandledException, IDisplayableError { public CustomModLoadingErrorDisplayException() { } @@ -72,4 +73,10 @@ public abstract class CustomModLoadingErrorDisplayException extends EnhancedRunt 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. + + @Override + public final GuiScreen createGui() + { + return new GuiCustomModLoadingErrorScreen(this); + } } diff --git a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java index 6dbedb119..6f03eb569 100644 --- a/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java +++ b/src/main/java/net/minecraftforge/fml/client/FMLClientHandler.java @@ -112,6 +112,7 @@ import net.minecraftforge.registries.GameData; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.FormattedMessage; import org.lwjgl.LWJGLUtil; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.Display; @@ -171,19 +172,13 @@ public class FMLClientHandler implements IFMLSidedHandler private DummyModContainer optifineContainer; + @Deprecated // TODO remove in 1.13. mods are referencing this to get around client-only dependencies in old Forge versions private MissingModsException modsMissing; - private ModSortingException modSorting; - private boolean loading = true; - private WrongMinecraftVersionException wrongMC; - - private CustomModLoadingErrorDisplayException customError; - - private DuplicateModsFoundException dupesFound; - - private MultipleModsErrored multipleModsErrored; + @Nullable + private IDisplayableError errorToDisplay; private boolean serverShouldBeKilledQuietly; @@ -209,7 +204,6 @@ public class FMLClientHandler implements IFMLSidedHandler * @param resourcePackList The resource pack list we will populate with mods * @param resourceManager The resource manager */ - @SuppressWarnings("unchecked") public void beginMinecraftLoading(Minecraft minecraft, List resourcePackList, IReloadableResourceManager resourceManager, MetadataSerializer metaSerializer) { detectOptifine(); @@ -230,30 +224,11 @@ public class FMLClientHandler implements IFMLSidedHandler { Loader.instance().loadMods(injectedModContainers); } - catch (WrongMinecraftVersionException wrong) + catch (WrongMinecraftVersionException | DuplicateModsFoundException | MissingModsException | ModSortingException | CustomModLoadingErrorDisplayException | MultipleModsErrored e) { - wrongMC = wrong; - } - catch (DuplicateModsFoundException dupes) - { - dupesFound = dupes; - } - catch (MissingModsException missing) - { - modsMissing = missing; - } - catch (ModSortingException sorting) - { - modSorting = sorting; - } - catch (CustomModLoadingErrorDisplayException custom) - { - FMLLog.log.error("A custom exception was thrown by a mod, the game will now halt", custom); - customError = custom; - } - catch (MultipleModsErrored multiple) - { - multipleModsErrored = multiple; + FMLLog.log.error("An exception was thrown, the game will display an error screen and halt.", e); + errorToDisplay = e; + MinecraftForge.EVENT_BUS.shutdown(); } catch (LoaderException le) { @@ -274,8 +249,9 @@ public class FMLClientHandler implements IFMLSidedHandler if (le.getCause() instanceof CustomModLoadingErrorDisplayException) { CustomModLoadingErrorDisplayException custom = (CustomModLoadingErrorDisplayException) le.getCause(); - FMLLog.log.error("A custom exception was thrown by a mod, the game will now halt", custom); - customError = custom; + FMLLog.log.error("A custom exception was thrown by a mod, the game will display an error screen and halt.", custom); + errorToDisplay = custom; + MinecraftForge.EVENT_BUS.shutdown(); } else { @@ -284,6 +260,7 @@ public class FMLClientHandler implements IFMLSidedHandler } } + @SuppressWarnings("unchecked") Map> sharedModList = (Map>) Launch.blackboard.get("modList"); if (sharedModList == null) { @@ -332,7 +309,7 @@ public class FMLClientHandler implements IFMLSidedHandler public boolean hasError() { - return modsMissing != null || wrongMC != null || customError != null || dupesFound != null || modSorting != null || multipleModsErrored != null; + return errorToDisplay != null; } /** @@ -356,8 +333,9 @@ public class FMLClientHandler implements IFMLSidedHandler if (le.getCause() instanceof CustomModLoadingErrorDisplayException) { CustomModLoadingErrorDisplayException custom = (CustomModLoadingErrorDisplayException) le.getCause(); - FMLLog.log.error("A custom exception was thrown by a mod, the game will now halt", custom); - customError = custom; + FMLLog.log.error("A custom exception was thrown by a mod, the game will display an error screen and halt.", custom); + errorToDisplay = custom; + MinecraftForge.EVENT_BUS.shutdown(); } else { @@ -369,6 +347,7 @@ public class FMLClientHandler implements IFMLSidedHandler // This call is being phased out for performance reasons in 1.12, // but we are keeping an option here in case something needs it for a little longer. // See https://github.com/MinecraftForge/MinecraftForge/pull/4032 + // TODO remove in 1.13 if (Boolean.parseBoolean(System.getProperty("fml.reloadResourcesOnStart", "false"))) { client.refreshResources(); @@ -401,7 +380,7 @@ public class FMLClientHandler implements IFMLSidedHandler } loading = false; client.gameSettings.loadOptions(); //Reload options to load any mod added keybindings. - if (customError == null) + if (!hasError()) Loader.instance().loadingComplete(); SplashProgress.finish(); } @@ -439,29 +418,10 @@ public class FMLClientHandler implements IFMLSidedHandler // re-sync TEXTURE_2D, splash screen disables it with a direct GL call GlStateManager.disableTexture2D(); GlStateManager.enableTexture2D(); - if (wrongMC != null) + if (errorToDisplay != null) { - showGuiScreen(new GuiWrongMinecraft(wrongMC)); - } - else if (modsMissing != null) - { - showGuiScreen(new GuiModsMissing(modsMissing)); - } - else if (dupesFound != null) - { - showGuiScreen(new GuiDupesFound(dupesFound)); - } - else if (modSorting != null) - { - showGuiScreen(new GuiSortingProblem(modSorting)); - } - else if (customError != null) - { - showGuiScreen(new GuiCustomModLoadingErrorScreen(customError)); - } - else if (multipleModsErrored != null) - { - showGuiScreen(new GuiMultipleModsErrored(multipleModsErrored)); + GuiScreen errorScreen = errorToDisplay.createGui(); + showGuiScreen(errorScreen); } else { @@ -616,6 +576,10 @@ public class FMLClientHandler implements IFMLSidedHandler return client.getIntegratedServer(); } + /** + * TODO remove in 1.13 + */ + @Deprecated public void displayMissingMods(Object modMissingPacket) { // showGuiScreen(new GuiModsMissingForServer(modMissingPacket)); @@ -679,7 +643,8 @@ public class FMLClientHandler implements IFMLSidedHandler } catch (Exception e) { - throw new RuntimeException("An unexpected exception occurred constructing the custom resource pack for " + container.getName(), e); + FormattedMessage message = new FormattedMessage("An unexpected exception occurred constructing the custom resource pack for {} ({})", container.getName(), container.getModId()); + throw new RuntimeException(message.getFormattedMessage(), e); } } } @@ -698,14 +663,6 @@ public class FMLClientHandler implements IFMLSidedHandler @Override public void serverStopped() { - // If the server crashes during startup, it might hang the client - reset the client so it can abend properly. - MinecraftServer server = getServer(); - - if (server != null && !server.serverIsInRunLoop()) - { -// ObfuscationReflectionHelper.setPrivateValue(MinecraftServer.class, server, true, "field_71296"+"_Q","serverIs"+"Running"); - } - GameData.revertToFrozen(); } diff --git a/src/main/java/net/minecraftforge/fml/client/IDisplayableError.java b/src/main/java/net/minecraftforge/fml/client/IDisplayableError.java new file mode 100644 index 000000000..7f16938f0 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/client/IDisplayableError.java @@ -0,0 +1,11 @@ +package net.minecraftforge.fml.client; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +public interface IDisplayableError +{ + @SideOnly(Side.CLIENT) + GuiScreen createGui(); +} diff --git a/src/main/java/net/minecraftforge/fml/common/DuplicateModsFoundException.java b/src/main/java/net/minecraftforge/fml/common/DuplicateModsFoundException.java index 7b9ec5aed..860ecf976 100644 --- a/src/main/java/net/minecraftforge/fml/common/DuplicateModsFoundException.java +++ b/src/main/java/net/minecraftforge/fml/common/DuplicateModsFoundException.java @@ -23,8 +23,14 @@ import java.io.File; import java.util.Map.Entry; import com.google.common.collect.SetMultimap; +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.fml.client.GuiDupesFound; +import net.minecraftforge.fml.client.IDisplayableError; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; -public class DuplicateModsFoundException extends LoaderException { +public class DuplicateModsFoundException extends LoaderException implements IDisplayableError +{ private static final long serialVersionUID = 1L; public SetMultimap dupes; @@ -42,4 +48,11 @@ public class DuplicateModsFoundException extends LoaderException { } stream.println(""); } + + @Override + @SideOnly(Side.CLIENT) + public GuiScreen createGui() + { + return new GuiDupesFound(this); + } } diff --git a/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java b/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java index 73eccdd2f..72f58ecc3 100644 --- a/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java +++ b/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java @@ -36,6 +36,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import com.google.common.base.Throwables; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.config.Config; import net.minecraftforge.common.config.ConfigManager; @@ -73,6 +74,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; +import org.apache.logging.log4j.message.FormattedMessage; import javax.annotation.Nullable; @@ -380,8 +382,7 @@ public class FMLModContainer implements ModContainer } @Nullable - @SuppressWarnings("unchecked") - private Method gatherAnnotations(Class clazz) throws Exception + private Method gatherAnnotations(Class clazz) { Method factoryMethod = null; for (Method m : clazz.getDeclaredMethods()) @@ -393,7 +394,9 @@ public class FMLModContainer implements ModContainer if (m.getParameterTypes().length == 1 && FMLEvent.class.isAssignableFrom(m.getParameterTypes()[0])) { m.setAccessible(true); - eventMethods.put((Class)m.getParameterTypes()[0], m); + @SuppressWarnings("unchecked") + Class parameterType = (Class) m.getParameterTypes()[0]; + eventMethods.put(parameterType, m); } else { @@ -421,7 +424,7 @@ public class FMLModContainer implements ModContainer return factoryMethod; } - private void processFieldAnnotations(ASMDataTable asmDataTable) throws Exception + private void processFieldAnnotations(ASMDataTable asmDataTable) throws IllegalAccessException { SetMultimap annotations = asmDataTable.getAnnotationsFor(this); @@ -503,86 +506,110 @@ public class FMLModContainer implements ModContainer @Subscribe public void constructMod(FMLConstructionEvent event) { + BlamingTransformer.addClasses(getModId(), candidate.getClassList()); + ModClassLoader modClassLoader = event.getModClassLoader(); try { - BlamingTransformer.addClasses(getModId(), candidate.getClassList()); - ModClassLoader modClassLoader = event.getModClassLoader(); modClassLoader.addFile(source); - modClassLoader.clearNegativeCacheFor(candidate.getClassList()); + } + catch (MalformedURLException e) + { + FormattedMessage message = new FormattedMessage("{} Failed to add file to classloader: {}", getModId(), source); + throw new LoaderException(message.getFormattedMessage(), e); + } + modClassLoader.clearNegativeCacheFor(candidate.getClassList()); - //Only place I could think to add this... - MinecraftForge.preloadCrashClasses(event.getASMHarvestedData(), getModId(), candidate.getClassList()); + //Only place I could think to add this... + MinecraftForge.preloadCrashClasses(event.getASMHarvestedData(), getModId(), candidate.getClassList()); - Class clazz = Class.forName(className, true, modClassLoader); + Class clazz; + try + { + clazz = Class.forName(className, true, modClassLoader); + } + catch (ClassNotFoundException e) + { + FormattedMessage message = new FormattedMessage("{} Failed load class: {}", getModId(), className); + throw new LoaderException(message.getFormattedMessage(), e); + } - Certificate[] certificates = clazz.getProtectionDomain().getCodeSource().getCertificates(); - ImmutableList certList = CertificateHelper.getFingerprints(certificates); - sourceFingerprints = ImmutableSet.copyOf(certList); + Certificate[] certificates = clazz.getProtectionDomain().getCodeSource().getCertificates(); + ImmutableList certList = CertificateHelper.getFingerprints(certificates); + sourceFingerprints = ImmutableSet.copyOf(certList); - String expectedFingerprint = (String)descriptor.get("certificateFingerprint"); + String expectedFingerprint = (String)descriptor.get("certificateFingerprint"); - fingerprintNotPresent = true; + fingerprintNotPresent = true; - if (expectedFingerprint != null && !expectedFingerprint.isEmpty()) + if (expectedFingerprint != null && !expectedFingerprint.isEmpty()) + { + if (!sourceFingerprints.contains(expectedFingerprint)) { - if (!sourceFingerprints.contains(expectedFingerprint)) - { - Level warnLevel = Level.ERROR; - if (source.isDirectory()) - { - warnLevel = Level.TRACE; - } - FMLLog.log.log(warnLevel, "The mod {} is expecting signature {} for source {}, however there is no signature matching that description", getModId(), expectedFingerprint, source.getName()); - } - else - { - certificate = certificates[certList.indexOf(expectedFingerprint)]; - fingerprintNotPresent = false; - } - } - - @SuppressWarnings("unchecked") - List> props = (List>)descriptor.get("customProperties"); - if (props != null) - { - com.google.common.collect.ImmutableMap.Builder builder = ImmutableMap.builder(); - for (Map p : props) - { - builder.put((String)p.get("k"), (String)p.get("v")); - } - customModProperties = builder.build(); + Level warnLevel = source.isDirectory() ? Level.TRACE : Level.ERROR; + FMLLog.log.log(warnLevel, "The mod {} is expecting signature {} for source {}, however there is no signature matching that description", getModId(), expectedFingerprint, source.getName()); } else { - customModProperties = EMPTY_PROPERTIES; + certificate = certificates[certList.indexOf(expectedFingerprint)]; + fingerprintNotPresent = false; } + } - Boolean hasDisableableFlag = (Boolean)descriptor.get("canBeDeactivated"); - boolean hasReverseDepends = !event.getReverseDependencies().get(getModId()).isEmpty(); - if (hasDisableableFlag != null && hasDisableableFlag) + @SuppressWarnings("unchecked") + List> props = (List>)descriptor.get("customProperties"); + if (props != null) + { + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Map p : props) { - disableability = hasReverseDepends ? Disableable.DEPENDENCIES : Disableable.YES; + builder.put(p.get("k"), p.get("v")); } - 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.getOrDefault("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()); - AutomaticEventSubscriber.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide()); - ConfigManager.sync(this.getModId(), Config.Type.INSTANCE); + 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); + ILanguageAdapter languageAdapter = getLanguageAdapter(); + try + { + modInstance = languageAdapter.getNewInstance(this, clazz, modClassLoader, factoryMethod); + } + catch (Exception e) + { + Throwables.throwIfUnchecked(e); + FormattedMessage message = new FormattedMessage("{} Failed to load new mod instance.", getModId()); + throw new LoaderException(message.getFormattedMessage(), e); + } + NetworkRegistry.INSTANCE.register(this, clazz, (String)(descriptor.getOrDefault("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(), languageAdapter); + AutomaticEventSubscriber.inject(this, event.getASMHarvestedData(), FMLCommonHandler.instance().getSide()); + ConfigManager.sync(this.getModId(), Config.Type.INSTANCE); + + try + { processFieldAnnotations(event.getASMHarvestedData()); } - catch (Throwable e) + catch (IllegalAccessException e) { - controller.errorOccurred(this, e); + FormattedMessage message = new FormattedMessage("{} Failed to process field annotations.", getModId()); + throw new LoaderException(message.getFormattedMessage(), e); } } diff --git a/src/main/java/net/minecraftforge/fml/common/LoadController.java b/src/main/java/net/minecraftforge/fml/common/LoadController.java index 681631f06..5e7e222aa 100644 --- a/src/main/java/net/minecraftforge/fml/common/LoadController.java +++ b/src/main/java/net/minecraftforge/fml/common/LoadController.java @@ -20,15 +20,14 @@ package net.minecraftforge.fml.common; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.stream.Collectors; +import java.util.stream.Stream; -import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.util.TextTable; import net.minecraftforge.fml.common.LoaderState.ModState; import net.minecraftforge.fml.common.ProgressManager.ProgressBar; @@ -39,9 +38,10 @@ import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; import net.minecraftforge.fml.common.event.FMLStateEvent; import net.minecraftforge.fml.common.versioning.ArtifactVersion; -import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.ThreadContext; +import org.apache.logging.log4j.message.FormattedMessage; +import com.google.common.base.Throwables; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; @@ -53,9 +53,8 @@ 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.FMLThrowingEventBus; import com.google.common.eventbus.Subscribe; -import com.google.common.eventbus.SubscriberExceptionHandler; -import com.google.common.eventbus.SubscriberExceptionContext; import javax.annotation.Nullable; @@ -63,11 +62,9 @@ public class LoadController { private Loader loader; private EventBus masterChannel; - private ImmutableMap eventChannels; + private ImmutableMap eventChannels; private LoaderState state; private Multimap modStates = ArrayListMultimap.create(); - private Multimap errors = ArrayListMultimap.create(); - private Map modNames = Maps.newHashMap(); private List activeModList = Lists.newArrayList(); private ModContainer activeContainer; private BiMap modObjectList; @@ -76,13 +73,13 @@ public class LoadController public LoadController(Loader loader) { this.loader = loader; - this.masterChannel = new EventBus(new SubscriberExceptionHandler() - { - @Override - public void handleException(Throwable exception, SubscriberExceptionContext context) - { - FMLLog.log.error("Could not dispatch event: {} to {}", context.getSubscriberMethod(), exception); - } + this.masterChannel = new FMLThrowingEventBus((exception, context) -> { + Throwables.throwIfUnchecked(exception); + // should not happen, but add some extra context for checked exceptions + Method method = context.getSubscriberMethod(); + String parameterNames = Stream.of(method.getParameterTypes()).map(Class::getName).collect(Collectors.joining(", ")); + String message = "Exception thrown during LoadController." + method.getName() + '(' + parameterNames + ')'; + throw new LoaderExceptionModCrash(message, exception); }); this.masterChannel.register(this); @@ -97,14 +94,12 @@ public class LoadController 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); - } + eventChannels = ImmutableMap.copyOf(temporary); + modStates.put(modId, ModState.DISABLED); + modObjectList.remove(mod); + activeModList.remove(mod); } + @Subscribe public void buildModList(FMLLoadEvent event) { @@ -112,15 +107,7 @@ public class LoadController for (final ModContainer mod : loader.getModList()) { - //Create mod logger, and make the EventBus logger a child of it. - EventBus bus = new EventBus(new SubscriberExceptionHandler() - { - @Override - public void handleException(final Throwable exception, final SubscriberExceptionContext context) - { - LoadController.this.errorOccurred(mod, exception); - } - }); + EventBus bus = new FMLThrowingEventBus((exception, context) -> this.errorOccurred(mod, exception)); boolean isActive = mod.registerBus(bus, this); if (isActive) @@ -136,7 +123,6 @@ public class LoadController modStates.put(mod.getModId(), ModState.UNLOADED); modStates.put(mod.getModId(), ModState.DISABLED); } - modNames.put(mod.getModId(), mod.getName()); } eventChannels = eventBus.build(); @@ -159,13 +145,13 @@ public class LoadController } LoaderState oldState = state; - state = state.transition(!errors.isEmpty()); + state = state.transition(false); if (state != desiredState) { if (!forceState) { - FMLLog.log.fatal("Fatal errors were detected during the transition from {} to {}. Loading cannot continue", oldState, desiredState); - throw throwStoredErrors(); + FormattedMessage message = new FormattedMessage("A fatal error occurred during the state transition from {} to {}. State became {} instead. Loading cannot continue.", oldState, desiredState, state); + throw new LoaderException(message.getFormattedMessage()); } else { @@ -178,60 +164,11 @@ public class LoadController @Deprecated // TODO remove in 1.13 public void checkErrorsAfterAvailable() { - checkErrors(); } + @Deprecated // TODO remove in 1.13 public void checkErrors() { - if (errors.size() > 0) - { - FMLLog.log.fatal("Fatal errors were detected during {}. Loading cannot continue.", state); - MinecraftForge.EVENT_BUS.shutdown(); - state = state.transition(true); - throw throwStoredErrors(); - } - } - - private RuntimeException throwStoredErrors() - { - Entry toThrow = null; - StringBuilder sb = new StringBuilder(); - printModStates(sb); - FMLLog.log.fatal(sb.toString()); - if (errors.size() > 0) - { - FMLLog.log.fatal("The following problems were captured during this phase"); - for (Entry entry : errors.entries()) - { - String modId = entry.getKey(); - String modName = modNames.get(modId); - Throwable error = entry.getValue(); - FMLLog.log.error("Caught exception from {} ({})", modId, modName, error); - if (error instanceof IFMLHandledException) - { - toThrow = entry; - } - else if (toThrow == null) - { - toThrow = entry; - } - } - } - - if (toThrow == null) - { - FMLLog.log.fatal("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"); - } - else - { - String modId = toThrow.getKey(); - String modName = modNames.get(modId); - String errMsg = String.format("Caught exception from %s (%s)", modName, modId); - throw new LoaderExceptionModCrash(errMsg, toThrow.getValue()); - } } @Nullable @@ -244,6 +181,7 @@ public class LoadController { activeContainer = container; } + @Subscribe public void propogateStateMessage(FMLEvent stateEvent) { @@ -263,10 +201,10 @@ public class LoadController private void sendEventToModContainer(FMLEvent stateEvent, ModContainer mc) { String modId = mc.getModId(); - Collection requirements = mc.getRequirements().stream().map(ArtifactVersion::getLabel).collect(Collectors.toCollection(HashSet::new)); + Collection requirements = mc.getRequirements().stream().map(ArtifactVersion::getLabel).collect(Collectors.toCollection(HashSet::new)); for (ArtifactVersion av : mc.getDependencies()) { - if (av.getLabel()!= null && requirements.contains(av.getLabel()) && modStates.containsEntry(av.getLabel(),ModState.ERRORED)) + if (av.getLabel() != null && requirements.contains(av.getLabel()) && modStates.containsEntry(av.getLabel(), ModState.ERRORED)) { FMLLog.log.error("Skipping event {} and marking errored mod {} since required dependency {} has errored", stateEvent.getEventType(), modId, av.getLabel()); modStates.put(modId, ModState.ERRORED); @@ -283,14 +221,7 @@ public class LoadController activeContainer = null; if (stateEvent instanceof FMLStateEvent) { - if (!errors.containsKey(modId)) - { - modStates.put(modId, ((FMLStateEvent)stateEvent).getModState()); - } - else - { - modStates.put(modId, ModState.ERRORED); - } + modStates.put(modId, ((FMLStateEvent) stateEvent).getModState()); } } @@ -299,7 +230,7 @@ public class LoadController ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); for (ModContainer mc : activeModList) { - if (!mc.isImmutable() && mc.getMod()!=null) + if (!mc.isImmutable() && mc.getMod() != null) { builder.put(mc, mc.getMod()); List packages = mc.getOwnedPackages(); @@ -308,13 +239,10 @@ public class LoadController packageOwners.put(pkg, mc); } } - if (mc.getMod()==null && !mc.isImmutable() && state!=LoaderState.CONSTRUCTING) + if (mc.getMod() == null && !mc.isImmutable() && state != LoaderState.CONSTRUCTING) { - FMLLog.log.fatal("There is a severe problem with {} - it appears not to have constructed correctly", mc.getModId()); - if (state != LoaderState.CONSTRUCTING) - { - this.errorOccurred(mc, new RuntimeException()); - } + FormattedMessage message = new FormattedMessage("There is a severe problem with {} ({}) - it appears not to have constructed correctly", mc.getName(), mc.getModId()); + this.errorOccurred(mc, new RuntimeException(message.getFormattedMessage())); } } return builder.build(); @@ -322,14 +250,19 @@ public class LoadController public void errorOccurred(ModContainer modContainer, Throwable exception) { + String modId = modContainer.getModId(); + String modName = modContainer.getName(); + modStates.put(modId, ModState.ERRORED); if (exception instanceof InvocationTargetException) { - errors.put(modContainer.getModId(), exception.getCause()); + exception = exception.getCause(); } - else + if (exception instanceof LoaderException) // avoid wrapping loader exceptions multiple times { - errors.put(modContainer.getModId(), exception); + throw (LoaderException) exception; } + FormattedMessage message = new FormattedMessage("Caught exception from {} ({})", modName, modId); + throw new LoaderExceptionModCrash(message.getFormattedMessage(), exception); } public void printModStates(StringBuilder ret) @@ -374,15 +307,16 @@ public class LoadController public void distributeStateMessage(Class customEvent) { + Object eventInstance; try { - masterChannel.post(customEvent.newInstance()); + eventInstance = customEvent.newInstance(); } - catch (Exception e) + catch (InstantiationException | IllegalAccessException e) { - FMLLog.log.error("An unexpected exception", e); - throw new LoaderException(e); + throw new LoaderException("Failed to create new event instance for " + customEvent.getName(), e); } + masterChannel.post(eventInstance); } public BiMap getModObjectList() @@ -400,8 +334,9 @@ public class LoadController return this.state == state; } - boolean hasReachedState(LoaderState state) { - return this.state.ordinal()>=state.ordinal() && this.state!=LoaderState.ERRORED; + boolean hasReachedState(LoaderState state) + { + return this.state.ordinal() >= state.ordinal() && this.state != LoaderState.ERRORED; } void forceState(LoaderState newState) @@ -419,7 +354,7 @@ public class LoadController { continue; } - String pkg = c.getName().substring(0,idx); + String pkg = c.getName().substring(0, idx); if (packageOwners.containsKey(pkg)) { return packageOwners.get(pkg).get(0); @@ -428,6 +363,7 @@ public class LoadController return null; } + private FMLSecurityManager accessibleManager = new FMLSecurityManager(); class FMLSecurityManager extends SecurityManager diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index c5e7b6995..8daa93ed5 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -547,9 +547,10 @@ public class Loader ObjectHolderRegistry.INSTANCE.findObjectHolders(new ASMDataTable()); modController.forceActiveContainer(containers[0]); } + /** * Called from the hook to start mod loading. We trigger the - * {@link #identifyMods()} and Constructing, Preinitalization, and Initalization phases here. Finally, + * {@link #identifyMods(List)} and Constructing, Preinitalization, and Initalization phases here. Finally, * the mod list is frozen completely and is consider immutable from then on. * @param injectedModContainers containers to inject */ @@ -624,7 +625,6 @@ public class Loader ItemStackHolderInjector.INSTANCE.findHolders(discoverer.getASMTable()); CapabilityManager.INSTANCE.injectCapabilities(discoverer.getASMTable()); modController.distributeStateMessage(LoaderState.PREINITIALIZATION, discoverer.getASMTable(), canonicalConfigDir); - modController.checkErrors(); GameData.fireRegistryEvents(rl -> !rl.equals(GameData.RECIPES)); FMLCommonHandler.instance().fireSidedRegistryEvents(); ObjectHolderRegistry.INSTANCE.applyObjectHolders(); @@ -750,7 +750,6 @@ public class Loader progressBar.step("Finishing up"); modController.transition(LoaderState.AVAILABLE, false); modController.distributeStateMessage(LoaderState.AVAILABLE); - modController.checkErrors(); GameData.freezeData(); FMLLog.log.info("Forge Mod Loader has successfully loaded {} mod{}", mods.size(), mods.size() == 1 ? "" : "s"); progressBar.step("Completing Minecraft initialization"); diff --git a/src/main/java/net/minecraftforge/fml/common/MissingModsException.java b/src/main/java/net/minecraftforge/fml/common/MissingModsException.java index c70ee6903..2bf455b10 100644 --- a/src/main/java/net/minecraftforge/fml/common/MissingModsException.java +++ b/src/main/java/net/minecraftforge/fml/common/MissingModsException.java @@ -28,9 +28,15 @@ import java.util.Set; import java.util.stream.Collectors; import com.google.common.base.Preconditions; -import net.minecraftforge.fml.common.versioning.ArtifactVersion; -public class MissingModsException extends EnhancedRuntimeException +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.fml.client.GuiModsMissing; +import net.minecraftforge.fml.client.IDisplayableError; +import net.minecraftforge.fml.common.versioning.ArtifactVersion; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +public class MissingModsException extends EnhancedRuntimeException implements IDisplayableError { private static final long serialVersionUID = 1L; private final String id; @@ -101,6 +107,13 @@ public class MissingModsException extends EnhancedRuntimeException stream.println(""); } + @Override + @SideOnly(Side.CLIENT) + public GuiScreen createGui() + { + return new GuiModsMissing(this); + } + public static class MissingModInfo { private final ArtifactVersion acceptedVersion; @@ -132,4 +145,5 @@ public class MissingModsException extends EnhancedRuntimeException return required; } } + } diff --git a/src/main/java/net/minecraftforge/fml/common/MultipleModsErrored.java b/src/main/java/net/minecraftforge/fml/common/MultipleModsErrored.java index 470a9d2b6..7af6836fb 100644 --- a/src/main/java/net/minecraftforge/fml/common/MultipleModsErrored.java +++ b/src/main/java/net/minecraftforge/fml/common/MultipleModsErrored.java @@ -21,7 +21,13 @@ package net.minecraftforge.fml.common; import java.util.List; -public class MultipleModsErrored extends EnhancedRuntimeException +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.fml.client.GuiMultipleModsErrored; +import net.minecraftforge.fml.client.IDisplayableError; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +public class MultipleModsErrored extends EnhancedRuntimeException implements IDisplayableError { public final List wrongMinecraftExceptions; public final List missingModsExceptions; @@ -31,6 +37,13 @@ public class MultipleModsErrored extends EnhancedRuntimeException this.missingModsExceptions = missingModsExceptions; } + @Override + @SideOnly(Side.CLIENT) + public GuiScreen createGui() + { + return new GuiMultipleModsErrored(this); + } + @Override protected void printStackTrace(WrappedPrintStream stream) { diff --git a/src/main/java/net/minecraftforge/fml/common/WrongMinecraftVersionException.java b/src/main/java/net/minecraftforge/fml/common/WrongMinecraftVersionException.java index 9aac5381b..7da699bd2 100644 --- a/src/main/java/net/minecraftforge/fml/common/WrongMinecraftVersionException.java +++ b/src/main/java/net/minecraftforge/fml/common/WrongMinecraftVersionException.java @@ -19,7 +19,13 @@ package net.minecraftforge.fml.common; -public class WrongMinecraftVersionException extends EnhancedRuntimeException +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.fml.client.GuiWrongMinecraft; +import net.minecraftforge.fml.client.IDisplayableError; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +public class WrongMinecraftVersionException extends EnhancedRuntimeException implements IDisplayableError { private static final long serialVersionUID = 1L; public ModContainer mod; @@ -42,4 +48,10 @@ public class WrongMinecraftVersionException extends EnhancedRuntimeException stream.println(""); } + @Override + @SideOnly(Side.CLIENT) + public GuiScreen createGui() + { + return new GuiWrongMinecraft(this); + } } diff --git a/src/main/java/net/minecraftforge/fml/common/eventhandler/FMLThrowingEventBus.java b/src/main/java/net/minecraftforge/fml/common/eventhandler/FMLThrowingEventBus.java new file mode 100644 index 000000000..ef9450b4b --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/common/eventhandler/FMLThrowingEventBus.java @@ -0,0 +1,52 @@ +/* + * Minecraft Forge + * Copyright (c) 2016. + * + * 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 version 2.1 + * of the License. + * + * 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.eventhandler; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.SubscriberExceptionHandler; + +/** + * Event bus that allows exceptions thrown by the exception handler to propagate. + * TODO remove this in 1.13 and stop using the guava event bus + */ +public class FMLThrowingEventBus extends EventBus +{ + public FMLThrowingEventBus(final SubscriberExceptionHandler exceptionHandler) + { + super((exception, context) -> { + try + { + exceptionHandler.handleException(exception, context); + } + catch (final Throwable t) + { + // this is a hack to break out of the catch statement in EventBus.handleSubscriberException + throw new RuntimeException() + { + @Override + public String toString() + { + throw t; + } + }; + } + }); + } +} diff --git a/src/main/java/net/minecraftforge/fml/common/toposort/ModSortingException.java b/src/main/java/net/minecraftforge/fml/common/toposort/ModSortingException.java index e53b51445..412ef0b9e 100644 --- a/src/main/java/net/minecraftforge/fml/common/toposort/ModSortingException.java +++ b/src/main/java/net/minecraftforge/fml/common/toposort/ModSortingException.java @@ -21,10 +21,15 @@ package net.minecraftforge.fml.common.toposort; import java.util.Set; +import net.minecraft.client.gui.GuiScreen; +import net.minecraftforge.fml.client.GuiSortingProblem; +import net.minecraftforge.fml.client.IDisplayableError; import net.minecraftforge.fml.common.EnhancedRuntimeException; import net.minecraftforge.fml.common.ModContainer; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; -public class ModSortingException extends EnhancedRuntimeException +public class ModSortingException extends EnhancedRuntimeException implements IDisplayableError { private static final long serialVersionUID = 1L; @@ -76,4 +81,11 @@ public class ModSortingException extends EnhancedRuntimeException } } + + @Override + @SideOnly(Side.CLIENT) + public GuiScreen createGui() + { + return new GuiSortingProblem(this); + } }