diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BukkitSessionManager.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BukkitSessionManager.java new file mode 100644 index 00000000..4db0f4b3 --- /dev/null +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BukkitSessionManager.java @@ -0,0 +1,23 @@ +package com.github.games647.fastlogin.bukkit; + +import com.github.games647.fastlogin.core.SessionManager; + +import java.net.InetSocketAddress; +import java.util.UUID; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +public class BukkitSessionManager extends SessionManager + implements Listener { + + @EventHandler + @Override + public void onPlayQuit(PlayerQuitEvent quitEvent) { + Player player = quitEvent.getPlayer(); + UUID playerId = player.getUniqueId(); + endPlaySession(playerId); + } +} diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java index eacfdfed..072c3a68 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java @@ -10,29 +10,30 @@ import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSup import com.github.games647.fastlogin.bukkit.task.DelayedAuthHook; import com.github.games647.fastlogin.core.CommonUtil; import com.github.games647.fastlogin.core.PremiumStatus; +import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.shared.FastLoginCore; import com.github.games647.fastlogin.core.shared.PlatformPlugin; + import io.papermc.lib.PaperLib; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.slf4j.Logger; -import java.net.InetSocketAddress; -import java.nio.file.Path; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - /** * This plugin checks if a player has a paid account and if so tries to skip offline mode authentication. */ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin { - //1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang) - private final ConcurrentMap loginSession = CommonUtil.buildCache(1, -1); + private final BukkitSessionManager sessionManager = new BukkitSessionManager(); private final Map premiumPlayers = new ConcurrentHashMap<>(); private final Logger logger; @@ -108,7 +109,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin getLoginSessions() { - return loginSession; - } - - public BukkitLoginSession getSession(InetSocketAddress addr) { - String id = getSessionId(addr); - return loginSession.get(id); - } - - public String getSessionId(InetSocketAddress addr) { - return addr.getAddress().getHostAddress() + ':' + addr.getPort(); - } - - public void putSession(InetSocketAddress addr, BukkitLoginSession session) { - String id = getSessionId(addr); - loginSession.put(id, session); - } - - public void removeSession(InetSocketAddress addr) { - String id = getSessionId(addr); - loginSession.remove(id); - } - - public Map getPremiumPlayers() { - return premiumPlayers; - } - /** * Fetches the premium status of an online player. * * @param onlinePlayer * @return the online status or unknown if an error happened, the player isn't online or BungeeCord doesn't send * us the status message yet (This means you cannot check the login status on the PlayerJoinEvent). + * @deprecated this method could be removed in future versions and exists only as a temporarily solution */ + @Deprecated public PremiumStatus getStatus(UUID onlinePlayer) { - return premiumPlayers.getOrDefault(onlinePlayer, PremiumStatus.UNKNOWN); + StoredProfile playSession = sessionManager.getPlaySession(onlinePlayer); + return Optional.ofNullable(playSession).map(profile -> { + if (profile.isPremium()) + return PremiumStatus.PREMIUM; + return PremiumStatus.CRACKED; + }).orElse(PremiumStatus.UNKNOWN); + } + + /** + * Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid + * account) + * + * @return a thread-safe loginSession map + */ + public BukkitSessionManager getSessionManager() { + return sessionManager; + } + + public Map getPremiumPlayers() { + return premiumPlayers; } /** diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/AuthMeHook.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/AuthMeHook.java index 0a5f5247..6fd6ef90 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/AuthMeHook.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/AuthMeHook.java @@ -1,20 +1,22 @@ package com.github.games647.fastlogin.bukkit.hook; -import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; +import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.hooks.AuthPlugin; + import fr.xephi.authme.api.v3.AuthMeApi; import fr.xephi.authme.events.RestoreSessionEvent; import fr.xephi.authme.process.Management; import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams; import fr.xephi.authme.process.register.executors.RegistrationMethod; + +import java.lang.reflect.Field; + import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import java.lang.reflect.Field; - /** * GitHub: https://github.com/Xephi/AuthMeReloaded/ *

@@ -50,8 +52,8 @@ public class AuthMeHook implements AuthPlugin, Listener { public void onSessionRestore(RestoreSessionEvent restoreSessionEvent) { Player player = restoreSessionEvent.getPlayer(); - BukkitLoginSession session = plugin.getSession(player.getAddress()); - if (session != null && session.isVerified()) { + StoredProfile session = plugin.getSessionManager().getPlaySession(player.getUniqueId()); + if (session != null && session.isPremium()) { restoreSessionEvent.setCancelled(true); } } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/BungeeListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/BungeeListener.java index 516b107c..d8c5a675 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/BungeeListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/BungeeListener.java @@ -101,7 +101,7 @@ public class BungeeListener implements PluginMessageListener { private void startLoginTaskIfReady(Player player, BukkitLoginSession session) { session.setVerified(true); - plugin.putSession(player.getAddress(), session); + plugin.getSessionManager().startLoginSession(player.getAddress(), session); // only start a new login task if the join event fired earlier. This event then didn boolean result = plugin.getBungeeManager().didJoinEventFired(player); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ConnectionListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ConnectionListener.java index dc76a34a..9db44014 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ConnectionListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ConnectionListener.java @@ -44,11 +44,8 @@ public class ConnectionListener implements Listener { // session exists so the player is ready for force login // cases: Paper (firing BungeeCord message before PlayerJoinEvent) or not running BungeeCord and already // having the login session from the login process - BukkitLoginSession session = plugin.getSession(player.getAddress()); - if (session == null) { - String sessionId = plugin.getSessionId(player.getAddress()); - plugin.getLog().info("No on-going login session for player: {} with ID {}", player, sessionId); - } else { + BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress()); + if (session != null) { Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session); Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask); } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/PaperPreLoginListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/PaperPreLoginListener.java index 167ae006..9acad66c 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/PaperPreLoginListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/PaperPreLoginListener.java @@ -27,7 +27,7 @@ public class PaperPreLoginListener implements Listener { } // event gives us only IP, not the port, so we need to loop through all the sessions - for (BukkitLoginSession session : plugin.getLoginSessions().values()) { + for (BukkitLoginSession session : plugin.getSessionManager().getLoginSessions().values()) { if (!event.getName().equals(session.getUsername())) { continue; } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/NameCheckTask.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/NameCheckTask.java index 67249722..1283937b 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/NameCheckTask.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/NameCheckTask.java @@ -74,7 +74,7 @@ public class NameCheckTask extends JoinManagement GAME_PROFILE = MinecraftReflection.getGameProfileClass(); @@ -40,28 +38,15 @@ public class SkinApplyListener implements Listener { //go through every session, because player.getAddress is null //loginEvent.getAddress is just a InetAddress not InetSocketAddress, so not unique enough - for (BukkitLoginSession session : plugin.getLoginSessions().values()) { - if (session.getUsername().equals(player.getName())) { - session.getSkin().ifPresent(skin -> applySkin(player, skin.getValue(), skin.getSignature())); - break; - } + BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress()); + if (session.getUsername().equals(player.getName())) { + session.getSkin().ifPresent(skin -> applySkin(player, skin.getValue(), skin.getSignature())); } } private void applySkin(Player player, String skinData, String signature) { WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player); - WrappedSignedProperty skin = WrappedSignedProperty.fromValues(Textures.KEY, skinData, signature); - try { - gameProfile.getProperties().put(Textures.KEY, skin); - } catch (ClassCastException castException) { - //Cauldron, MCPC, Thermos, ... - Object map = GET_PROPERTIES.invoke(gameProfile.getHandle()); - try { - MethodUtils.invokeMethod(map, "put", new Object[]{Textures.KEY, skin.getHandle()}); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { - plugin.getLog().error("Error setting premium skin of: {}", player, ex); - } - } + gameProfile.getProperties().put(Textures.KEY, skin); } } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/VerifyResponseTask.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/VerifyResponseTask.java index 3013a488..559d5999 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/VerifyResponseTask.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/VerifyResponseTask.java @@ -14,10 +14,7 @@ import com.github.games647.craftapi.model.skin.SkinProperty; import com.github.games647.craftapi.resolver.MojangResolver; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; -import org.bukkit.entity.Player; -import javax.crypto.Cipher; -import javax.crypto.SecretKey; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -31,6 +28,11 @@ import java.util.Arrays; import java.util.Optional; import java.util.UUID; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; + +import org.bukkit.entity.Player; + import static com.comphenix.protocol.PacketType.Login.Client.START; import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT; @@ -59,7 +61,7 @@ public class VerifyResponseTask implements Runnable { @Override public void run() { try { - BukkitLoginSession session = plugin.getSession(player.getAddress()); + BukkitLoginSession session = plugin.getSessionManager().getLoginSession(player.getAddress()); if (session == null) { disconnect("invalid-request", true , "GameProfile {0} tried to send encryption response at invalid state", player.getAddress()); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java index ca8fb755..2476e9cc 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java @@ -48,7 +48,7 @@ public class ProtocolSupportListener extends JoinManagement + implements Listener { + + //todo: memory leak on cancelled login event + @EventHandler + public void onPlayQuit(PlayerDisconnectEvent disconnectEvent) { + ProxiedPlayer player = disconnectEvent.getPlayer(); + UUID playerId = player.getUniqueId(); + endPlaySession(playerId); + } +} diff --git a/core/src/main/java/com/github/games647/fastlogin/core/SessionManager.java b/core/src/main/java/com/github/games647/fastlogin/core/SessionManager.java new file mode 100644 index 00000000..637a0264 --- /dev/null +++ b/core/src/main/java/com/github/games647/fastlogin/core/SessionManager.java @@ -0,0 +1,61 @@ +package com.github.games647.fastlogin.core; + +import com.github.games647.fastlogin.core.shared.LoginSession; +import com.google.common.collect.MapMaker; + +import java.util.UUID; +import java.util.concurrent.ConcurrentMap; + +/** + * Manages player connection sessions. Login sessions that are only valid through the login process and play + * sessions that hold the stored profile object after the login process is finished and until the player leaves the + * server (Spigot) or proxy (BungeeCord). + * + * @param connection object + * @param platform dependent login session + */ +public abstract class SessionManager { + + // 1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang) + // these login sessions are only meant for during the login process not be used after + private final ConcurrentMap loginSessions = CommonUtil.buildCache(1, 0); + private final ConcurrentMap playSessions = new MapMaker().makeMap(); + + public void startLoginSession(C connectionId, S session) { + loginSessions.put(connectionId, session); + } + + public S getLoginSession(C connectionId) { + return loginSessions.get(connectionId); + } + + public void endLoginSession(C connectionId) { + loginSessions.remove(connectionId); + } + + public ConcurrentMap getLoginSessions() { + return loginSessions; + } + + public S promoteSession(C connectionId, UUID playerId) { + S loginSession = loginSessions.remove(connectionId); + StoredProfile profile = loginSession.getProfile(); + playSessions.put(playerId, profile); + return loginSession; + } + + public StoredProfile getPlaySession(UUID playerId) { + return playSessions.get(playerId); + } + + public void endPlaySession(UUID playerId) { + playSessions.remove(playerId); + } + + public abstract void onPlayQuit(E quitEvent); + + public void clear() { + loginSessions.clear(); + playSessions.clear(); + } +}