diff --git a/.gitignore b/.gitignore
index 8fffe724..38cd6398 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,42 @@
-target/
-pom.xml.tag
-pom.xml.releaseBackup
-pom.xml.versionsBackup
-pom.xml.next
-release.properties
-dependency-reduced-pom.xml
-buildNumber.properties
+# Eclipse stuff
+/.classpath
+/.project
+/.settings
+
+# netbeans
+/nbproject
+nb-configuration.xml
+
+# maven
+/target
+
+# vim
+.*.sw[a-p]
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+# various other potential build files
+/build
+/bin
+/dist
+/manifest.mf
+*.log
+
+# Mac filesystem dust
+.DS_Store
+
+# intellij
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# Gradle
+.gradle
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
diff --git a/README.md b/README.md
index a59f4463..46f8e780 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,7 @@
-# FastLogin
-Checks if a minecraft player has a valid paid account. If so, they can skip offline authentification.
+# mcMMOExtras
+
+A visual boss bar Bukkit plugin for mcMMO that keeps people entertained and encourages them to want to level up.
+
+See
+* http://dev.bukkit.org/bukkit-plugins/mcmmoextras/
+* http://www.curse.com/bukkit-plugins/minecraft/mcmmoextras
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..40161e20
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,149 @@
+
+ 4.0.0
+
+ com.github.games647
+
+ fastlogin
+ jar
+
+ FastLogin
+ 0.1
+ 2015
+ http://dev.bukkit.org/bukkit-plugins/fastlogin
+
+ Automatically logins premium player on a offline mode server
+
+
+
+ UTF-8
+
+ ${basedir}/target
+
+
+
+ GitHub
+ https://github.com/games647/FastLogin/issues
+
+
+
+ https://github.com/games647/FastLogin
+ scm:git:git://github.com/games647/FastLogin.git
+ scm:git:ssh://git@github.com:games647/FastLogin.git
+
+
+
+ install
+
+ ${project.name}
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.2
+
+
+ 1.8
+ 1.8
+ true
+ true
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.6
+
+ ${outputDir}
+
+
+
+
+
+
+ src/main/resources
+
+ true
+
+
+
+
+ ${basedir}
+
+ LICENSE
+
+
+
+
+
+
+
+
+ spigot-repo
+ https://hub.spigotmc.org/nexus/content/repositories/snapshots/
+
+
+
+
+ dmulloy2-repo
+ http://repo.dmulloy2.net/content/groups/public/
+
+
+
+
+ xephi-repo
+ http://ci.xephi.fr/plugin/repository/everything/
+
+
+
+
+ luricos.de-repo
+ http://repo.luricos.de/bukkit-plugins/
+
+
+
+
+
+
+ org.spigotmc
+ spigot
+ 1.8.8-R0.1-SNAPSHOT
+ provided
+
+
+
+
+ com.comphenix.protocol
+ ProtocolLib
+ 3.6.3-SNAPSHOT
+ true
+
+
+
+
+ fr.xephi
+ authme
+ 5.0-SNAPSHOT
+
+
+
+ de.luricos.bukkit
+ xAuth
+ 2.6
+
+
+ net.gravitydevelopment.updater
+ updater
+
+
+ net.ess3
+ EssentialsGroupManager
+
+
+
+
+
diff --git a/src/main/java/com/github/games647/fastlogin/FastLogin.java b/src/main/java/com/github/games647/fastlogin/FastLogin.java
new file mode 100644
index 00000000..4b6f7a2c
--- /dev/null
+++ b/src/main/java/com/github/games647/fastlogin/FastLogin.java
@@ -0,0 +1,93 @@
+package com.github.games647.fastlogin;
+
+import com.github.games647.fastlogin.listener.PlayerListener;
+import com.comphenix.protocol.ProtocolLibrary;
+import com.comphenix.protocol.ProtocolManager;
+import com.github.games647.fastlogin.listener.EncryptionPacketListener;
+import com.github.games647.fastlogin.listener.StartPacketListener;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.TimeUnit;
+
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class FastLogin extends JavaPlugin {
+
+ private final KeyPair keyPair = generateKey();
+ private final Cache session = CacheBuilder.newBuilder()
+ .expireAfterWrite(2, TimeUnit.MINUTES)
+ .build();
+
+ @Override
+ public void onEnable() {
+ if (!isEnabled()) {
+ return;
+ }
+
+ if (!getServer().getPluginManager().isPluginEnabled("AuthMe")
+ && !getServer().getPluginManager().isPluginEnabled("xAuth")) {
+ getLogger().warning("No support offline Auth plugin found. ");
+ getLogger().warning("Disabling this plugin...");
+
+ setEnabled(false);
+ return;
+ }
+
+ ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
+ protocolManager.addPacketListener(new EncryptionPacketListener(this, protocolManager));
+ protocolManager.addPacketListener(new StartPacketListener(this, protocolManager));
+
+ getServer().getPluginManager().registerEvents(new PlayerListener(this), this);
+ }
+
+ @Override
+ public void onLoad() {
+ //online mode is only changeable aftter a restart
+ if (getServer().getOnlineMode()) {
+ getLogger().severe("Server have to be in offline mode");
+
+ setEnabled(false);
+ }
+
+ generateKey();
+ }
+
+ private KeyPair generateKey() {
+ try {
+ KeyPairGenerator keypairgenerator = KeyPairGenerator.getInstance("RSA");
+
+ keypairgenerator.initialize(1024);
+ return keypairgenerator.generateKeyPair();
+ } catch (NoSuchAlgorithmException noSuchAlgorithmException) {
+ //Should be default existing in every vm
+ }
+
+ return null;
+ }
+
+ public Cache getSession() {
+ return session;
+ }
+
+ public KeyPair getKeyPair() {
+ return keyPair;
+ }
+
+ public HttpURLConnection getConnection(String url) throws IOException {
+ final HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
+ connection.setConnectTimeout(15000);
+ connection.setReadTimeout(15000);
+ connection.setRequestProperty("Content-Type", "application/json");
+ connection.setRequestProperty("User-Agent", "Premium-Checker");
+
+ return connection;
+ }
+}
diff --git a/src/main/java/com/github/games647/fastlogin/PlayerData.java b/src/main/java/com/github/games647/fastlogin/PlayerData.java
new file mode 100644
index 00000000..aabe2ff6
--- /dev/null
+++ b/src/main/java/com/github/games647/fastlogin/PlayerData.java
@@ -0,0 +1,20 @@
+package com.github.games647.fastlogin;
+
+public class PlayerData {
+
+ private final byte[] verifyToken;
+ private final String username;
+
+ public PlayerData(byte[] verifyToken, String username) {
+ this.username = username;
+ this.verifyToken = verifyToken;
+ }
+
+ public byte[] getVerifyToken() {
+ return verifyToken;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+}
diff --git a/src/main/java/com/github/games647/fastlogin/listener/EncryptionPacketListener.java b/src/main/java/com/github/games647/fastlogin/listener/EncryptionPacketListener.java
new file mode 100644
index 00000000..75b43e60
--- /dev/null
+++ b/src/main/java/com/github/games647/fastlogin/listener/EncryptionPacketListener.java
@@ -0,0 +1,145 @@
+package com.github.games647.fastlogin.listener;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.ProtocolManager;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketEvent;
+import com.comphenix.protocol.injector.server.SocketInjector;
+import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
+import com.comphenix.protocol.wrappers.WrappedGameProfile;
+import com.github.games647.fastlogin.FastLogin;
+import com.github.games647.fastlogin.PlayerData;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.net.HttpURLConnection;
+import java.security.PrivateKey;
+import java.util.Arrays;
+import java.util.logging.Level;
+
+import javax.crypto.SecretKey;
+
+import net.minecraft.server.v1_8_R3.MinecraftEncryption;
+import net.minecraft.server.v1_8_R3.NetworkManager;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+
+public class EncryptionPacketListener extends PacketAdapter {
+
+ private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?";
+
+ private final ProtocolManager protocolManager;
+ private final FastLogin fastLogin;
+
+ public EncryptionPacketListener(FastLogin plugin, ProtocolManager protocolManger) {
+ super(params(plugin, PacketType.Login.Client.ENCRYPTION_BEGIN).optionAsync());
+
+ this.fastLogin = plugin;
+ this.protocolManager = protocolManger;
+ }
+
+ /*
+ * C->S : Handshake State=2
+ * C->S : Login Start
+ * S->C : Encryption Key Request
+ * (Client Auth)
+ * C->S : Encryption Key Response
+ * (Server Auth, Both enable encryption)
+ * S->C : Login Success (*)
+ */
+ @Override
+ public void onPacketReceiving(PacketEvent event) {
+ PacketContainer packet = event.getPacket();
+ Player player = event.getPlayer();
+
+ final byte[] sharedSecret = packet.getByteArrays().read(0);
+ byte[] clientVerify = packet.getByteArrays().read(1);
+
+ PrivateKey privateKey = fastLogin.getKeyPair().getPrivate();
+
+ String addressString = player.getAddress().toString();
+ PlayerData cachedEntry = fastLogin.getSession().asMap().get(addressString);
+ byte[] serverVerify = cachedEntry.getVerifyToken();
+ if (!Arrays.equals(serverVerify, MinecraftEncryption.b(privateKey, clientVerify))) {
+ player.kickPlayer("Invalid token");
+ event.setCancelled(true);
+ return;
+ }
+
+ //encrypt all following packets
+ NetworkManager networkManager = getNetworkManager(event);
+ SecretKey loginKey = MinecraftEncryption.a(privateKey, sharedSecret);
+ networkManager.a(loginKey);
+ String serverId = (new BigInteger(MinecraftEncryption.a("", fastLogin.getKeyPair().getPublic(), loginKey)))
+ .toString(16);
+
+ String username = cachedEntry.getUsername();
+ if (!hasJoinedServer(username, serverId)) {
+ //user tried to fake a authentification
+ player.kickPlayer("Invalid session");
+ event.setCancelled(true);
+ return;
+ }
+
+ //fake a new login packet
+ PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START, true);
+ WrappedGameProfile fakeProfile = WrappedGameProfile.fromOfflinePlayer(Bukkit.getOfflinePlayer(username));
+ startPacket.getGameProfiles().write(0, fakeProfile);
+ try {
+ protocolManager.recieveClientPacket(event.getPlayer(), startPacket, false);
+ } catch (InvocationTargetException | IllegalAccessException ex) {
+ plugin.getLogger().log(Level.WARNING, null, ex);
+ }
+
+ event.setCancelled(true);
+ }
+
+ private NetworkManager getNetworkManager(PacketEvent event) throws IllegalArgumentException {
+ SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(event.getPlayer());
+ NetworkManager networkManager = null;
+ try {
+ Field declaredField = injector.getClass().getDeclaredField("injector");
+ declaredField.setAccessible(true);
+
+ Object rawInjector = declaredField.get(injector);
+
+ declaredField = rawInjector.getClass().getDeclaredField("networkManager");
+ declaredField.setAccessible(true);
+ networkManager = (NetworkManager) declaredField.get(rawInjector);
+ } catch (IllegalAccessException | NoSuchFieldException ex) {
+ plugin.getLogger().log(Level.WARNING, null, ex);
+ }
+
+ return networkManager;
+ }
+
+ private boolean hasJoinedServer(String username, String serverId) {
+ try {
+ String url = HAS_JOINED_URL + "username=" + username + "&serverId=" + serverId;
+
+ HttpURLConnection conn = fastLogin.getConnection(url);
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ String line = reader.readLine();
+ if (!line.equals("null")) {
+ JSONObject object = (JSONObject) JSONValue.parse(line);
+ String uuid = (String) object.get("id");
+ String name = (String) object.get("name");
+
+ return true;
+ }
+ } catch (IOException ex) {
+ plugin.getLogger().log(Level.WARNING, null, ex);
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/java/com/github/games647/fastlogin/listener/PlayerListener.java b/src/main/java/com/github/games647/fastlogin/listener/PlayerListener.java
new file mode 100644
index 00000000..91781900
--- /dev/null
+++ b/src/main/java/com/github/games647/fastlogin/listener/PlayerListener.java
@@ -0,0 +1,59 @@
+package com.github.games647.fastlogin.listener;
+
+import com.github.games647.fastlogin.FastLogin;
+
+import de.luricos.bukkit.xAuth.xAuth;
+import de.luricos.bukkit.xAuth.xAuthPlayer;
+import de.luricos.bukkit.xAuth.xAuthPlayer.Status;
+
+import fr.xephi.authme.api.NewAPI;
+import fr.xephi.authme.cache.limbo.LimboCache;
+
+import java.sql.Timestamp;
+
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerJoinEvent;
+
+public class PlayerListener implements Listener {
+
+ private final FastLogin plugin;
+
+ public PlayerListener(FastLogin plugin) {
+ this.plugin = plugin;
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onJoin(PlayerJoinEvent joinEvent) {
+ final Player player = joinEvent.getPlayer();
+ String address = player.getAddress().toString();
+ if (plugin.getSession().asMap().containsKey(address)) {
+ Bukkit.getScheduler().runTaskLater(plugin, () -> {
+ doLogin(player);
+ }, 1 * 20L);
+ }
+ }
+
+ private void doLogin(Player player) {
+ if (Bukkit.getPluginManager().isPluginEnabled("AuthMe")) {
+ //add cache entry - otherwise loggin wouldn't work
+ LimboCache.getInstance().addLimboPlayer(player);
+
+ //skips registration and login
+ NewAPI.getInstance().forceLogin(player);
+ } else if (Bukkit.getPluginManager().isPluginEnabled("xAuth")) {
+ xAuth xAuthPlugin = xAuth.getPlugin();
+
+ xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
+ xAuthPlayer.setPremium(true);
+ xAuthPlugin.getAuthClass(xAuthPlayer).online(xAuthPlayer.getName());
+ xAuthPlayer.setLoginTime(new Timestamp(System.currentTimeMillis()));
+
+ xAuthPlayer.setStatus(Status.AUTHENTICATED);
+
+ xAuthPlugin.getPlayerManager().unprotect(xAuthPlayer);
+ }
+ }
+}
diff --git a/src/main/java/com/github/games647/fastlogin/listener/StartPacketListener.java b/src/main/java/com/github/games647/fastlogin/listener/StartPacketListener.java
new file mode 100644
index 00000000..b0f3dfb5
--- /dev/null
+++ b/src/main/java/com/github/games647/fastlogin/listener/StartPacketListener.java
@@ -0,0 +1,90 @@
+package com.github.games647.fastlogin.listener;
+
+import com.comphenix.protocol.PacketType;
+import com.comphenix.protocol.ProtocolManager;
+import com.comphenix.protocol.events.PacketAdapter;
+import com.comphenix.protocol.events.PacketContainer;
+import com.comphenix.protocol.events.PacketEvent;
+import com.github.games647.fastlogin.FastLogin;
+import com.github.games647.fastlogin.PlayerData;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.net.HttpURLConnection;
+import java.security.PublicKey;
+
+import java.util.Random;
+import java.util.logging.Level;
+
+import org.bukkit.entity.Player;
+
+public class StartPacketListener extends PacketAdapter {
+
+ //only premium members have a uuid from there
+ private static final String UUID_LINK = "https://api.mojang.com/users/profiles/minecraft/";
+
+ private final ProtocolManager protocolManager;
+ private final FastLogin fastLogin;
+
+ private final Random random = new Random();
+
+ public StartPacketListener(FastLogin plugin, ProtocolManager protocolManger) {
+ super(params(plugin, PacketType.Login.Client.START).optionAsync());
+
+ this.fastLogin = plugin;
+ this.protocolManager = protocolManger;
+ }
+
+ /*
+ * C->S : Handshake State=2
+ * C->S : Login Start
+ * S->C : Encryption Key Request
+ * (Client Auth)
+ * C->S : Encryption Key Response
+ * (Server Auth, Both enable encryption)
+ * S->C : Login Success (*)
+ */
+ @Override
+ public void onPacketReceiving(PacketEvent packetEvent) {
+ PacketContainer packet = packetEvent.getPacket();
+ Player player = packetEvent.getPlayer();
+
+ String username = packet.getGameProfiles().read(0).getName();
+ if (isPremium(username)) {
+ //do premium login process
+ try {
+ PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN, true);
+
+ //constr ServerID=""
+ //public key=plugin.getPublic
+ newPacket.getSpecificModifier(PublicKey.class).write(0, fastLogin.getKeyPair().getPublic());
+ byte[] verifyToken = new byte[4];
+ random.nextBytes(verifyToken);
+ newPacket.getByteArrays().write(0, verifyToken);
+
+ String addressString = player.getAddress().toString();
+ fastLogin.getSession().asMap().put(addressString, new PlayerData(verifyToken, username));
+
+ protocolManager.sendServerPacket(player, newPacket, false);
+ } catch (InvocationTargetException ex) {
+ plugin.getLogger().log(Level.SEVERE, null, ex);
+ }
+
+ //cancel only if the player is premium
+ packetEvent.setCancelled(true);
+ }
+ }
+
+ private boolean isPremium(String playerName) {
+ try {
+ final HttpURLConnection connection = fastLogin.getConnection(UUID_LINK + playerName);
+ final int responseCode = connection.getResponseCode();
+
+ return responseCode == HttpURLConnection.HTTP_OK;
+ } catch (IOException ex) {
+ plugin.getLogger().log(Level.SEVERE, null, ex);
+ }
+
+ return false;
+ }
+}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 00000000..92b3ab2b
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,14 @@
+# project informations for Bukkit in order to register our plugin with all it components
+# ${project.name} are variables from Maven (pom.xml) which will be replaced after the build
+name: ${project.name}
+version: ${project.version}
+main: ${project.groupId}.${project.artifactId}.${project.name}
+
+# meta informations for plugin managers
+authors: [Xeroun, games647, 'https://github.com/games647/FastLogin/graphs/contributors']
+description: |
+ ${project.description}
+website: ${project.url}
+dev-url: ${project.url}
+
+depend: [ProtocolLib]
\ No newline at end of file