diff --git a/pom.xml b/pom.xml index d7c7d90..2a0c814 100755 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ Parabot client - The only perfect open source (Runescape private server) bot! + The best open-source (Runescape Private Server) bot http://www.parabot.org/ @@ -68,7 +68,7 @@ junit junit 4.12 - provided + test org.parabot @@ -191,13 +191,13 @@ false parabot-maven Custom Maven Repository - ftp://maven.parabot.org + ftp://maven.parabot.org/public_html default parabot-maven Frontend Parabot Maven - ftp://maven.parabot.org/docs/${artifactId}/ + ftp://maven.parabot.org/public_html/docs/${artifactId}/ diff --git a/src/main/java/org/parabot/Landing.java b/src/main/java/org/parabot/Landing.java index b5ff725..c870393 100644 --- a/src/main/java/org/parabot/Landing.java +++ b/src/main/java/org/parabot/Landing.java @@ -11,32 +11,31 @@ import org.parabot.core.network.proxy.ProxyType; import org.parabot.core.ui.BotUI; import org.parabot.core.ui.ServerSelector; import org.parabot.core.ui.utils.UILog; +import org.parabot.environment.handlers.exceptions.ExceptionHandler; +import org.parabot.environment.handlers.exceptions.FileExceptionHandler; import javax.swing.*; import java.io.File; -import java.io.IOException; /** - * Parabot v2.7 - * * @author Everel, JKetelaar, Matt, Dane - * @version 2.7 - * @see Homepage + * @version 2.8.1 + * @see Homepage */ public final class Landing { private static String username; private static String password; - public static void main(String... args) throws IOException { + public static void main(String... args) { + Thread.setDefaultUncaughtExceptionHandler(new FileExceptionHandler(ExceptionHandler.ExceptionType.CLIENT)); if (Context.getJavaVersion() >= 9) { UILog.log("Parabot", "Parabot doesn't support Java 9+ currently. Please downgrade to Java 8 to ensure Parabot is working correctly."); - System.exit(0); } if (!System.getProperty("os.arch").contains("64")) { UILog.log("Parabot", "You are not running a 64-bit version of Java, this might cause the client to lag or crash unexpectedly.\r\n" + - "It's recommended to upgrade to a 64-bit version."); + "It is recommended to upgrade to a 64-bit version."); } parseArgs(args); @@ -80,11 +79,11 @@ public final class Landing { switch (arg.toLowerCase()) { case "-createdirs": Directories.validate(); - System.out - .println(TranslationHelper.translate(("DIRECTORIES_CREATED"))); + System.out.println(TranslationHelper.translate(("DIRECTORIES_CREATED"))); System.exit(0); break; case "-debug": + Core.setDump(true); case "-offlinemode": Core.setDebug(true); break; @@ -130,14 +129,9 @@ public final class Landing { break; case "-proxy": ProxyType type = ProxyType.valueOf(args[++i].toUpperCase()); - if (type == null) { - System.err.println(TranslationHelper.translate("INVALID_PROXY_TYPE") + args[i]); - System.exit(1); - return; - } - ProxySocket.setProxy(type, args[++i], - Integer.parseInt(args[++i])); + ProxySocket.setProxy(type, args[++i], Integer.parseInt(args[++i])); break; + case "-proxy_auth": case "-auth": ProxySocket.auth = true; ProxySocket.setLogin(args[++i], args[++i]); @@ -146,6 +140,7 @@ public final class Landing { Core.disableSec(); break; case "-no_validation": + case "-ignore_updates": Core.disableValidation(); break; case "-uuid": diff --git a/src/main/java/org/parabot/core/parsers/scripts/ScriptParser.java b/src/main/java/org/parabot/core/parsers/scripts/ScriptParser.java index a7e5d1f..cbb4ad2 100644 --- a/src/main/java/org/parabot/core/parsers/scripts/ScriptParser.java +++ b/src/main/java/org/parabot/core/parsers/scripts/ScriptParser.java @@ -18,9 +18,10 @@ public abstract class ScriptParser { public static final Map SCRIPT_CACHE = new HashMap<>(); + private static final ArrayList parsers = new ArrayList<>(); + public static ScriptDescription[] getDescriptions() { SCRIPT_CACHE.clear(); - final ArrayList parsers = new ArrayList<>(); if (Core.inLoadLocal()) { parsers.add(new LocalJavaScripts()); parsers.add(new BDNScripts()); @@ -47,6 +48,9 @@ public abstract class ScriptParser { return SORTED_SCRIPT_CACHE.keySet().toArray(new ScriptDescription[SORTED_SCRIPT_CACHE.size()]); } - public abstract void execute(); + public static final void addParser(ScriptParser parser) { + parsers.add(parser); + } + public abstract void execute(); } \ No newline at end of file diff --git a/src/main/java/org/parabot/core/ui/utils/UILog.java b/src/main/java/org/parabot/core/ui/utils/UILog.java index d1261f6..ffa4552 100644 --- a/src/main/java/org/parabot/core/ui/utils/UILog.java +++ b/src/main/java/org/parabot/core/ui/utils/UILog.java @@ -39,4 +39,9 @@ public class UILog { return JOptionPane.showOptionDialog(null, message, title, JOptionPane.YES_NO_CANCEL_OPTION, messageType, null, options, null); } + + public static int alert(final String title, final String message, Object[] options, int initialValue, int messageType) { + return JOptionPane.showOptionDialog(null, message, title, + JOptionPane.YES_NO_CANCEL_OPTION, messageType, null, options, initialValue); + } } diff --git a/src/main/java/org/parabot/environment/handlers/exceptions/ExceptionHandler.java b/src/main/java/org/parabot/environment/handlers/exceptions/ExceptionHandler.java new file mode 100644 index 0000000..198a55a --- /dev/null +++ b/src/main/java/org/parabot/environment/handlers/exceptions/ExceptionHandler.java @@ -0,0 +1,88 @@ +package org.parabot.environment.handlers.exceptions; + +/** + * Class to be implemented that allows multiple types of exception handlers + */ +public abstract class ExceptionHandler implements Thread.UncaughtExceptionHandler { + /** + * The name of the exception handler + */ + private final String name; + + /** + * The status of the exception handler; Defines if the exception handler is enabled or disabled + */ + private boolean enabled = true; + + /** + * The type the handler is meant for + */ + private ExceptionType exceptionType; + + public ExceptionHandler(String name, ExceptionType exceptionType) { + this.name = name; + this.exceptionType = exceptionType; + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + this.handle(e); + } + + /** + * Writes the exception to class extending this abstract class + * + * @param e + */ + public abstract void handle(Throwable e); + + /** + * Returns if the exception handler is enabled or disabled + * + * @return + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Sets the enabled status of the exception handler + * + * @param enabled + * + * @return + */ + public ExceptionHandler setEnabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public ExceptionType getExceptionType() { + return exceptionType; + } + + public ExceptionHandler setExceptionType(ExceptionType exceptionType) { + this.exceptionType = exceptionType; + return this; + } + + public String getName() { + return name; + } + + public enum ExceptionType { + SERVER("Server"), + SCRIPT("Script"), + CLIENT("Client"); + + private String name; + + ExceptionType(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/src/main/java/org/parabot/environment/handlers/exceptions/FileExceptionHandler.java b/src/main/java/org/parabot/environment/handlers/exceptions/FileExceptionHandler.java new file mode 100644 index 0000000..e72c355 --- /dev/null +++ b/src/main/java/org/parabot/environment/handlers/exceptions/FileExceptionHandler.java @@ -0,0 +1,139 @@ +package org.parabot.environment.handlers.exceptions; + +import org.parabot.core.Directories; +import org.parabot.core.ui.utils.UILog; +import org.parabot.environment.api.utils.FileUtil; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.io.IOException; + +/** + * Writes exceptions to a file and reports the file location back to the user + */ +public class FileExceptionHandler extends ExceptionHandler { + /** + * The default index of all options to be selected when the popup appears + */ + private static final int defaultOptionIndex = 1; + + /** + * Directory where the reports get written to + */ + private final File reportsDirectory; + + /** + * All possible options to select when the popup appears + */ + private final Object[] options = new Object[]{ + "Close", + "Open report", + "Ignore " + this.getExceptionType().getName().toLowerCase() + " errors" }; + + /** + * Defines if the alert should popup during this client instance again + */ + private boolean ignored = false; + + /** + * Initializes the exception handler and ensures the reports directory is created and writable + */ + public FileExceptionHandler(ExceptionType exceptionType) { + super("File exception handler", exceptionType); + + this.reportsDirectory = new File(Directories.getWorkspace(), "reports"); + if (!this.reportsDirectory.exists() || !this.reportsDirectory.isDirectory()) { + this.reportsDirectory.mkdir(); + } + + this.cleanOldErrors(); + } + + @Override + public void handle(Throwable e) { + File report = new File(this.reportsDirectory, "report-" + this.getExceptionType().getName().toLowerCase() + "-" + (System.currentTimeMillis() / 1000) + ".txt"); + try { + report.createNewFile(); + + StringBuilder reportContent = new StringBuilder(); + reportContent.append("Message: ").append(e.getMessage()).append("\n\n"); + reportContent.append(e.toString()).append("\n\n"); + + for (int i = 0; i < e.getStackTrace().length; i++) { + if (i > 0) { + reportContent.append("\t"); + } + reportContent.append(e.getStackTrace()[i]).append("\n"); + } + + FileUtil.writeFileContents(report, reportContent.toString()); + + if (!ignored) { + displayAlert(report); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + public boolean isIgnored() { + return ignored; + } + + public FileExceptionHandler setIgnored(boolean ignored) { + this.ignored = ignored; + return this; + } + + public File getReportsDirectory() { + return reportsDirectory; + } + + /** + * Displays the dialog of the alert + * + * @param report + */ + private void displayAlert(File report) { + int response = UILog.alert( + "Error occurred", + "We are sorry to inform you that an error occurred within Parabot.\n\n" + + "The error has been written to a report file.\n" + + "Please report the error to the Parabot staff with as much information as possible.", + this.options, + defaultOptionIndex, + JOptionPane.WARNING_MESSAGE + ); + + switch (response) { + case 1: + try { + Desktop.getDesktop().open(report); + } catch (Exception ex) { + ex.printStackTrace(); + } + break; + case 2: + ignored = true; + break; + + } + } + + /** + * Remove errors older than 24 hours + */ + private void cleanOldErrors() { + File[] reports = this.reportsDirectory.listFiles(); + if (reports != null) { + for (File report : reports) { + if (report.isFile()) { + if ((System.currentTimeMillis() - report.lastModified()) / 1000 > 60 * 60 * 24) { + report.delete(); + } + } + } + } + } +} diff --git a/src/main/java/org/parabot/environment/servers/ServerProvider.java b/src/main/java/org/parabot/environment/servers/ServerProvider.java index ae9b5ba..78ca006 100644 --- a/src/main/java/org/parabot/environment/servers/ServerProvider.java +++ b/src/main/java/org/parabot/environment/servers/ServerProvider.java @@ -77,7 +77,7 @@ public abstract class ServerProvider implements Opcodes { return; } - HookParser parser = hookFile.getParser(); + HookParser parser = hookFile.getParser(); Injectable[] injectables = parser.getInjectables(); if (injectables == null) { @@ -92,17 +92,18 @@ public abstract class ServerProvider implements Opcodes { index++; } } catch (NullPointerException ex) { - if(!crashed) { + if (!crashed) { Injectable inj = injectables[index]; - int resp = UILog.alert("Outdated client", "This server currently has outdated hooks, please report it to a member of the Parabot staff.\r\n\r\n" + - "Broken hook:\r\n"+inj, new Object[]{"Close", "Report here..."}, JOptionPane.ERROR_MESSAGE); + int resp = UILog.alert("Outdated client", "This server currently has outdated hooks, please report it to the Parabot staff.\r\n\r\n" + + "Broken hook:\r\n" + inj, new Object[]{ "Close", "Report here..." }, JOptionPane.ERROR_MESSAGE); - if(resp == 1) { + if (resp == 1) { URI uri = URI.create(Configuration.COMMUNITY_PAGE + "forum/135-reports/"); try { Desktop.getDesktop().browse(uri); - } catch (IOException ignore) {} + } catch (IOException ignore) { + } } } crashed = true; @@ -112,20 +113,6 @@ public abstract class ServerProvider implements Opcodes { Context.getInstance().setHookParser(parser); } - private HookFile fetchHookFile() { - HookFile hookFile = getHookFile(); - if (hookFile != null) { - return hookFile; - } - - URL hookLocation = getHooks(); - if (hookLocation == null) { - return null; - } - - return new HookFile(hookLocation, HookFile.TYPE_XML); - } - /** * Add custom items to the bot menu bar * @@ -156,16 +143,16 @@ public abstract class ServerProvider implements Opcodes { public void initMouse() { final Context context = Context.getInstance(); - final Applet applet = context.getApplet(); - final Mouse mouse = new Mouse(applet); + final Applet applet = context.getApplet(); + final Mouse mouse = new Mouse(applet); applet.addMouseListener(mouse); applet.addMouseMotionListener(mouse); context.setMouse(mouse); } public void initKeyboard() { - final Context context = Context.getInstance(); - final Applet applet = context.getApplet(); + final Context context = Context.getInstance(); + final Applet applet = context.getApplet(); final Keyboard keyboard = new Keyboard(applet); applet.addKeyListener(keyboard); context.setKeyboard(keyboard); @@ -191,4 +178,18 @@ public abstract class ServerProvider implements Opcodes { } + private HookFile fetchHookFile() { + HookFile hookFile = getHookFile(); + if (hookFile != null) { + return hookFile; + } + + URL hookLocation = getHooks(); + if (hookLocation == null) { + return null; + } + + return new HookFile(hookLocation, HookFile.TYPE_XML); + } + } diff --git a/src/main/java/org/parabot/environment/servers/executers/LocalPublicServerExecuter.java b/src/main/java/org/parabot/environment/servers/executers/LocalPublicServerExecuter.java index 3f06b14..b52574c 100644 --- a/src/main/java/org/parabot/environment/servers/executers/LocalPublicServerExecuter.java +++ b/src/main/java/org/parabot/environment/servers/executers/LocalPublicServerExecuter.java @@ -24,9 +24,9 @@ import java.net.URL; * @author JKetelaar */ public class LocalPublicServerExecuter extends ServerExecuter { - private String serverName; - private String serverUrl; - private String providerUrl; + private String serverName; + private String serverUrl; + private String providerUrl; private ServerProviderInfo serverProviderInfo; public LocalPublicServerExecuter(final String serverName, final ServerProviderInfo serverProviderInfo, String serverUrl, String providerUrl) { @@ -80,8 +80,8 @@ public class LocalPublicServerExecuter extends ServerExecuter { BuildPath.add(destination.toURI().toURL()); - ServerLoader serverLoader = new ServerLoader(classPath); - final String[] classNames = serverLoader.getServerClassNames(); + ServerLoader serverLoader = new ServerLoader(classPath); + final String[] classNames = serverLoader.getServerClassNames(); if (classNames.length == 0) { UILog.log( "Error", diff --git a/src/main/java/org/parabot/environment/servers/executers/LocalServerExecuter.java b/src/main/java/org/parabot/environment/servers/executers/LocalServerExecuter.java index 1ff3a40..f509ecc 100644 --- a/src/main/java/org/parabot/environment/servers/executers/LocalServerExecuter.java +++ b/src/main/java/org/parabot/environment/servers/executers/LocalServerExecuter.java @@ -16,8 +16,8 @@ import java.net.MalformedURLException; */ public class LocalServerExecuter extends ServerExecuter { private final Constructor serverProviderConstructor; - private ClassPath classPath; - private String serverName; + private ClassPath classPath; + private String serverName; public LocalServerExecuter(Constructor serverProviderConstructor, ClassPath classPath, final String serverName) { diff --git a/src/main/java/org/parabot/environment/servers/executers/PublicServerExecuter.java b/src/main/java/org/parabot/environment/servers/executers/PublicServerExecuter.java index a316586..b8d2f77 100644 --- a/src/main/java/org/parabot/environment/servers/executers/PublicServerExecuter.java +++ b/src/main/java/org/parabot/environment/servers/executers/PublicServerExecuter.java @@ -36,10 +36,9 @@ public class PublicServerExecuter extends ServerExecuter { } }; - private String serverName; - - private PBLocalPreferences settings; private final String cacheVersionKey = "cachedProviderVersion"; + private String serverName; + private PBLocalPreferences settings; public PublicServerExecuter(final String serverName) { this.serverName = serverName; @@ -58,14 +57,14 @@ public class PublicServerExecuter extends ServerExecuter { Core.verbose("Downloading: " + jarUrl + " ..."); String providerVersion = serverProviderInfo.getProviderVersion(); - if(providerVersion == null) { + if (providerVersion == null) { providerVersion = "error"; } - settings = new PBLocalPreferences(serverProviderInfo.getClientCRC32()+".json"); - if(settings.getSetting(cacheVersionKey) != null) { + settings = new PBLocalPreferences(serverProviderInfo.getClientCRC32() + ".json"); + if (settings.getSetting(cacheVersionKey) != null) { Core.verbose(String.format("Latest provider version: %s, local provider version: %s", settings.getSetting(cacheVersionKey), providerVersion)); - if(!settings.getSetting(cacheVersionKey).equals(providerVersion)) { + if (!settings.getSetting(cacheVersionKey).equals(providerVersion)) { Core.verbose("Local provider outdated, clearing cache."); Directories.clearCache(); } @@ -88,8 +87,8 @@ public class PublicServerExecuter extends ServerExecuter { BuildPath.add(destination.toURI().toURL()); - ServerLoader serverLoader = new ServerLoader(classPath); - final String[] classNames = serverLoader.getServerClassNames(); + ServerLoader serverLoader = new ServerLoader(classPath); + final String[] classNames = serverLoader.getServerClassNames(); if (classNames == null || classNames.length == 0) { UILog.log( "Error", diff --git a/src/main/java/org/parabot/environment/servers/executers/ServerExecuter.java b/src/main/java/org/parabot/environment/servers/executers/ServerExecuter.java index cae7c5f..1fd6df9 100644 --- a/src/main/java/org/parabot/environment/servers/executers/ServerExecuter.java +++ b/src/main/java/org/parabot/environment/servers/executers/ServerExecuter.java @@ -3,6 +3,8 @@ package org.parabot.environment.servers.executers; import org.parabot.core.Context; import org.parabot.core.ui.BotUI; import org.parabot.core.ui.components.PaintComponent; +import org.parabot.environment.handlers.exceptions.ExceptionHandler; +import org.parabot.environment.handlers.exceptions.FileExceptionHandler; import org.parabot.environment.servers.ServerProvider; /** @@ -15,7 +17,7 @@ public abstract class ServerExecuter { public abstract void run(); public void finalize(final ServerProvider provider, final String serverName) { - new Thread(new Runnable() { + Thread serverThread = new Thread(new Runnable() { @Override public void run() { try { @@ -30,7 +32,11 @@ public abstract class ServerExecuter { t.printStackTrace(); } } - }).start(); + }); + + serverThread.setUncaughtExceptionHandler(new FileExceptionHandler(ExceptionHandler.ExceptionType.SERVER)); + + serverThread.start(); } } diff --git a/src/main/java/org/parabot/environment/servers/loader/ServerLoader.java b/src/main/java/org/parabot/environment/servers/loader/ServerLoader.java index 4ed965b..7f214a6 100644 --- a/src/main/java/org/parabot/environment/servers/loader/ServerLoader.java +++ b/src/main/java/org/parabot/environment/servers/loader/ServerLoader.java @@ -29,8 +29,9 @@ public class ServerLoader extends ASMClassLoader { public final String[] getServerClassNames() { final List classNames = new ArrayList(); for (ClassNode c : classPath.classes.values()) { - if (c.superName.replace('/', '.').equals( - ServerProvider.class.getName())) { + if (c.superName + .replace('/', '.') + .equals(ServerProvider.class.getName())) { classNames.add(c.name.replace('/', '.')); } } diff --git a/src/test/java/org/parabot/FileExceptionHandlerTest.java b/src/test/java/org/parabot/FileExceptionHandlerTest.java new file mode 100644 index 0000000..7066c7f --- /dev/null +++ b/src/test/java/org/parabot/FileExceptionHandlerTest.java @@ -0,0 +1,39 @@ +package org.parabot; + +import org.junit.Test; +import org.parabot.environment.handlers.exceptions.ExceptionHandler; +import org.parabot.environment.handlers.exceptions.FileExceptionHandler; + +public class FileExceptionHandlerTest { + + @Test + public void manualTest() { + FileExceptionHandler handler = new FileExceptionHandler(ExceptionHandler.ExceptionType.CLIENT); + handler.setIgnored(true); + + Exception exception = new NullPointerException("Manual test"); + handler.handle(exception); + } + + @Test + public void threadHandlerTest() { + FileExceptionHandler handler = new FileExceptionHandler(ExceptionHandler.ExceptionType.CLIENT); + handler.setIgnored(true); + + Thread thread = new Thread() { + @Override + public void run() throws NullPointerException { + throw new NullPointerException("Thread test"); + } + }; + + thread.setUncaughtExceptionHandler(handler); + thread.start(); + + try { + Thread.sleep(1500); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +}