From 51d0aefbf36defe77140592f9573c3dfa5519d8c Mon Sep 17 00:00:00 2001 From: games647 Date: Tue, 31 May 2016 17:46:49 +0200 Subject: [PATCH] Added support of detecting name changes (Fixes #18) --- CHANGELOG.md | 1 + README.md | 1 + .../fastlogin/bukkit/hooks/FakePlayer.java | 1 + .../bukkit/hooks/LoginSecurityHook.java | 2 +- .../listener/ProtocolSupportListener.java | 30 +++-- .../listener/packet/StartPacketListener.java | 13 +- .../fastlogin/bungee/AsyncPremiumCheck.java | 12 ++ .../fastlogin/bungee/FastLoginBungee.java | 7 +- .../games647/fastlogin/core/Storage.java | 124 +++++++++++++----- core/src/main/resources/config.yml | 44 +++++++ .../fastlogin/core/FastLoginCore.class | Bin 0 -> 2656 bytes .../fastlogin/core/LoginSession.class | Bin 0 -> 982 bytes .../fastlogin/core/MojangApiConnector.class | Bin 0 -> 3100 bytes .../fastlogin/core/PlayerProfile.class | Bin 0 -> 1969 bytes .../games647/fastlogin/core/Storage.class | Bin 0 -> 7690 bytes 15 files changed, 186 insertions(+), 49 deletions(-) create mode 100644 out/production/main2/com/github/games647/fastlogin/core/FastLoginCore.class create mode 100644 out/production/main2/com/github/games647/fastlogin/core/LoginSession.class create mode 100644 out/production/main2/com/github/games647/fastlogin/core/MojangApiConnector.class create mode 100644 out/production/main2/com/github/games647/fastlogin/core/PlayerProfile.class create mode 100644 out/production/main2/com/github/games647/fastlogin/core/Storage.class diff --git a/CHANGELOG.md b/CHANGELOG.md index 9240b11e..b470dcc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ######1.4 * Added Bungee setAuthPlugin method +* Added nameChangeCheck * Multiple BungeeCord support ######1.3.1 diff --git a/README.md b/README.md index f8822a52..bc4c1245 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ So they don't need to enter passwords. This is also called auto login (auto-logi * Forge/Sponge message support * Premium UUID support * Forwards Skins +* Detect user name changed and will update the existing database record * BungeeCord support * Auto register new premium players * Plugin: ProtocolSupport is supported and can be used as an alternative to ProtocolLib diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/FakePlayer.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/FakePlayer.java index 541d051a..50c4753f 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/FakePlayer.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/FakePlayer.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; + import org.bukkit.Achievement; import org.bukkit.Effect; import org.bukkit.EntityEffect; diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/LoginSecurityHook.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/LoginSecurityHook.java index e22e2ba3..90ad55be 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/LoginSecurityHook.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hooks/LoginSecurityHook.java @@ -71,7 +71,7 @@ public class LoginSecurityHook implements BukkitAuthPlugin { } @Override - public boolean forceRegister(final Player player, final String password) { + public boolean forceRegister(Player player, String password) { DataManager dataManager = securityPlugin.data; UUID playerUUID = player.getUniqueId(); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java index 532d5089..e981dea9 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java @@ -1,7 +1,7 @@ package com.github.games647.fastlogin.bukkit.listener; -import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; +import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin; import com.github.games647.fastlogin.core.PlayerProfile; @@ -40,20 +40,31 @@ public class ProtocolSupportListener implements Listener { return; } - PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(username); - if (playerProfile != null) { - if (playerProfile.isPremium()) { - if (playerProfile.getUserId() != -1) { - startPremiumSession(username, loginStartEvent, true, playerProfile); + PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username); + if (profile != null) { + if (profile.isPremium()) { + if (profile.getUserId() != -1) { + startPremiumSession(username, loginStartEvent, true, profile); } - } else if (playerProfile.getUserId() == -1) { + } else if (profile.getUserId() == -1) { //user not exists in the db try { + if (plugin.getConfig().getBoolean("nameChangeCheck")) { + UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); + if (premiumUUID != null) { + profile = plugin.getCore().getStorage().loadProfile(premiumUUID); + if (profile != null) { + plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID); + startPremiumSession(username, loginStartEvent, false, profile); + } + } + } + if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) { UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); if (premiumUUID != null) { plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username); - startPremiumSession(username, loginStartEvent, false, playerProfile); + startPremiumSession(username, loginStartEvent, false, profile); } } } catch (Exception ex) { @@ -75,8 +86,7 @@ public class ProtocolSupportListener implements Listener { } } - private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered - , PlayerProfile playerProfile) { + private void startPremiumSession(String playerName, PlayerLoginStartEvent loginStartEvent, boolean registered, PlayerProfile playerProfile) { loginStartEvent.setOnlineMode(true); InetSocketAddress address = loginStartEvent.getAddress(); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/packet/StartPacketListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/packet/StartPacketListener.java index 41b5d711..04f5a10d 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/packet/StartPacketListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/packet/StartPacketListener.java @@ -5,8 +5,8 @@ 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.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; +import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin; import com.github.games647.fastlogin.core.PlayerProfile; @@ -92,6 +92,17 @@ public class StartPacketListener extends PacketAdapter { } else if (profile.getUserId() == -1) { //user not exists in the db try { + if (plugin.getConfig().getBoolean("nameChangeCheck")) { + UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); + if (premiumUUID != null) { + profile = plugin.getCore().getStorage().loadProfile(premiumUUID); + if (profile != null) { + plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID); + enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false); + } + } + } + if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) { UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); if (premiumUUID != null) { diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/AsyncPremiumCheck.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/AsyncPremiumCheck.java index a0662eb0..8a7019a0 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/AsyncPremiumCheck.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/AsyncPremiumCheck.java @@ -38,6 +38,18 @@ public class AsyncPremiumCheck implements Runnable { } else if (profile.getUserId() == -1) { //user not exists in the db BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin(); + if (plugin.getConfiguration().getBoolean("nameChangeCheck")) { + UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); + if (premiumUUID != null) { + profile = plugin.getCore().getStorage().loadProfile(premiumUUID); + if (profile != null) { + plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID); + connection.setOnlineMode(true); + plugin.getSession().put(connection, new LoginSession(username, false, profile)); + } + } + } + if (plugin.getConfiguration().getBoolean("autoRegister") && (authPlugin == null || !authPlugin.isRegistered(username))) { UUID premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username); diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java index f04ffdef..57932c43 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java @@ -37,14 +37,9 @@ public class FastLoginBungee extends Plugin { private final Random random = new Random(); - private final ConcurrentMap pendingAutoRegister = CacheBuilder - .newBuilder() - .expireAfterWrite(1, TimeUnit.MINUTES) - .build().asMap(); - private final ConcurrentMap session = CacheBuilder .newBuilder() - .expireAfterWrite(1, TimeUnit.MINUTES) + .expireAfterWrite(5, TimeUnit.MINUTES) .build().asMap(); @Override diff --git a/core/src/main/java/com/github/games647/fastlogin/core/Storage.java b/core/src/main/java/com/github/games647/fastlogin/core/Storage.java index aa97b5ef..e44204f5 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/Storage.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/Storage.java @@ -46,9 +46,10 @@ public class Storage { public void createTables() throws SQLException { Connection con = null; + Statement createStmt = null; try { con = dataSource.getConnection(); - Statement statement = con.createStatement(); + createStmt = con.createStatement(); String createDataStmt = "CREATE TABLE IF NOT EXISTS " + PREMIUM_TABLE + " (" + "UserID INTEGER PRIMARY KEY AUTO_INCREMENT, " + "UUID CHAR(36), " @@ -65,21 +66,23 @@ public class Storage { createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT"); } - statement.executeUpdate(createDataStmt); + createStmt.executeUpdate(createDataStmt); } finally { closeQuietly(con); + closeQuietly(createStmt); } } public PlayerProfile loadProfile(String name) { Connection con = null; + PreparedStatement loadStmt = null; + ResultSet resultSet = null; try { con = dataSource.getConnection(); - PreparedStatement loadStatement = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE - + " WHERE Name=? LIMIT 1"); - loadStatement.setString(1, name); + loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE Name=?"); + loadStmt.setString(1, name); - ResultSet resultSet = loadStatement.executeQuery(); + resultSet = loadStmt.executeQuery(); if (resultSet.next()) { long userId = resultSet.getInt(1); @@ -104,6 +107,39 @@ public class Storage { core.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx); } finally { closeQuietly(con); + closeQuietly(loadStmt); + closeQuietly(resultSet); + } + + return null; + } + + public PlayerProfile loadProfile(UUID uuid) { + Connection con = null; + PreparedStatement loadStmt = null; + ResultSet resultSet = null; + try { + con = dataSource.getConnection(); + loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE UUID=?"); + loadStmt.setString(1, uuid.toString().replace("-", "")); + + resultSet = loadStmt.executeQuery(); + if (resultSet.next()) { + long userId = resultSet.getInt(1); + + String name = resultSet.getString(3); + boolean premium = resultSet.getBoolean(4); + String lastIp = resultSet.getString(5); + long lastLogin = resultSet.getTimestamp(6).getTime(); + PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin); + return playerProfile; + } + } catch (SQLException sqlEx) { + core.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx); + } finally { + closeQuietly(con); + closeQuietly(loadStmt); + closeQuietly(resultSet); } return null; @@ -111,46 +147,69 @@ public class Storage { public boolean save(PlayerProfile playerProfile) { Connection con = null; + PreparedStatement updateStmt = null; + PreparedStatement saveStmt = null; + + ResultSet generatedKeys = null; try { con = dataSource.getConnection(); UUID uuid = playerProfile.getUuid(); - if (playerProfile.getUserId() == -1) { - PreparedStatement saveStatement = con.prepareStatement("INSERT INTO " + PREMIUM_TABLE - + " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); + //User was authenticated with a premium authentication, so it's possible that the player is premium + if (uuid != null) { + updateStmt = con.prepareStatement("UPDATE " + PREMIUM_TABLE + + " SET NAME=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP" + + " WHERE UUID=? AND PREMIUM=1"); - if (uuid == null) { - saveStatement.setString(1, null); - } else { - saveStatement.setString(1, uuid.toString().replace("-", "")); + updateStmt.setString(1, playerProfile.getPlayerName()); + updateStmt.setString(2, playerProfile.getLastIp()); + updateStmt.setString(3, uuid.toString().replace("-", "")); + + int affectedRows = updateStmt.executeUpdate(); + if (affectedRows > 0) { + //username changed and we updated the existing database record + //so we don't need to run an insert + return true; + } } - saveStatement.setString(2, playerProfile.getPlayerName()); - saveStatement.setBoolean(3, playerProfile.isPremium()); - saveStatement.setString(4, playerProfile.getLastIp()); - saveStatement.execute(); + saveStmt = con.prepareStatement("INSERT INTO " + PREMIUM_TABLE + + " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?) " + , Statement.RETURN_GENERATED_KEYS); - ResultSet generatedKeys = saveStatement.getGeneratedKeys(); + if (uuid == null) { + saveStmt.setString(1, null); + } else { + saveStmt.setString(1, uuid.toString().replace("-", "")); + } + + saveStmt.setString(2, playerProfile.getPlayerName()); + saveStmt.setBoolean(3, playerProfile.isPremium()); + saveStmt.setString(4, playerProfile.getLastIp()); + + saveStmt.execute(); + + generatedKeys = saveStmt.getGeneratedKeys(); if (generatedKeys != null && generatedKeys.next()) { playerProfile.setUserId(generatedKeys.getInt(1)); } } else { - PreparedStatement saveStatement = con.prepareStatement("UPDATE " + PREMIUM_TABLE + saveStmt = con.prepareStatement("UPDATE " + PREMIUM_TABLE + " SET UUID=?, Name=?, Premium=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP WHERE UserID=?"); if (uuid == null) { - saveStatement.setString(1, null); + saveStmt.setString(1, null); } else { - saveStatement.setString(1, uuid.toString().replace("-", "")); + saveStmt.setString(1, uuid.toString().replace("-", "")); } - saveStatement.setString(2, playerProfile.getPlayerName()); - saveStatement.setBoolean(3, playerProfile.isPremium()); - saveStatement.setString(4, playerProfile.getLastIp()); + saveStmt.setString(2, playerProfile.getPlayerName()); + saveStmt.setBoolean(3, playerProfile.isPremium()); + saveStmt.setString(4, playerProfile.getLastIp()); - saveStatement.setLong(5, playerProfile.getUserId()); - saveStatement.execute(); + saveStmt.setLong(5, playerProfile.getUserId()); + saveStmt.execute(); } return true; @@ -158,6 +217,9 @@ public class Storage { core.getLogger().log(Level.SEVERE, "Failed to save playerProfile", ex); } finally { closeQuietly(con); + closeQuietly(updateStmt); + closeQuietly(saveStmt); + closeQuietly(generatedKeys); } return false; @@ -167,12 +229,12 @@ public class Storage { dataSource.close(); } - private void closeQuietly(Connection con) { - if (con != null) { + private void closeQuietly(AutoCloseable closeable) { + if (closeable != null) { try { - con.close(); - } catch (SQLException sqlEx) { - core.getLogger().log(Level.SEVERE, "Failed to close connection", sqlEx); + closeable.close(); + } catch (Exception closeEx) { + core.getLogger().log(Level.SEVERE, "Failed to close connection", closeEx); } } } diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 38996455..c8c689d2 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -40,6 +40,50 @@ autoRegister: false # This feature requires Cauldron, Spigot or a fork of Spigot (PaperSpigot, TacoSpigot) premiumUuid: false +# This will make an additional check (only for player names which are not in the database) against the mojang servers +# in order to get the premium UUID. If that premium UUID is in the database, we can assume on sucessful login that the +# player changed it's username and we just update the name in the database. +# Examples: +# #### Case 1 +# nameChangeCheck = false ----- autoRegister = false +# +# Player logins as cracked until the player invoked the command /premium. Then we could override the existing database +# record. +# +# #### Case 2 +# +# nameChangeCheck = true ----- autoRegister = false +# +# Connect the Mojang API and check what UUID the player has (UUID exists => Paid Minecraft account). If that UUID is in +# the database it's an **existing player** and FastLogin can **assume** the player is premium and changed the username. +# If it's not in the database, it's a new player and **could be a cracked player**. So we just use a offline mode +# authentication for this player. +# +# **Limitation**: Cracked players who uses the new username of a paid account cannot join the server if the database +# contains the old name. (Example: The owner of the paid account no longer plays on the server, but changed the username +# in the meanwhile). +# +# #### Case 3 +# +# nameChangeCheck = false ----- autoRegister = true +# +# We will always request a premium authentication if the username is unknown to us, but is in use by a paid minecraft +# account. This means it's kind of a more aggressive check like nameChangeCheck = true and autoRegister = false, because +# it request a premium authentication which are completely new to us, that even the premium UUID is not in our database. +# +# **Limitation**: see below +# +# #### Case 4 +# +# nameChangeCheck = true ----- autoRegister = true +# +# Based on autoRegister it checks if the player name is premium and login using a premium authentication. After that +# fastlogin receives the premium UUID and can update the database record. +# +# **Limitation from autoRegister**: New offline players who uses the username of an existing minecraft cannot join the +# server. +nameChangeCheck: false + # If your players have a premium account and a skin associated to their account, this plugin # can download the data and set it to the online player. # diff --git a/out/production/main2/com/github/games647/fastlogin/core/FastLoginCore.class b/out/production/main2/com/github/games647/fastlogin/core/FastLoginCore.class new file mode 100644 index 0000000000000000000000000000000000000000..d7f45d1bdd24082227310893113eaf42524b8beb GIT binary patch literal 2656 zcmX^0Z`VEs1_l#`EG`C721j-VCoTpi21hOiX9gD#%awz{4Me+hF?cX|ax#c8c!3CS zb_O3V25tsl5aGwp;LpV%!Vtj85Xcb3!4S;F5W)}&k_lsH2xn)AU}O-=&Cg2AOLr{D zbk5JqOHD4xFJfdc_esvr)lbhX$tX?IPfyHEEjBYT*H23n32JV7%SZpi%Wb!zHrVjN(H&NAhD=8 z)iZ^WK}EwSE3qt5KPNFST|c;_C^IkJS`#8vT9TQg9~$cEV$I0FTb@~xkzZQkl31LP zS`2a#hAu`1(c;t+%m89!u+kvLjhH&EL4hKgj@?j84NWplWn|z=Pc4DQ0waSSaRx!v zgTf*`wZtW{B+)HDCndFrkwHKM8ep0E`fiyypz!5PPX&c{I!IauCJheHoc#3k%)E4c zpZxUn)FNv}27&a{l8}s|)Wj6GL{LJkWMoi7(UF{=mt0y@l$uwfkD?CblH$~o(gKiM zk`jwk85!cRL?$FIk)ziWQ-~N5%_v3&rql{X22mu{t`*6t1tppJd7#vsQj}Q+@-a(B zesKvS14}`EQ3)dhqbDN+M@nKzBE*d>rNtmM90iHR#pU@$DU1xf!6k{w*}jPd;DpW2 z;Lpgwnw*ml@(@>WerZv1DkxML8H7+$u^uP^@-Rp-NHa2sgV@MP*r_xV6gTV)kvt4h z4ADFcF$}Sc42-%w3~>zc>GJq4G{;>4t!%)E4kf}GOy z%sf3kJ$8n49)=8tOdbY1kog8gI143I7#Rc*PV!I6N=+_75rX7!Mh4E}(xhT=Zf9f= z)A00!reNe`!pOjuSWu9fm%_-PizQuQ>VO8iH6sH@Nj}UX5m;JAHJT6XN^p(U+um>8HqNgd3RV31^BVBlh41mzqCMg|!M1_pfwMg~R(1_oBG?F@_? z!OCPA7#P^Vf}9L;3=9lBU^Xj*Jc9xQ0|O_6B7+iC^E3t)uw2A$2BygE49r^00vy{I zShg{+3bActVAT@f5a2=K^9Tr_@C5`!Q1~JO3K0G_2KF5c92?;7kz!y2yF{9SmqD9> zkHMOOpTV0!fFXo|2g!{Q49W~D3=9m645|!jP&e9vgMfj7O-p+#11G|Vpr!-^gE#{# zg9O+FF$N~EzahQ^g@71?I5=?B88o0~Ap&a)11G|9kie1#8-p5Hj0~C#T39r5p=*}M z)Xc)jpv|Dez`()?DpbIUcKUstv literal 0 HcmV?d00001 diff --git a/out/production/main2/com/github/games647/fastlogin/core/LoginSession.class b/out/production/main2/com/github/games647/fastlogin/core/LoginSession.class new file mode 100644 index 0000000000000000000000000000000000000000..e99a1e4e0afdaf498a4e0552dc3851311cb63ea2 GIT binary patch literal 982 zcmX^0Z`VEs1_l!b6)pxg23bx9Rt7l`ArB%H*clYr8I%|qI7*9Ci}Dh4QyCeAe6kYD z67_Qu^V0Q$ONuh{(ybX8xQbHKGmA@7i&9e<85pA&8Q2So^3yVNQW+VHe3J8X_0uy; zGD?&5(-U)3i_J{T_0tlIOLFqlGxPM5^NUjT19B28Q;Py1daM~4*laTMGE3|j89X#F zt&1YvM9nZp1{UZ1lvG9rexJ;|RKL>Pq|~C2#3Yalg?#dp6LZ26i!wocs31#8MrJW1 zgCQ|?`G7nUoLXF*nV)CP$iSVRS`rEmFA)t*OfN7p2_P|dtrTNs#UF|e~NXJBDo1NJw_y%0l0kPP948Y02Kz`)AD#O(mGT#`Ww WY96vag`FW4#7pC15MoH@Vi07=01=rS3|U+Z*$g=# z<+dwB@CtP3}svl@(kr5q5@<@C5WivV5sI`r~xr+IT-3V z80y&>8WG$;{C&N=;9#&<{u~DM>BLvu0#qE66EL z&&*?FF!D*x&(%-QEXgQM(oav!O)WMvG1pH^EH25(PtVNLPtGq&)ptuQF7W|zo%4%Q zLAq@+^D;~97#U18NY}0z#>l|poS%})$iVNDnV0HUnwykb6q1;flM3>CesW??SYlBo zhz}KHDaptzW@IoY#xCFdti-%@$AV1f{Jgx>$bEYclgefzOm$!N?#AQ3G~?YejNuK?y{X3lfuliMgqa z47|Z5iOJc%i3Q-0U}wls!sOc+dg7}^-x*%>-`7&;lc7#YMt_95pfr_#(EaNHVXl#~<{Tj}d37G&z>f|HLP z$U&vWsYS*51x5L3nK`M&`nj2TsmVo&X(jqR4BZSpJPf@IeLM_448A-J{R|Tr8Kl4# zU=1Wj1~ITCM)F}~kc3Hi<`tBd1eX-0Cgwtt77xQjhDo3RnhYYQ@Gwke=woDH$t%st zVP}}e!!Vs;1`mTbgB>G-FxU*#gv8D;lZRmz!)$hjIXnz=8RjuE=(r_j=A@=5l;kTU zXQU=)D`ciAK$4n5Mq;r-qC!DpW{N^$a&mrYUI{zHd>)1c3=0_QQj_Ij+C5#OG(Cnz|3~~!76$^kZ0{a4zhG8Dd%-8qyho>$^24)RV2?B{v zaArj*+ZY+xlk;;6z{yS*cVfoWXwAsLo(n5FjgfUaXCxK{rxuo`<|U_EBbO2oi=pMM zH7t!G84Z$YF-K>10E-Wg94HTp=s5ck%6NmA0ouaAff?v3z9-> zNFEPLEiTBhtwEw3 zpmGvamNPOSMI+3iY^jB%i8;lL45CoYU74kj&iF{L&Ie1{MuZP#p0Ur_L61Qn&evd&VlY7Bb2CUW z7$WhR8H7MZHUk5L2&mL%U|?WnU}P|3FlS(7uwY7-Se&7$g{Y8DtrR7~~kN7#JADAX*u$8EhCpKp1QV zBLgFYErT7z0!BdwRt8oE28L3tEex#M+ZfolF>uUe$k5u&z!|xnflG*c8v~E_HU^$; z47}SI_=_&wm>E17#26eI^ch$f)EF!ooEV%Lm>7~71Q=WxT*1Ln3J;D_1~+g_xHB*? za4|6dW{7uUXYdeUVPx=R@M2(KVFA@I3=9k`plXhR2^=4d49pD73=9nVyBXvowYM`U zY-3P7z#!nOBgp(>8-tRLAp3=F49Yq}DnhE;7}Rux)P*!a^aglbNinc7FfceW@H03u zh%q=b$TGMxs4}=Q=rVYKJ);0I9_~ne20ySP`56?Tp794eiIE|Ifsr8)Y;F($haWVw literal 0 HcmV?d00001 diff --git a/out/production/main2/com/github/games647/fastlogin/core/PlayerProfile.class b/out/production/main2/com/github/games647/fastlogin/core/PlayerProfile.class new file mode 100644 index 0000000000000000000000000000000000000000..f54b7a91327391b1dc27c9a0a828939e5a66bff6 GIT binary patch literal 1969 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 E0PtX|4FCWD literal 0 HcmV?d00001