diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index 6abb3b2208..96acb797ef 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -323,6 +323,7 @@ public class RuneLite // Load the plugins, but does not start them yet. // This will initialize configuration pluginManager.loadCorePlugins(); + pluginManager.loadSideLoadPlugins(); externalPluginManager.loadExternalPlugins(); SplashScreen.stage(.70, null, "Finalizing configuration"); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginClassLoader.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginClassLoader.java new file mode 100644 index 0000000000..3be3a4ed91 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginClassLoader.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016-2017, Adam + * 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.plugins; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; + +class PluginClassLoader extends URLClassLoader +{ + private final ClassLoader parent; + + PluginClassLoader(File plugin, ClassLoader parent) throws MalformedURLException + { + // null parent classloader, or else class path scanning includes everything from the main class loader + super(new URL[]{plugin.toURI().toURL()}, null); + + this.parent = parent; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException + { + try + { + return super.loadClass(name); + } + catch (ClassNotFoundException ex) + { + // fall back to main class loader + return parent.loadClass(name); + } + } +} \ No newline at end of file diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java index 55f8405b39..b2092e8d6c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/PluginManager.java @@ -37,6 +37,7 @@ import com.google.inject.CreationException; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; +import java.io.File; import java.io.IOException; import java.lang.invoke.CallSite; import java.lang.invoke.LambdaMetafactory; @@ -87,6 +88,7 @@ public class PluginManager * Base package where the core plugins are */ private static final String PLUGIN_PACKAGE = "net.runelite.client.plugins"; + private static final File SIDELOADED_PLUGINS = new File(RuneLite.RUNELITE_DIR, "sideloaded-plugins"); private final boolean developerMode; private final boolean safeMode; @@ -275,6 +277,45 @@ public class PluginManager SplashScreen.stage(.60, .70, null, "Loading Plugins", loaded, total, false)); } + public void loadSideLoadPlugins() + { + if (!developerMode) + { + return; + } + + File[] files = SIDELOADED_PLUGINS.listFiles(); + if (files == null) + { + return; + } + + for (File f : files) + { + if (f.getName().endsWith(".jar")) + { + log.info("Side-loading plugin {}", f); + + try + { + ClassLoader classLoader = new PluginClassLoader(f, getClass().getClassLoader()); + + List> plugins = ClassPath.from(classLoader) + .getAllClasses() + .stream() + .map(ClassInfo::load) + .collect(Collectors.toList()); + + loadPlugins(plugins, null); + } + catch (PluginInstantiationException | IOException ex) + { + log.error("error sideloading plugin", ex); + } + } + } + } + public List loadPlugins(List> plugins, BiConsumer onPluginLoaded) throws PluginInstantiationException { MutableGraph> graph = GraphBuilder