runelite-client: Add fatal error dialog

This commit is contained in:
Max Weber
2019-08-07 03:43:41 -06:00
parent 944064b1b5
commit cdfe2e9306
7 changed files with 301 additions and 23 deletions

View File

@@ -37,6 +37,7 @@ import java.util.Locale;
import javax.annotation.Nullable;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.swing.SwingUtilities;
import joptsimple.ArgumentAcceptingOptionSpec;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
@@ -60,6 +61,7 @@ import net.runelite.client.rs.ClientLoader;
import net.runelite.client.rs.ClientUpdateCheckMode;
import net.runelite.client.ui.ClientUI;
import net.runelite.client.ui.DrawManager;
import net.runelite.client.ui.FatalErrorDialog;
import net.runelite.client.ui.SplashScreen;
import net.runelite.client.ui.overlay.OverlayManager;
import net.runelite.client.ui.overlay.OverlayRenderer;
@@ -78,6 +80,7 @@ public class RuneLite
public static final File RUNELITE_DIR = new File(System.getProperty("user.home"), ".runelite");
public static final File PROFILES_DIR = new File(RUNELITE_DIR, "profiles");
public static final File SCREENSHOT_DIR = new File(RUNELITE_DIR, "screenshots");
public static final File LOGS_DIR = new File(RUNELITE_DIR, "logs");
@Getter
private static Injector injector;
@@ -219,7 +222,11 @@ public class RuneLite
assert assertions = true;
if (!assertions)
{
throw new RuntimeException("Developers should enable assertions; Add `-ea` to your JVM arguments`");
SwingUtilities.invokeLater(() ->
new FatalErrorDialog("Developers should enable assertions; Add `-ea` to your JVM arguments`")
.addBuildingGuide()
.open());
return;
}
}
@@ -238,6 +245,13 @@ public class RuneLite
final long uptime = rb.getUptime();
log.info("Client initialization took {}ms. Uptime: {}ms", end - start, uptime);
}
catch (Exception e)
{
log.warn("Failure during startup", e);
SwingUtilities.invokeLater(() ->
new FatalErrorDialog("RuneLite has encountered an unexpected error during startup.")
.open());
}
finally
{
SplashScreen.stop();

View File

@@ -40,6 +40,9 @@ public class RuneLiteProperties
private static final String WIKI_LINK = "runelite.wiki.link";
private static final String PATREON_LINK = "runelite.patreon.link";
private static final String LAUNCHER_VERSION_PROPERTY = "runelite.launcher.version";
private static final String TROUBLESHOOTING_LINK = "runelite.wiki.troubleshooting.link";
private static final String BUILDING_LINK = "runelite.wiki.building.link";
private static final String DNS_CHANGE_LINK = "runelite.dnschange.link";
private static final Properties properties = new Properties();
@@ -100,4 +103,19 @@ public class RuneLiteProperties
{
return System.getProperty(LAUNCHER_VERSION_PROPERTY);
}
public static String getTroubleshootingLink()
{
return properties.getProperty(TROUBLESHOOTING_LINK);
}
public static String getBuildingLink()
{
return properties.getProperty(BUILDING_LINK);
}
public static String getDNSChangeLink()
{
return properties.getProperty(DNS_CHANGE_LINK);
}
}

View File

@@ -49,11 +49,13 @@ import java.util.Map;
import java.util.function.Supplier;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import javax.swing.SwingUtilities;
import lombok.extern.slf4j.Slf4j;
import net.runelite.api.Client;
import static net.runelite.client.rs.ClientUpdateCheckMode.AUTO;
import static net.runelite.client.rs.ClientUpdateCheckMode.NONE;
import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA;
import net.runelite.client.ui.FatalErrorDialog;
import net.runelite.client.ui.SplashScreen;
import net.runelite.http.api.RuneLiteAPI;
import okhttp3.Request;
@@ -64,7 +66,7 @@ import org.apache.commons.compress.compressors.CompressorException;
public class ClientLoader implements Supplier<Applet>
{
private ClientUpdateCheckMode updateCheckMode;
private Applet client = null;
private Object client = null;
public ClientLoader(ClientUpdateCheckMode updateCheckMode)
{
@@ -78,10 +80,15 @@ public class ClientLoader implements Supplier<Applet>
{
client = doLoad();
}
return client;
if (client instanceof Throwable)
{
throw new RuntimeException((Throwable) client);
}
return (Applet) client;
}
private Applet doLoad()
private Object doLoad()
{
if (updateCheckMode == NONE)
{
@@ -172,6 +179,15 @@ public class ClientLoader implements Supplier<Applet>
Map<String, String> hashes;
try (InputStream is = ClientLoader.class.getResourceAsStream("/patch/hashes.json"))
{
if (is == null)
{
SwingUtilities.invokeLater(() ->
new FatalErrorDialog("The client-patch is missing from the classpath. If you are building " +
"the client you need to re-run maven")
.addBuildingGuide()
.open());
throw new NullPointerException();
}
hashes = new Gson().fromJson(new InputStreamReader(is), new TypeToken<HashMap<String, String>>()
{
}.getType());
@@ -264,15 +280,10 @@ public class ClientLoader implements Supplier<Applet>
| CompressorException | InvalidHeaderException | CertificateException | VerificationException
| SecurityException e)
{
if (e instanceof ClassNotFoundException)
{
log.error("Unable to load client - class not found. This means you"
+ " are not running RuneLite with Maven as the client patch"
+ " is not in your classpath.");
}
log.error("Error loading RS!", e);
return null;
SwingUtilities.invokeLater(() -> FatalErrorDialog.showNetErrorWindow("loading the client", e));
return e;
}
}

View File

@@ -24,7 +24,7 @@
*/
package net.runelite.client.rs;
class VerificationException extends Exception
public class VerificationException extends Exception
{
public VerificationException(String message)
{

View File

@@ -53,7 +53,6 @@ import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import static javax.swing.JOptionPane.ERROR_MESSAGE;
import static javax.swing.JOptionPane.INFORMATION_MESSAGE;
import javax.swing.JPanel;
import javax.swing.JRootPane;
@@ -518,14 +517,7 @@ public class ClientUI
});
// Show out of date dialog if needed
if (client == null)
{
SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(frame,
"Error loading client! Check your logs for more details.",
"Unable to load client",
ERROR_MESSAGE));
}
else if (!(client instanceof Client))
if (client != null && !(client instanceof Client))
{
SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(frame,
"RuneLite has not yet been updated to work with the latest\n"

View File

@@ -0,0 +1,240 @@
/*
* Copyright (c) 2019 Abex
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.client.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import lombok.extern.slf4j.Slf4j;
import net.runelite.client.RuneLite;
import net.runelite.client.RuneLiteProperties;
import net.runelite.client.rs.VerificationException;
import net.runelite.client.util.LinkBrowser;
@Slf4j
public class FatalErrorDialog extends JDialog
{
private static final AtomicBoolean alreadyOpen = new AtomicBoolean(false);
private final JPanel rightColumn = new JPanel();
private final Font font = new Font(Font.DIALOG, Font.PLAIN, 12);
public FatalErrorDialog(String message)
{
if (alreadyOpen.getAndSet(true))
{
throw new IllegalStateException("Fatal error during fatal error: " + message);
}
try
{
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
}
catch (Exception e)
{
}
UIManager.put("Button.select", ColorScheme.DARKER_GRAY_COLOR);
try
{
BufferedImage logo = ImageIO.read(SplashScreen.class.getResourceAsStream("runelite_transparent.png"));
setIconImage(logo);
JLabel runelite = new JLabel();
runelite.setIcon(new ImageIcon(logo));
runelite.setAlignmentX(Component.CENTER_ALIGNMENT);
runelite.setBackground(ColorScheme.DARK_GRAY_COLOR);
runelite.setOpaque(true);
rightColumn.add(runelite);
}
catch (IOException e)
{
}
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
System.exit(-1);
}
});
setTitle("Fatal error starting RuneLite");
setLayout(new BorderLayout());
Container pane = getContentPane();
pane.setBackground(ColorScheme.DARKER_GRAY_COLOR);
JPanel leftPane = new JPanel();
leftPane.setBackground(ColorScheme.DARKER_GRAY_COLOR);
leftPane.setLayout(new BorderLayout());
JLabel title = new JLabel("There was a fatal error starting RuneLite");
title.setForeground(Color.WHITE);
title.setFont(font.deriveFont(16.f));
title.setBorder(new EmptyBorder(10, 10, 10, 10));
leftPane.add(title, BorderLayout.NORTH);
leftPane.setPreferredSize(new Dimension(400, 200));
JTextArea textArea = new JTextArea(message);
textArea.setFont(font);
textArea.setBackground(ColorScheme.DARKER_GRAY_COLOR);
textArea.setForeground(Color.LIGHT_GRAY);
textArea.setLineWrap(true);
textArea.setWrapStyleWord(true);
textArea.setBorder(new EmptyBorder(10, 10, 10, 10));
textArea.setEditable(false);
leftPane.add(textArea, BorderLayout.CENTER);
pane.add(leftPane, BorderLayout.CENTER);
rightColumn.setLayout(new BoxLayout(rightColumn, BoxLayout.Y_AXIS));
rightColumn.setBackground(ColorScheme.DARK_GRAY_COLOR);
rightColumn.setMaximumSize(new Dimension(200, Integer.MAX_VALUE));
addButton("Open logs folder", () ->
{
try
{
Desktop.getDesktop().open(RuneLite.LOGS_DIR);
}
catch (IOException e)
{
log.warn("Unable to open logs", e);
}
});
addButton("Get help on Discord", () -> LinkBrowser.browse(RuneLiteProperties.getDiscordInvite()));
addButton("Troubleshooting steps", () -> LinkBrowser.browse(RuneLiteProperties.getTroubleshootingLink()));
pane.add(rightColumn, BorderLayout.EAST);
}
public void open()
{
addButton("Exit", () -> System.exit(-1));
pack();
SplashScreen.stop();
setVisible(true);
}
public FatalErrorDialog addButton(String message, Runnable action)
{
JButton button = new JButton(message);
button.addActionListener(e -> action.run());
button.setFont(font);
button.setBackground(ColorScheme.DARK_GRAY_COLOR);
button.setForeground(Color.LIGHT_GRAY);
button.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createMatteBorder(1, 0, 0, 0, ColorScheme.DARK_GRAY_COLOR.brighter()),
new EmptyBorder(4, 4, 4, 4)
));
button.setAlignmentX(Component.CENTER_ALIGNMENT);
button.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
button.setFocusPainted(false);
button.addChangeListener(ev ->
{
if (button.getModel().isPressed())
{
button.setBackground(ColorScheme.DARKER_GRAY_COLOR);
}
else if (button.getModel().isRollover())
{
button.setBackground(ColorScheme.DARK_GRAY_HOVER_COLOR);
}
else
{
button.setBackground(ColorScheme.DARK_GRAY_COLOR);
}
});
rightColumn.add(button);
rightColumn.revalidate();
return this;
}
public FatalErrorDialog addBuildingGuide()
{
return addButton("Building guide", () -> LinkBrowser.browse(RuneLiteProperties.getBuildingLink()));
}
public static void showNetErrorWindow(String action, Throwable err)
{
if (err instanceof VerificationException || err instanceof GeneralSecurityException)
{
new FatalErrorDialog("RuneLite was unable to verify the security of its connection to the internet while " +
action + ". You may have a misbehaving antivirus, internet service provider, a proxy, or an incomplete" +
" java installation.")
.open();
return;
}
if (err instanceof ConnectException)
{
new FatalErrorDialog("RuneLite is unable to connect to a required server while " + action + ". " +
"Please check your internet connection")
.open();
return;
}
if (err instanceof UnknownHostException)
{
new FatalErrorDialog("RuneLite is unable to resolve the address of a required server while " + action + ". " +
"Your DNS resolver may be misconfigured, pointing to an inaccurate resolver, or your internet connection may " +
"be down. ")
.addButton("Change your DNS resolver", () -> LinkBrowser.browse(RuneLiteProperties.getDNSChangeLink()))
.open();
return;
}
new FatalErrorDialog("RuneLite encountered a fatal error while " + action + ".").open();
}
}

View File

@@ -5,4 +5,7 @@ runelite.discord.appid=409416265891971072
runelite.discord.invite=https://discord.gg/R4BQ8tU
runelite.github.link=https://github.com/runelite
runelite.wiki.link=https://github.com/runelite/runelite/wiki
runelite.patreon.link=https://www.patreon.com/runelite
runelite.patreon.link=https://www.patreon.com/runelite
runelite.wiki.troubleshooting.link=https://github.com/runelite/runelite/wiki/Troubleshooting-problems-with-the-client
runelite.wiki.building.link=https://github.com/runelite/runelite/wiki/Building-with-IntelliJ-IDEA#client-failing-to-start
runelite.dnschange.link=https://1.1.1.1/dns/