Give immediate and complete error messages when there is a crash during startup (#4869)
This commit is contained in:
parent
e65bd4a62b
commit
a38f5fd6a2
12 changed files with 301 additions and 256 deletions
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue