Skip to content

Instantly share code, notes, and snippets.

@Geolykt
Created December 17, 2022 13:16
Show Gist options
  • Save Geolykt/dec4cb6b8e254ca6e5ead5e7a9d51b08 to your computer and use it in GitHub Desktop.
Save Geolykt/dec4cb6b8e254ca6e5ead5e7a9d51b08 to your computer and use it in GitHub Desktop.
In-memory bukkit plugins (unlikely to work as-is)
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