diff --git a/src/main/java/net/md_5/bungee/BungeeCord.java b/src/main/java/net/md_5/bungee/BungeeCord.java index 34e537c9..9aa59e3c 100644 --- a/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/src/main/java/net/md_5/bungee/BungeeCord.java @@ -18,6 +18,7 @@ import net.md_5.bungee.command.CommandList; import net.md_5.bungee.command.CommandSender; import net.md_5.bungee.command.CommandServer; import net.md_5.bungee.command.ConsoleCommandSender; +import net.md_5.bungee.plugin.JavaPluginManager; public class BungeeCord { @@ -56,7 +57,11 @@ public class BungeeCord { /** * Registered commands. */ - private Map commandMap = new HashMap<>(); + public Map commandMap = new HashMap<>(); + /** + * Plugin manager. + */ + public final JavaPluginManager pluginManager = new JavaPluginManager(); { commandMap.put("end", new CommandEnd()); @@ -105,6 +110,8 @@ public class BungeeCord { config.load(); isRunning = true; + pluginManager.loadPlugins(); + InetSocketAddress addr = Util.getAddr(config.bindHost); listener = new ListenThread(addr); listener.start(); @@ -115,6 +122,8 @@ public class BungeeCord { public void stop() { this.isRunning = false; + $().info("Disabling plugin"); + pluginManager.onDisable(); $().info("Closing listen thread"); try { diff --git a/src/main/java/net/md_5/bungee/InitialHandler.java b/src/main/java/net/md_5/bungee/InitialHandler.java index 968b1f35..52b2ea3e 100644 --- a/src/main/java/net/md_5/bungee/InitialHandler.java +++ b/src/main/java/net/md_5/bungee/InitialHandler.java @@ -9,6 +9,7 @@ import net.md_5.bungee.packet.PacketFCEncryptionResponse; import net.md_5.bungee.packet.PacketFDEncryptionRequest; import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketInputStream; +import net.md_5.bungee.plugin.ConnectEvent; import org.bouncycastle.crypto.io.CipherInputStream; import org.bouncycastle.crypto.io.CipherOutputStream; @@ -32,6 +33,13 @@ public class InitialHandler implements Runnable { switch (id) { case 0x02: Packet2Handshake handshake = new Packet2Handshake(packet); + // fire connect event + ConnectEvent event = new ConnectEvent(handshake.username, socket.getInetAddress()); + BungeeCord.instance.pluginManager.onConnect(event); + if (event.isCancelled()) { + throw new KickException(event.getCancelReason()); + } + PacketFDEncryptionRequest request = EncryptionUtil.encryptRequest(); out.write(request.getPacket()); PacketFCEncryptionResponse response = new PacketFCEncryptionResponse(in.readPacket()); diff --git a/src/main/java/net/md_5/bungee/plugin/Cancellable.java b/src/main/java/net/md_5/bungee/plugin/Cancellable.java new file mode 100644 index 00000000..3c7359fd --- /dev/null +++ b/src/main/java/net/md_5/bungee/plugin/Cancellable.java @@ -0,0 +1,8 @@ +package net.md_5.bungee.plugin; + +public interface Cancellable { + + public void setCancelled(boolean cancelled); + + public boolean isCancelled(); +} diff --git a/src/main/java/net/md_5/bungee/plugin/ConnectEvent.java b/src/main/java/net/md_5/bungee/plugin/ConnectEvent.java new file mode 100644 index 00000000..71c3830a --- /dev/null +++ b/src/main/java/net/md_5/bungee/plugin/ConnectEvent.java @@ -0,0 +1,13 @@ +package net.md_5.bungee.plugin; + +import java.net.InetAddress; +import lombok.Data; + +@Data +public class ConnectEvent implements Cancellable { + + private boolean cancelled; + private String cancelReason; + private final String username; + private final InetAddress address; +} diff --git a/src/main/java/net/md_5/bungee/plugin/InvalidPluginException.java b/src/main/java/net/md_5/bungee/plugin/InvalidPluginException.java new file mode 100644 index 00000000..1710fe1c --- /dev/null +++ b/src/main/java/net/md_5/bungee/plugin/InvalidPluginException.java @@ -0,0 +1,14 @@ +package net.md_5.bungee.plugin; + +public class InvalidPluginException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public InvalidPluginException(String message, Throwable cause) { + super(message, cause); + } + + public InvalidPluginException(String message) { + super(message); + } +} diff --git a/src/main/java/net/md_5/bungee/plugin/JavaPlugin.java b/src/main/java/net/md_5/bungee/plugin/JavaPlugin.java new file mode 100644 index 00000000..6b7a3e6c --- /dev/null +++ b/src/main/java/net/md_5/bungee/plugin/JavaPlugin.java @@ -0,0 +1,38 @@ +package net.md_5.bungee.plugin; + +import net.md_5.bungee.BungeeCord; +import net.md_5.bungee.command.Command; + +public abstract class JavaPlugin { + + /** + * Description file. + */ + PluginDescription description; + + /** + * Called on enable. + */ + public void onEnable() { + } + + /** + * Called on disable. + */ + public void onDisable() { + } + + /** + * Called when a user connects with their name and address. To keep things + * simple this name has not been checked with minecraft.net. + */ + public void onConnect(ConnectEvent event) { + } + + /** + * Register a command. + */ + protected void registerCommand(String label, Command command) { + BungeeCord.instance.commandMap.put(label, command); + } +} diff --git a/src/main/java/net/md_5/bungee/plugin/JavaPluginManager.java b/src/main/java/net/md_5/bungee/plugin/JavaPluginManager.java new file mode 100644 index 00000000..b4cb648f --- /dev/null +++ b/src/main/java/net/md_5/bungee/plugin/JavaPluginManager.java @@ -0,0 +1,66 @@ +package net.md_5.bungee.plugin; + +import com.google.common.io.PatternFilenameFilter; +import java.io.File; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; +import lombok.Getter; +import static net.md_5.bungee.Logger.$; + +public class JavaPluginManager extends JavaPlugin { + + @Getter + private final Set plugins = new HashSet<>(); + + public void loadPlugins() { + File dir = new File("plugins"); + dir.mkdir(); + + for (File file : dir.listFiles(new PatternFilenameFilter(".*\\.jar"))) { + try { + JarFile jar = new JarFile(file); + ZipEntry entry = jar.getEntry("plugin.yml"); + if (entry == null) { + throw new InvalidPluginException("Jar does not contain a plugin.yml"); + } + + PluginDescription description; + try (InputStream is = jar.getInputStream(entry)) { + description = PluginDescription.load(is); + } + URLClassLoader classloader = new URLClassLoader(new URL[]{file.toURI().toURL()}, getClass().getClassLoader()); + Class clazz = Class.forName(description.getMain(), true, classloader); + Class subClazz = clazz.asSubclass(JavaPlugin.class); + JavaPlugin plugin = subClazz.getDeclaredConstructor().newInstance(); + + plugin.description = description; + plugin.onEnable(); + plugins.add(plugin); + + $().info("Loaded plugin: " + plugin.description.getName()); + } catch (Exception ex) { + $().severe("Could not load plugin: " + file); + ex.printStackTrace(); + } + } + } + + @Override + public void onDisable() { + for (JavaPlugin p : plugins) { + p.onDisable(); + } + } + + @Override + public void onConnect(ConnectEvent event) { + for (JavaPlugin p : plugins) { + p.onConnect(event); + } + } +} diff --git a/src/main/java/net/md_5/bungee/plugin/PluginDescription.java b/src/main/java/net/md_5/bungee/plugin/PluginDescription.java new file mode 100644 index 00000000..fea40c77 --- /dev/null +++ b/src/main/java/net/md_5/bungee/plugin/PluginDescription.java @@ -0,0 +1,36 @@ +package net.md_5.bungee.plugin; + +import java.io.InputStream; +import java.lang.reflect.Field; +import lombok.Data; +import org.yaml.snakeyaml.Yaml; + +@Data +public class PluginDescription { + + private String name; + private String main; + private String version; + private String author; + + private PluginDescription() { + } + + public static PluginDescription load(InputStream is) { + PluginDescription ret = new Yaml().loadAs(is, PluginDescription.class); + if (ret == null) { + throw new InvalidPluginException("Could not load plugin description file."); + } + + for (Field f : PluginDescription.class.getDeclaredFields()) { + try { + if (f.get(ret) == null) { + throw new InvalidPluginException(f.getName() + " is not set properly in plugin description"); + } + } catch (IllegalArgumentException | IllegalAccessException ex) { + } + } + + return ret; + } +}