Created
December 17, 2022 13:16
-
-
Save Geolykt/dec4cb6b8e254ca6e5ead5e7a9d51b08 to your computer and use it in GitHub Desktop.
In-memory bukkit plugins (unlikely to work as-is)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.io.ByteArrayInputStream; | |
import java.io.File; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.UncheckedIOException; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
import java.net.URL; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.jar.JarEntry; | |
import java.util.jar.JarInputStream; | |
import java.util.logging.Level; | |
import java.util.regex.Pattern; | |
import org.bukkit.Bukkit; | |
import org.bukkit.event.Event; | |
import org.bukkit.event.Listener; | |
import org.bukkit.event.server.PluginEnableEvent; | |
import org.bukkit.plugin.InvalidDescriptionException; | |
import org.bukkit.plugin.InvalidPluginException; | |
import org.bukkit.plugin.Plugin; | |
import org.bukkit.plugin.PluginDescriptionFile; | |
import org.bukkit.plugin.PluginLoader; | |
import org.bukkit.plugin.RegisteredListener; | |
import org.bukkit.plugin.UnknownDependencyException; | |
import org.bukkit.plugin.java.JavaPlugin; | |
import org.jetbrains.annotations.NotNull; | |
public class Test { | |
private static class TestClassloader extends ClassLoader { | |
private final Map<String, byte[]> resources; | |
public TestClassloader(Map<String, byte[]> resources) { | |
this.resources = resources; | |
} | |
@Override | |
public Class<?> findClass(String name) throws ClassNotFoundException { | |
String s = name.replace('.', '/') + ".class"; | |
byte[] a = resources.get(s); | |
if (a != null) { | |
return defineClass(name, a, 0, a.length); | |
} | |
throw new ClassNotFoundException(name); | |
} | |
@Override | |
public InputStream getResourceAsStream(String name) { | |
byte[] a = resources.get(name); | |
if (a != null) { | |
return new ByteArrayInputStream(a); | |
} | |
return null; | |
} | |
} | |
public static void main(String file) { | |
Map<String, byte[]> bytes = new HashMap<>(); | |
// I'd recommend using LL-Java-ZIP by Col-E as that would allow loading many obfuscated jars | |
// JarInputStream has it's flaws and might be incapable of reading certain jar-files. | |
try (JarInputStream jarIn = new JarInputStream(new URL(file).openStream())) { | |
for (JarEntry entry = jarIn.getNextJarEntry(); entry != null; entry = jarIn.getNextJarEntry()) { | |
String name = entry.getName(); | |
if (name.codePointBefore(name.length()) == '/') { // This emulates a common obfuscation technique | |
name = name.substring(0, name.length() - 1); | |
} | |
if (name.codePointAt(0) == '/') { // This just annoys me | |
name = name.substring(1); | |
} | |
bytes.put(name, jarIn.readAllBytes()); | |
} | |
} catch (IOException e) { | |
throw new UncheckedIOException(e); | |
} | |
try { | |
TestClassloader cl = new TestClassloader(bytes); | |
PluginDescriptionFile description = new PluginDescriptionFile(cl.getResourceAsStream("plugin.yml")); | |
// Copied from Bukkit's JavaPluginLoader | |
final File parentFile = Bukkit.getServer().getPluginsFolder(); // Paper | |
final File dataFolder = new File(parentFile, description.getName()); | |
@SuppressWarnings("deprecation") | |
final File oldDataFolder = new File(parentFile, description.getRawName()); | |
// Found old data folder | |
if (dataFolder.equals(oldDataFolder)) { | |
// They are equal -- nothing needs to be done! | |
} else if (dataFolder.isDirectory() && oldDataFolder.isDirectory()) { | |
Bukkit.getServer().getLogger().warning(String.format( | |
"While loading %s (%s) found old-data folder: `%s' next to the new one `%s'", | |
description.getFullName(), | |
file, | |
oldDataFolder, | |
dataFolder | |
)); | |
} else if (oldDataFolder.isDirectory() && !dataFolder.exists()) { | |
if (!oldDataFolder.renameTo(dataFolder)) { | |
throw new InvalidPluginException("Unable to rename old data folder: `" + oldDataFolder + "' to: `" + dataFolder + "'"); | |
} | |
Bukkit.getServer().getLogger().log(Level.INFO, String.format( | |
"While loading %s (%s) renamed data folder: `%s' to `%s'", | |
description.getFullName(), | |
file, | |
oldDataFolder, | |
dataFolder | |
)); | |
} | |
if (dataFolder.exists() && !dataFolder.isDirectory()) { | |
throw new InvalidPluginException(String.format( | |
"Projected datafolder: `%s' for %s (%s) exists and is not a directory", | |
dataFolder, | |
description.getFullName(), | |
file | |
)); | |
} | |
Set<String> missingHardDependencies = new HashSet<>(description.getDepend().size()); // Paper - list all missing hard depends | |
for (final String pluginName : description.getDepend()) { | |
Plugin current = Bukkit.getServer().getPluginManager().getPlugin(pluginName); | |
if (current == null) { | |
missingHardDependencies.add(pluginName); // Paper - list all missing hard depends | |
} | |
} | |
// Paper start - list all missing hard depends | |
if (!missingHardDependencies.isEmpty()) { | |
throw new UnknownDependencyException(missingHardDependencies, description.getFullName()); | |
} | |
// Paper end | |
Bukkit.getServer().getUnsafe().checkSupported(description); | |
JavaPlugin plugin; | |
try { | |
Class<?> jarClass; | |
try { | |
jarClass = Class.forName(description.getMain(), true, cl); | |
} catch (ClassNotFoundException ex) { | |
throw new InvalidPluginException("Cannot find main class `" + description.getMain() + "'", ex); | |
} | |
Class<? extends JavaPlugin> pluginClass; | |
try { | |
pluginClass = jarClass.asSubclass(JavaPlugin.class); | |
} catch (ClassCastException ex) { | |
throw new InvalidPluginException("main class `" + description.getMain() + "' does not extend JavaPlugin", ex); | |
} | |
plugin = pluginClass.newInstance(); | |
} catch (IllegalAccessException ex) { | |
throw new InvalidPluginException("No public constructor", ex); | |
} catch (InstantiationException ex) { | |
throw new InvalidPluginException("Abnormal plugin type", ex); | |
} | |
if (plugin == null) { | |
throw new IllegalStateException(); | |
} | |
Field loader = plugin.getClass().getDeclaredField("loader"); | |
loader.setAccessible(true); | |
loader.set(plugin, new PluginLoader() { | |
@Override | |
@NotNull | |
public Plugin loadPlugin(@NotNull File file) | |
throws InvalidPluginException, UnknownDependencyException { | |
throw new InvalidPluginException(); | |
} | |
@Override | |
@NotNull | |
public Pattern[] getPluginFileFilters() { | |
return new Pattern[0]; | |
} | |
@Override | |
@NotNull | |
public PluginDescriptionFile getPluginDescription(@NotNull File file) | |
throws InvalidDescriptionException { | |
throw new InvalidDescriptionException(); | |
} | |
@Override | |
public void enablePlugin(@NotNull Plugin plugin) { | |
// TODO Auto-generated method stub | |
} | |
@Override | |
public void disablePlugin(@NotNull Plugin plugin) { | |
// TODO Auto-generated method stub | |
} | |
@Override | |
@NotNull | |
public Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners( | |
@NotNull Listener listener, @NotNull Plugin plugin) { | |
throw new UnsupportedOperationException(); | |
} | |
}); | |
loader.setAccessible(false); | |
try { | |
Method m = plugin.getClass().getMethod("setEnabled", boolean.class); | |
m.setAccessible(true); | |
m.invoke(plugin, true); | |
m.setAccessible(false); | |
} catch (Throwable ex) { | |
Bukkit.getServer().getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); | |
// Paper start - Disable plugins that fail to load | |
Bukkit.getServer().getPluginManager().disablePlugin(plugin); | |
return; | |
// Paper end | |
} | |
// Perhaps abort here, rather than continue going, but as it stands, | |
// an abort is not possible the way it's currently written | |
Bukkit.getServer().getPluginManager().callEvent(new PluginEnableEvent(plugin)); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment