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

This commit is contained in:
mezz 2018-05-12 22:39:42 -07:00
parent ad099a4bfe
commit 979797a2a6
12 changed files with 309 additions and 256 deletions

View File

@ -21,6 +21,7 @@ package net.minecraftforge.fml.client;
import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.GuiErrorScreen; import net.minecraft.client.gui.GuiErrorScreen;
import net.minecraft.client.gui.GuiScreen;
import net.minecraftforge.fml.common.EnhancedRuntimeException; import net.minecraftforge.fml.common.EnhancedRuntimeException;
import net.minecraftforge.fml.common.IFMLHandledException; import net.minecraftforge.fml.common.IFMLHandledException;
import net.minecraftforge.fml.relauncher.Side; import net.minecraftforge.fml.relauncher.Side;
@ -37,7 +38,7 @@ import net.minecraftforge.fml.relauncher.SideOnly;
* *
*/ */
@SideOnly(Side.CLIENT) @SideOnly(Side.CLIENT)
public abstract class CustomModLoadingErrorDisplayException extends EnhancedRuntimeException implements IFMLHandledException public abstract class CustomModLoadingErrorDisplayException extends EnhancedRuntimeException implements IFMLHandledException, IDisplayableError
{ {
public CustomModLoadingErrorDisplayException() { 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); 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 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.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.FormattedMessage;
import org.lwjgl.LWJGLUtil; import org.lwjgl.LWJGLUtil;
import org.lwjgl.input.Mouse; import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display; import org.lwjgl.opengl.Display;
@ -171,19 +172,13 @@ public class FMLClientHandler implements IFMLSidedHandler
private DummyModContainer optifineContainer; 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 MissingModsException modsMissing;
private ModSortingException modSorting;
private boolean loading = true; private boolean loading = true;
private WrongMinecraftVersionException wrongMC; @Nullable
private IDisplayableError errorToDisplay;
private CustomModLoadingErrorDisplayException customError;
private DuplicateModsFoundException dupesFound;
private MultipleModsErrored multipleModsErrored;
private boolean serverShouldBeKilledQuietly; private boolean serverShouldBeKilledQuietly;
@ -209,7 +204,6 @@ public class FMLClientHandler implements IFMLSidedHandler
* @param resourcePackList The resource pack list we will populate with mods * @param resourcePackList The resource pack list we will populate with mods
* @param resourceManager The resource manager * @param resourceManager The resource manager
*/ */
@SuppressWarnings("unchecked")
public void beginMinecraftLoading(Minecraft minecraft, List<IResourcePack> resourcePackList, IReloadableResourceManager resourceManager, MetadataSerializer metaSerializer) public void beginMinecraftLoading(Minecraft minecraft, List<IResourcePack> resourcePackList, IReloadableResourceManager resourceManager, MetadataSerializer metaSerializer)
{ {
detectOptifine(); detectOptifine();
@ -230,30 +224,11 @@ public class FMLClientHandler implements IFMLSidedHandler
{ {
Loader.instance().loadMods(injectedModContainers); Loader.instance().loadMods(injectedModContainers);
} }
catch (WrongMinecraftVersionException wrong) catch (WrongMinecraftVersionException | DuplicateModsFoundException | MissingModsException | ModSortingException | CustomModLoadingErrorDisplayException | MultipleModsErrored e)
{ {
wrongMC = wrong; FMLLog.log.error("An exception was thrown, the game will display an error screen and halt.", e);
} errorToDisplay = e;
catch (DuplicateModsFoundException dupes) MinecraftForge.EVENT_BUS.shutdown();
{
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;
} }
catch (LoaderException le) catch (LoaderException le)
{ {
@ -274,8 +249,9 @@ public class FMLClientHandler implements IFMLSidedHandler
if (le.getCause() instanceof CustomModLoadingErrorDisplayException) if (le.getCause() instanceof CustomModLoadingErrorDisplayException)
{ {
CustomModLoadingErrorDisplayException custom = (CustomModLoadingErrorDisplayException) le.getCause(); CustomModLoadingErrorDisplayException custom = (CustomModLoadingErrorDisplayException) le.getCause();
FMLLog.log.error("A custom exception was thrown by a mod, the game will now halt", custom); FMLLog.log.error("A custom exception was thrown by a mod, the game will display an error screen and halt.", custom);
customError = custom; errorToDisplay = custom;
MinecraftForge.EVENT_BUS.shutdown();
} }
else 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"); Map<String,Map<String,String>> sharedModList = (Map<String, Map<String, String>>) Launch.blackboard.get("modList");
if (sharedModList == null) if (sharedModList == null)
{ {
@ -332,7 +309,7 @@ public class FMLClientHandler implements IFMLSidedHandler
public boolean hasError() 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) if (le.getCause() instanceof CustomModLoadingErrorDisplayException)
{ {
CustomModLoadingErrorDisplayException custom = (CustomModLoadingErrorDisplayException) le.getCause(); CustomModLoadingErrorDisplayException custom = (CustomModLoadingErrorDisplayException) le.getCause();
FMLLog.log.error("A custom exception was thrown by a mod, the game will now halt", custom); FMLLog.log.error("A custom exception was thrown by a mod, the game will display an error screen and halt.", custom);
customError = custom; errorToDisplay = custom;
MinecraftForge.EVENT_BUS.shutdown();
} }
else else
{ {
@ -369,6 +347,7 @@ public class FMLClientHandler implements IFMLSidedHandler
// This call is being phased out for performance reasons in 1.12, // 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. // but we are keeping an option here in case something needs it for a little longer.
// See https://github.com/MinecraftForge/MinecraftForge/pull/4032 // See https://github.com/MinecraftForge/MinecraftForge/pull/4032
// TODO remove in 1.13
if (Boolean.parseBoolean(System.getProperty("fml.reloadResourcesOnStart", "false"))) if (Boolean.parseBoolean(System.getProperty("fml.reloadResourcesOnStart", "false")))
{ {
client.refreshResources(); client.refreshResources();
@ -401,7 +380,7 @@ public class FMLClientHandler implements IFMLSidedHandler
} }
loading = false; loading = false;
client.gameSettings.loadOptions(); //Reload options to load any mod added keybindings. client.gameSettings.loadOptions(); //Reload options to load any mod added keybindings.
if (customError == null) if (!hasError())
Loader.instance().loadingComplete(); Loader.instance().loadingComplete();
SplashProgress.finish(); SplashProgress.finish();
} }
@ -439,29 +418,10 @@ public class FMLClientHandler implements IFMLSidedHandler
// re-sync TEXTURE_2D, splash screen disables it with a direct GL call // re-sync TEXTURE_2D, splash screen disables it with a direct GL call
GlStateManager.disableTexture2D(); GlStateManager.disableTexture2D();
GlStateManager.enableTexture2D(); GlStateManager.enableTexture2D();
if (wrongMC != null) if (errorToDisplay != null)
{ {
showGuiScreen(new GuiWrongMinecraft(wrongMC)); GuiScreen errorScreen = errorToDisplay.createGui();
} showGuiScreen(errorScreen);
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));
} }
else else
{ {
@ -616,6 +576,10 @@ public class FMLClientHandler implements IFMLSidedHandler
return client.getIntegratedServer(); return client.getIntegratedServer();
} }
/**
* TODO remove in 1.13
*/
@Deprecated
public void displayMissingMods(Object modMissingPacket) public void displayMissingMods(Object modMissingPacket)
{ {
// showGuiScreen(new GuiModsMissingForServer(modMissingPacket)); // showGuiScreen(new GuiModsMissingForServer(modMissingPacket));
@ -679,7 +643,8 @@ public class FMLClientHandler implements IFMLSidedHandler
} }
catch (Exception e) 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 @Override
public void serverStopped() 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(); 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 java.util.Map.Entry;
import com.google.common.collect.SetMultimap; 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; private static final long serialVersionUID = 1L;
public SetMultimap<ModContainer,File> dupes; public SetMultimap<ModContainer,File> dupes;
@ -42,4 +48,11 @@ public class DuplicateModsFoundException extends LoaderException {
} }
stream.println(""); 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.Properties;
import java.util.Set; import java.util.Set;
import com.google.common.base.Throwables;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Config; import net.minecraftforge.common.config.Config;
import net.minecraftforge.common.config.ConfigManager; 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.collect.SetMultimap;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import org.apache.logging.log4j.message.FormattedMessage;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -380,8 +382,7 @@ public class FMLModContainer implements ModContainer
} }
@Nullable @Nullable
@SuppressWarnings("unchecked") private Method gatherAnnotations(Class<?> clazz)
private Method gatherAnnotations(Class<?> clazz) throws Exception
{ {
Method factoryMethod = null; Method factoryMethod = null;
for (Method m : clazz.getDeclaredMethods()) 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])) if (m.getParameterTypes().length == 1 && FMLEvent.class.isAssignableFrom(m.getParameterTypes()[0]))
{ {
m.setAccessible(true); 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 else
{ {
@ -421,7 +424,7 @@ public class FMLModContainer implements ModContainer
return factoryMethod; return factoryMethod;
} }
private void processFieldAnnotations(ASMDataTable asmDataTable) throws Exception private void processFieldAnnotations(ASMDataTable asmDataTable) throws IllegalAccessException
{ {
SetMultimap<String, ASMData> annotations = asmDataTable.getAnnotationsFor(this); SetMultimap<String, ASMData> annotations = asmDataTable.getAnnotationsFor(this);
@ -503,86 +506,110 @@ public class FMLModContainer implements ModContainer
@Subscribe @Subscribe
public void constructMod(FMLConstructionEvent event) public void constructMod(FMLConstructionEvent event)
{ {
BlamingTransformer.addClasses(getModId(), candidate.getClassList());
ModClassLoader modClassLoader = event.getModClassLoader();
try try
{ {
BlamingTransformer.addClasses(getModId(), candidate.getClassList());
ModClassLoader modClassLoader = event.getModClassLoader();
modClassLoader.addFile(source); 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... //Only place I could think to add this...
MinecraftForge.preloadCrashClasses(event.getASMHarvestedData(), getModId(), candidate.getClassList()); 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(); Certificate[] certificates = clazz.getProtectionDomain().getCodeSource().getCertificates();
ImmutableList<String> certList = CertificateHelper.getFingerprints(certificates); ImmutableList<String> certList = CertificateHelper.getFingerprints(certificates);
sourceFingerprints = ImmutableSet.copyOf(certList); 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 = 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());
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();
} }
else else
{ {
customModProperties = EMPTY_PROPERTIES; certificate = certificates[certList.indexOf(expectedFingerprint)];
fingerprintNotPresent = false;
} }
}
Boolean hasDisableableFlag = (Boolean)descriptor.get("canBeDeactivated"); @SuppressWarnings("unchecked")
boolean hasReverseDepends = !event.getReverseDependencies().get(getModId()).isEmpty(); List<Map<String, String>> props = (List<Map<String, String>>)descriptor.get("customProperties");
if (hasDisableableFlag != null && hasDisableableFlag) 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 customModProperties = builder.build();
{ }
disableability = hasReverseDepends ? Disableable.DEPENDENCIES : Disableable.RESTART; else
} {
Method factoryMethod = gatherAnnotations(clazz); customModProperties = EMPTY_PROPERTIES;
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);
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()); 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; package net.minecraftforge.fml.common;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.TextTable; import net.minecraftforge.common.util.TextTable;
import net.minecraftforge.fml.common.LoaderState.ModState; import net.minecraftforge.fml.common.LoaderState.ModState;
import net.minecraftforge.fml.common.ProgressManager.ProgressBar; 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.event.FMLStateEvent;
import net.minecraftforge.fml.common.versioning.ArtifactVersion; import net.minecraftforge.fml.common.versioning.ArtifactVersion;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.ThreadContext; 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.ArrayListMultimap;
import com.google.common.collect.BiMap; import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap; 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.Maps;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.FMLThrowingEventBus;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import com.google.common.eventbus.SubscriberExceptionHandler;
import com.google.common.eventbus.SubscriberExceptionContext;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -63,11 +62,9 @@ public class LoadController
{ {
private Loader loader; private Loader loader;
private EventBus masterChannel; private EventBus masterChannel;
private ImmutableMap<String,EventBus> eventChannels; private ImmutableMap<String, EventBus> eventChannels;
private LoaderState state; private LoaderState state;
private Multimap<String, ModState> modStates = ArrayListMultimap.create(); 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 List<ModContainer> activeModList = Lists.newArrayList();
private ModContainer activeContainer; private ModContainer activeContainer;
private BiMap<ModContainer, Object> modObjectList; private BiMap<ModContainer, Object> modObjectList;
@ -76,13 +73,13 @@ public class LoadController
public LoadController(Loader loader) public LoadController(Loader loader)
{ {
this.loader = loader; this.loader = loader;
this.masterChannel = new EventBus(new SubscriberExceptionHandler() this.masterChannel = new FMLThrowingEventBus((exception, context) -> {
{ Throwables.throwIfUnchecked(exception);
@Override // should not happen, but add some extra context for checked exceptions
public void handleException(Throwable exception, SubscriberExceptionContext context) Method method = context.getSubscriberMethod();
{ String parameterNames = Stream.of(method.getParameterTypes()).map(Class::getName).collect(Collectors.joining(", "));
FMLLog.log.error("Could not dispatch event: {} to {}", context.getSubscriberMethod(), exception); String message = "Exception thrown during LoadController." + method.getName() + '(' + parameterNames + ')';
} throw new LoaderExceptionModCrash(message, exception);
}); });
this.masterChannel.register(this); this.masterChannel.register(this);
@ -97,14 +94,12 @@ public class LoadController
String modId = mod.getModId(); String modId = mod.getModId();
EventBus bus = temporary.remove(modId); EventBus bus = temporary.remove(modId);
bus.post(new FMLModDisabledEvent()); bus.post(new FMLModDisabledEvent());
if (errors.get(modId).isEmpty()) eventChannels = ImmutableMap.copyOf(temporary);
{ modStates.put(modId, ModState.DISABLED);
eventChannels = ImmutableMap.copyOf(temporary); modObjectList.remove(mod);
modStates.put(modId, ModState.DISABLED); activeModList.remove(mod);
modObjectList.remove(mod);
activeModList.remove(mod);
}
} }
@Subscribe @Subscribe
public void buildModList(FMLLoadEvent event) public void buildModList(FMLLoadEvent event)
{ {
@ -112,15 +107,7 @@ public class LoadController
for (final ModContainer mod : loader.getModList()) for (final ModContainer mod : loader.getModList())
{ {
//Create mod logger, and make the EventBus logger a child of it. EventBus bus = new FMLThrowingEventBus((exception, context) -> this.errorOccurred(mod, exception));
EventBus bus = new EventBus(new SubscriberExceptionHandler()
{
@Override
public void handleException(final Throwable exception, final SubscriberExceptionContext context)
{
LoadController.this.errorOccurred(mod, exception);
}
});
boolean isActive = mod.registerBus(bus, this); boolean isActive = mod.registerBus(bus, this);
if (isActive) if (isActive)
@ -136,7 +123,6 @@ public class LoadController
modStates.put(mod.getModId(), ModState.UNLOADED); modStates.put(mod.getModId(), ModState.UNLOADED);
modStates.put(mod.getModId(), ModState.DISABLED); modStates.put(mod.getModId(), ModState.DISABLED);
} }
modNames.put(mod.getModId(), mod.getName());
} }
eventChannels = eventBus.build(); eventChannels = eventBus.build();
@ -159,13 +145,13 @@ public class LoadController
} }
LoaderState oldState = state; LoaderState oldState = state;
state = state.transition(!errors.isEmpty()); state = state.transition(false);
if (state != desiredState) if (state != desiredState)
{ {
if (!forceState) if (!forceState)
{ {
FMLLog.log.fatal("Fatal errors were detected during the transition from {} to {}. Loading cannot continue", oldState, desiredState); FormattedMessage message = new FormattedMessage("A fatal error occurred during the state transition from {} to {}. State became {} instead. Loading cannot continue.", oldState, desiredState, state);
throw throwStoredErrors(); throw new LoaderException(message.getFormattedMessage());
} }
else else
{ {
@ -178,60 +164,11 @@ public class LoadController
@Deprecated // TODO remove in 1.13 @Deprecated // TODO remove in 1.13
public void checkErrorsAfterAvailable() public void checkErrorsAfterAvailable()
{ {
checkErrors();
} }
@Deprecated // TODO remove in 1.13
public void checkErrors() 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 @Nullable
@ -244,6 +181,7 @@ public class LoadController
{ {
activeContainer = container; activeContainer = container;
} }
@Subscribe @Subscribe
public void propogateStateMessage(FMLEvent stateEvent) public void propogateStateMessage(FMLEvent stateEvent)
{ {
@ -263,10 +201,10 @@ public class LoadController
private void sendEventToModContainer(FMLEvent stateEvent, ModContainer mc) private void sendEventToModContainer(FMLEvent stateEvent, ModContainer mc)
{ {
String modId = mc.getModId(); 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()) 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()); FMLLog.log.error("Skipping event {} and marking errored mod {} since required dependency {} has errored", stateEvent.getEventType(), modId, av.getLabel());
modStates.put(modId, ModState.ERRORED); modStates.put(modId, ModState.ERRORED);
@ -283,14 +221,7 @@ public class LoadController
activeContainer = null; activeContainer = null;
if (stateEvent instanceof FMLStateEvent) if (stateEvent instanceof FMLStateEvent)
{ {
if (!errors.containsKey(modId)) modStates.put(modId, ((FMLStateEvent) stateEvent).getModState());
{
modStates.put(modId, ((FMLStateEvent)stateEvent).getModState());
}
else
{
modStates.put(modId, ModState.ERRORED);
}
} }
} }
@ -299,7 +230,7 @@ public class LoadController
ImmutableBiMap.Builder<ModContainer, Object> builder = ImmutableBiMap.builder(); ImmutableBiMap.Builder<ModContainer, Object> builder = ImmutableBiMap.builder();
for (ModContainer mc : activeModList) for (ModContainer mc : activeModList)
{ {
if (!mc.isImmutable() && mc.getMod()!=null) if (!mc.isImmutable() && mc.getMod() != null)
{ {
builder.put(mc, mc.getMod()); builder.put(mc, mc.getMod());
List<String> packages = mc.getOwnedPackages(); List<String> packages = mc.getOwnedPackages();
@ -308,13 +239,10 @@ public class LoadController
packageOwners.put(pkg, mc); 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()); FormattedMessage message = new FormattedMessage("There is a severe problem with {} ({}) - it appears not to have constructed correctly", mc.getName(), mc.getModId());
if (state != LoaderState.CONSTRUCTING) this.errorOccurred(mc, new RuntimeException(message.getFormattedMessage()));
{
this.errorOccurred(mc, new RuntimeException());
}
} }
} }
return builder.build(); return builder.build();
@ -322,14 +250,19 @@ public class LoadController
public void errorOccurred(ModContainer modContainer, Throwable exception) public void errorOccurred(ModContainer modContainer, Throwable exception)
{ {
String modId = modContainer.getModId();
String modName = modContainer.getName();
modStates.put(modId, ModState.ERRORED);
if (exception instanceof InvocationTargetException) 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) public void printModStates(StringBuilder ret)
@ -374,15 +307,16 @@ public class LoadController
public void distributeStateMessage(Class<?> customEvent) public void distributeStateMessage(Class<?> customEvent)
{ {
Object eventInstance;
try 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("Failed to create new event instance for " + customEvent.getName(), e);
throw new LoaderException(e);
} }
masterChannel.post(eventInstance);
} }
public BiMap<ModContainer, Object> getModObjectList() public BiMap<ModContainer, Object> getModObjectList()
@ -400,8 +334,9 @@ public class LoadController
return this.state == state; return this.state == state;
} }
boolean hasReachedState(LoaderState state) { boolean hasReachedState(LoaderState state)
return this.state.ordinal()>=state.ordinal() && this.state!=LoaderState.ERRORED; {
return this.state.ordinal() >= state.ordinal() && this.state != LoaderState.ERRORED;
} }
void forceState(LoaderState newState) void forceState(LoaderState newState)
@ -419,7 +354,7 @@ public class LoadController
{ {
continue; continue;
} }
String pkg = c.getName().substring(0,idx); String pkg = c.getName().substring(0, idx);
if (packageOwners.containsKey(pkg)) if (packageOwners.containsKey(pkg))
{ {
return packageOwners.get(pkg).get(0); return packageOwners.get(pkg).get(0);
@ -428,6 +363,7 @@ public class LoadController
return null; return null;
} }
private FMLSecurityManager accessibleManager = new FMLSecurityManager(); private FMLSecurityManager accessibleManager = new FMLSecurityManager();
class FMLSecurityManager extends SecurityManager class FMLSecurityManager extends SecurityManager

View File

@ -547,9 +547,10 @@ public class Loader
ObjectHolderRegistry.INSTANCE.findObjectHolders(new ASMDataTable()); ObjectHolderRegistry.INSTANCE.findObjectHolders(new ASMDataTable());
modController.forceActiveContainer(containers[0]); modController.forceActiveContainer(containers[0]);
} }
/** /**
* Called from the hook to start mod loading. We trigger the * 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. * the mod list is frozen completely and is consider immutable from then on.
* @param injectedModContainers containers to inject * @param injectedModContainers containers to inject
*/ */
@ -624,7 +625,6 @@ public class Loader
ItemStackHolderInjector.INSTANCE.findHolders(discoverer.getASMTable()); ItemStackHolderInjector.INSTANCE.findHolders(discoverer.getASMTable());
CapabilityManager.INSTANCE.injectCapabilities(discoverer.getASMTable()); CapabilityManager.INSTANCE.injectCapabilities(discoverer.getASMTable());
modController.distributeStateMessage(LoaderState.PREINITIALIZATION, discoverer.getASMTable(), canonicalConfigDir); modController.distributeStateMessage(LoaderState.PREINITIALIZATION, discoverer.getASMTable(), canonicalConfigDir);
modController.checkErrors();
GameData.fireRegistryEvents(rl -> !rl.equals(GameData.RECIPES)); GameData.fireRegistryEvents(rl -> !rl.equals(GameData.RECIPES));
FMLCommonHandler.instance().fireSidedRegistryEvents(); FMLCommonHandler.instance().fireSidedRegistryEvents();
ObjectHolderRegistry.INSTANCE.applyObjectHolders(); ObjectHolderRegistry.INSTANCE.applyObjectHolders();
@ -750,7 +750,6 @@ public class Loader
progressBar.step("Finishing up"); progressBar.step("Finishing up");
modController.transition(LoaderState.AVAILABLE, false); modController.transition(LoaderState.AVAILABLE, false);
modController.distributeStateMessage(LoaderState.AVAILABLE); modController.distributeStateMessage(LoaderState.AVAILABLE);
modController.checkErrors();
GameData.freezeData(); GameData.freezeData();
FMLLog.log.info("Forge Mod Loader has successfully loaded {} mod{}", mods.size(), mods.size() == 1 ? "" : "s"); FMLLog.log.info("Forge Mod Loader has successfully loaded {} mod{}", mods.size(), mods.size() == 1 ? "" : "s");
progressBar.step("Completing Minecraft initialization"); progressBar.step("Completing Minecraft initialization");

