Give immediate and complete error messages when there is a crash during startup (#4869)

This commit is contained in:
mezz 2018-05-12 16:56:57 -07:00 committed by GitHub
parent e65bd4a62b
commit a38f5fd6a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 301 additions and 256 deletions

View file

@ -0,0 +1,44 @@
/*
* 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 com.google.common.eventbus;
import com.google.common.base.Preconditions;
/**
* 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
{
private final SubscriberExceptionHandler exceptionHandler;
public FMLThrowingEventBus(SubscriberExceptionHandler exceptionHandler)
{
this.exceptionHandler = Preconditions.checkNotNull(exceptionHandler);
}
@Override
void handleSubscriberException(Throwable e, SubscriberExceptionContext context)
{
Preconditions.checkNotNull(e);
Preconditions.checkNotNull(context);
exceptionHandler.handleException(e, context);
}
}

View file

@ -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);
}
}

View file

@ -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<IResourcePack> 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<String,Map<String,String>> sharedModList = (Map<String, Map<String, String>>) 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();
}

View file

@ -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();
}

View file

@ -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<ModContainer,File> dupes;
@ -42,4 +48,11 @@ public class DuplicateModsFoundException extends LoaderException {
}
stream.println("");
}
@Override
@SideOnly(Side.CLIENT)
public GuiScreen createGui()
{
return new GuiDupesFound(this);
}
}

View file

@ -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<? extends FMLEvent>)m.getParameterTypes()[0], m);
@SuppressWarnings("unchecked")
Class<? extends FMLEvent> parameterType = (Class<? extends FMLEvent>) 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<String, ASMData> 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<String> certList = CertificateHelper.getFingerprints(certificates);
sourceFingerprints = ImmutableSet.copyOf(certList);
Certificate[] certificates = clazz.getProtectionDomain().getCodeSource().getCertificates();
ImmutableList<String> 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<Map<String, Object>> props = (List<Map<String, Object>>)descriptor.get("customProperties");
if (props != null)
{
com.google.common.collect.ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for (Map<String, Object> 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<Map<String, String>> props = (List<Map<String, String>>)descriptor.get("customProperties");
if (props != null)
{
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for (Map<String, String> 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);
}
}

View file

@ -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<String,EventBus> eventChannels;
private ImmutableMap<String, EventBus> eventChannels;
private LoaderState state;
private Multimap<String, ModState> modStates = ArrayListMultimap.create();
private Multimap<String, Throwable> errors = ArrayListMultimap.create();
private Map<String, String> modNames = Maps.newHashMap();
private List<ModContainer> activeModList = Lists.newArrayList();
private ModContainer activeContainer;
private BiMap<ModContainer, Object> 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<String, Throwable> 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<String, Throwable> 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<String> requirements = mc.getRequirements().stream().map(ArtifactVersion::getLabel).collect(Collectors.toCollection(HashSet::new));
Collection<String> 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<ModContainer, Object> 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<String> 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<ModContainer, Object> 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

View file

@ -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");

View file

@ -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;
}
}
}

View file

@ -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<WrongMinecraftVersionException> wrongMinecraftExceptions;
public final List<MissingModsException> 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)
{

View file

@ -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);
}
}

View file

@ -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);
}
}