package net.minecraft.server.players; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.Files; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfileRepository; import com.mojang.logging.LogUtils; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; import net.minecraft.Util; import net.minecraft.core.UUIDUtil; import net.minecraft.util.StringUtil; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class GameProfileCache { private static final Logger LOGGER = LogUtils.getLogger(); private static final int GAMEPROFILES_MRU_LIMIT = 1000; private static final int GAMEPROFILES_EXPIRATION_MONTHS = 1; private static boolean usesAuthentication; /** * A map between player usernames and */ private final Map profilesByName = Maps.newConcurrentMap(); /** * A map between and */ private final Map profilesByUUID = Maps.newConcurrentMap(); private final Map>> requests = Maps.>>newConcurrentMap(); private final GameProfileRepository profileRepository; private final Gson gson = new GsonBuilder().create(); private final File file; private final AtomicLong operationCount = new AtomicLong(); @Nullable private Executor executor; public GameProfileCache(GameProfileRepository profileRepository, File file) { this.profileRepository = profileRepository; this.file = file; Lists.reverse(this.load()).forEach(this::safeAdd); } private void safeAdd(GameProfileCache.GameProfileInfo profile) { GameProfile gameProfile = profile.getProfile(); profile.setLastAccess(this.getNextOperation()); this.profilesByName.put(gameProfile.getName().toLowerCase(Locale.ROOT), profile); this.profilesByUUID.put(gameProfile.getId(), profile); } private static Optional lookupGameProfile(GameProfileRepository profileRepo, String name) { if (!StringUtil.isValidPlayerName(name)) { return createUnknownProfile(name); } else { Optional optional = profileRepo.findProfileByName(name); return optional.isEmpty() ? createUnknownProfile(name) : optional; } } private static Optional createUnknownProfile(String profileName) { return usesAuthentication() ? Optional.empty() : Optional.of(UUIDUtil.createOfflineProfile(profileName)); } public static void setUsesAuthentication(boolean onlineMode) { usesAuthentication = onlineMode; } private static boolean usesAuthentication() { return usesAuthentication; } /** * Add an entry to this cache */ public void add(GameProfile gameProfile) { Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); calendar.add(2, 1); Date date = calendar.getTime(); GameProfileCache.GameProfileInfo gameProfileInfo = new GameProfileCache.GameProfileInfo(gameProfile, date); this.safeAdd(gameProfileInfo); this.save(); } private long getNextOperation() { return this.operationCount.incrementAndGet(); } /** * Get a player's GameProfile given their username. Mojang's servers will be contacted if the entry is not cached locally. */ public Optional get(String name) { String string = name.toLowerCase(Locale.ROOT); GameProfileCache.GameProfileInfo gameProfileInfo = (GameProfileCache.GameProfileInfo)this.profilesByName.get(string); boolean bl = false; if (gameProfileInfo != null && new Date().getTime() >= gameProfileInfo.expirationDate.getTime()) { this.profilesByUUID.remove(gameProfileInfo.getProfile().getId()); this.profilesByName.remove(gameProfileInfo.getProfile().getName().toLowerCase(Locale.ROOT)); bl = true; gameProfileInfo = null; } Optional optional; if (gameProfileInfo != null) { gameProfileInfo.setLastAccess(this.getNextOperation()); optional = Optional.of(gameProfileInfo.getProfile()); } else { optional = lookupGameProfile(this.profileRepository, string); if (optional.isPresent()) { this.add((GameProfile)optional.get()); bl = false; } } if (bl) { this.save(); } return optional; } public CompletableFuture> getAsync(String name) { if (this.executor == null) { throw new IllegalStateException("No executor"); } else { CompletableFuture> completableFuture = (CompletableFuture>)this.requests.get(name); if (completableFuture != null) { return completableFuture; } else { CompletableFuture> completableFuture2 = CompletableFuture.supplyAsync( () -> this.get(name), Util.backgroundExecutor().forName("getProfile") ) .whenCompleteAsync((optional, throwable) -> this.requests.remove(name), this.executor); this.requests.put(name, completableFuture2); return completableFuture2; } } } /** * @param uuid Get a player's {@link GameProfile} given their UUID */ public Optional get(UUID uuid) { GameProfileCache.GameProfileInfo gameProfileInfo = (GameProfileCache.GameProfileInfo)this.profilesByUUID.get(uuid); if (gameProfileInfo == null) { return Optional.empty(); } else { gameProfileInfo.setLastAccess(this.getNextOperation()); return Optional.of(gameProfileInfo.getProfile()); } } public void setExecutor(Executor exectutor) { this.executor = exectutor; } public void clearExecutor() { this.executor = null; } private static DateFormat createDateFormat() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT); } public List load() { List list = Lists.newArrayList(); try { Reader reader = Files.newReader(this.file, StandardCharsets.UTF_8); Object var9; label60: { try { JsonArray jsonArray = this.gson.fromJson(reader, JsonArray.class); if (jsonArray == null) { var9 = list; break label60; } DateFormat dateFormat = createDateFormat(); jsonArray.forEach(jsonElement -> readGameProfile(jsonElement, dateFormat).ifPresent(list::add)); } catch (Throwable var6) { if (reader != null) { try { reader.close(); } catch (Throwable var5) { var6.addSuppressed(var5); } } throw var6; } if (reader != null) { reader.close(); } return list; } if (reader != null) { reader.close(); } return (List)var9; } catch (FileNotFoundException var7) { } catch (JsonParseException | IOException var8) { LOGGER.warn("Failed to load profile cache {}", this.file, var8); } return list; } /** * Save the cached profiles to disk */ public void save() { JsonArray jsonArray = new JsonArray(); DateFormat dateFormat = createDateFormat(); this.getTopMRUProfiles(1000).forEach(gameProfileInfo -> jsonArray.add(writeGameProfile(gameProfileInfo, dateFormat))); String string = this.gson.toJson((JsonElement)jsonArray); try { Writer writer = Files.newWriter(this.file, StandardCharsets.UTF_8); try { writer.write(string); } catch (Throwable var8) { if (writer != null) { try { writer.close(); } catch (Throwable var7) { var8.addSuppressed(var7); } } throw var8; } if (writer != null) { writer.close(); } } catch (IOException var9) { } } private Stream getTopMRUProfiles(int limit) { return ImmutableList.copyOf(this.profilesByUUID.values()) .stream() .sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()) .limit(limit); } private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo profileInfo, DateFormat dateFormat) { JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("name", profileInfo.getProfile().getName()); jsonObject.addProperty("uuid", profileInfo.getProfile().getId().toString()); jsonObject.addProperty("expiresOn", dateFormat.format(profileInfo.getExpirationDate())); return jsonObject; } private static Optional readGameProfile(JsonElement json, DateFormat dateFormat) { if (json.isJsonObject()) { JsonObject jsonObject = json.getAsJsonObject(); JsonElement jsonElement = jsonObject.get("name"); JsonElement jsonElement2 = jsonObject.get("uuid"); JsonElement jsonElement3 = jsonObject.get("expiresOn"); if (jsonElement != null && jsonElement2 != null) { String string = jsonElement2.getAsString(); String string2 = jsonElement.getAsString(); Date date = null; if (jsonElement3 != null) { try { date = dateFormat.parse(jsonElement3.getAsString()); } catch (ParseException var12) { } } if (string2 != null && string != null && date != null) { UUID uUID; try { uUID = UUID.fromString(string); } catch (Throwable var11) { return Optional.empty(); } return Optional.of(new GameProfileCache.GameProfileInfo(new GameProfile(uUID, string2), date)); } else { return Optional.empty(); } } else { return Optional.empty(); } } else { return Optional.empty(); } } static class GameProfileInfo { /** * The player's GameProfile */ private final GameProfile profile; /** * The date that this entry will expire */ final Date expirationDate; private volatile long lastAccess; GameProfileInfo(GameProfile profile, Date expirationDate) { this.profile = profile; this.expirationDate = expirationDate; } /** * Get the player's GameProfile */ public GameProfile getProfile() { return this.profile; } /** * Get the date that this entry will expire */ public Date getExpirationDate() { return this.expirationDate; } public void setLastAccess(long lastAccess) { this.lastAccess = lastAccess; } public long getLastAccess() { return this.lastAccess; } } }