View File

@ -28,9 +28,15 @@ import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.google.common.base.Preconditions; 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 static final long serialVersionUID = 1L;
private final String id; private final String id;
@ -101,6 +107,13 @@ public class MissingModsException extends EnhancedRuntimeException
stream.println(""); stream.println("");
} }
@Override
@SideOnly(Side.CLIENT)
public GuiScreen createGui()
{
return new GuiModsMissing(this);
}
public static class MissingModInfo public static class MissingModInfo
{ {
private final ArtifactVersion acceptedVersion; private final ArtifactVersion acceptedVersion;
@ -132,4 +145,5 @@ public class MissingModsException extends EnhancedRuntimeException
return required; return required;
} }
} }
} }

View File

@ -21,7 +21,13 @@ package net.minecraftforge.fml.common;
import java.util.List; 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<WrongMinecraftVersionException> wrongMinecraftExceptions;
public final List<MissingModsException> missingModsExceptions; public final List<MissingModsException> missingModsExceptions;
@ -31,6 +37,13 @@ public class MultipleModsErrored extends EnhancedRuntimeException
this.missingModsExceptions = missingModsExceptions; this.missingModsExceptions = missingModsExceptions;
} }
@Override
@SideOnly(Side.CLIENT)
public GuiScreen createGui()
{
return new GuiMultipleModsErrored(this);
}
@Override @Override
protected void printStackTrace(WrappedPrintStream stream) protected void printStackTrace(WrappedPrintStream stream)
{ {

View File

@ -19,7 +19,13 @@
package net.minecraftforge.fml.common; 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; private static final long serialVersionUID = 1L;
public ModContainer mod; public ModContainer mod;
@ -42,4 +48,10 @@ public class WrongMinecraftVersionException extends EnhancedRuntimeException
stream.println(""); stream.println("");
} }
@Override
@SideOnly(Side.CLIENT)
public GuiScreen createGui()
{
return new GuiWrongMinecraft(this);
}
} }

View File

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

View File

@ -21,10 +21,15 @@ package net.minecraftforge.fml.common.toposort;
import java.util.Set; 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.EnhancedRuntimeException;
import net.minecraftforge.fml.common.ModContainer; 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; 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);
}
} }