From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Aikar <aikar@aikar.co>
Date: Mon, 15 Jan 2018 22:11:48 -0500
Subject: [PATCH] Basic PlayerProfile API

Establishes base extension of profile systems for future edits too

diff --git a/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java
new file mode 100644
index 0000000000000000000000000000000000000000..b151a13c1b9058eb057081ef8a64558028443a07
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java
@@ -0,0 +1,280 @@
+package com.destroystokyo.paper.profile;
+
+import com.destroystokyo.paper.PaperConfig;
+import com.google.common.base.Charsets;
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.properties.Property;
+import com.mojang.authlib.properties.PropertyMap;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.UserCache;
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.spigotmc.SpigotConfig;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+
+public class CraftPlayerProfile implements PlayerProfile {
+
+    private GameProfile profile;
+    private final PropertySet properties = new PropertySet();
+
+    public CraftPlayerProfile(CraftPlayer player) {
+        this.profile = player.getHandle().getProfile();
+    }
+
+    public CraftPlayerProfile(UUID id, String name) {
+        this.profile = new GameProfile(id, name);
+    }
+
+    public CraftPlayerProfile(GameProfile profile) {
+        this.profile = profile;
+    }
+
+    @Override
+    public boolean hasProperty(String property) {
+        return profile.getProperties().containsKey(property);
+    }
+
+    @Override
+    public void setProperty(ProfileProperty property) {
+        String name = property.getName();
+        PropertyMap properties = profile.getProperties();
+        properties.removeAll(name);
+        properties.put(name, new Property(name, property.getValue(), property.getSignature()));
+    }
+
+    public GameProfile getGameProfile() {
+        return profile;
+    }
+
+    @Nullable
+    @Override
+    public UUID getId() {
+        return profile.getId();
+    }
+
+    @Override
+    public UUID setId(@Nullable UUID uuid) {
+        GameProfile prev = this.profile;
+        this.profile = new GameProfile(uuid, prev.getName());
+        copyProfileProperties(prev, this.profile);
+        return prev.getId();
+    }
+
+    @Nullable
+    @Override
+    public String getName() {
+        return profile.getName();
+    }
+
+    @Override
+    public String setName(@Nullable String name) {
+        GameProfile prev = this.profile;
+        this.profile = new GameProfile(prev.getId(), name);
+        copyProfileProperties(prev, this.profile);
+        return prev.getName();
+    }
+
+    @Nonnull
+    @Override
+    public Set<ProfileProperty> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public void setProperties(Collection<ProfileProperty> properties) {
+        properties.forEach(this::setProperty);
+    }
+
+    @Override
+    public void clearProperties() {
+        profile.getProperties().clear();
+    }
+
+    @Override
+    public boolean removeProperty(String property) {
+        return !profile.getProperties().removeAll(property).isEmpty();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        CraftPlayerProfile that = (CraftPlayerProfile) o;
+        return Objects.equals(profile, that.profile);
+    }
+
+    @Override
+    public int hashCode() {
+        return profile.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return profile.toString();
+    }
+
+    @Override
+    public CraftPlayerProfile clone() {
+        CraftPlayerProfile clone = new CraftPlayerProfile(this.getId(), this.getName());
+        clone.setProperties(getProperties());
+        return clone;
+    }
+
+    @Override
+    public boolean isComplete() {
+        return profile.isComplete();
+    }
+
+    @Override
+    public boolean completeFromCache() {
+        return completeFromCache(false);
+    }
+
+    public boolean completeFromCache(boolean lookupName) {
+        if (profile.isComplete()) {
+            return true;
+        }
+        MinecraftServer server = MinecraftServer.getServer();
+        String name = profile.getName();
+        UserCache userCache = server.getUserCache();
+        if (profile.getId() == null) {
+            final GameProfile profile;
+            boolean isOnlineMode = server.getOnlineMode() || (SpigotConfig.bungee && PaperConfig.bungeeOnlineMode);
+            if (isOnlineMode) {
+                profile = lookupName ? userCache.getProfile(name) : userCache.getProfileIfCached(name);
+            } else {
+                // Make an OfflinePlayer using an offline mode UUID since the name has no profile
+                profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name);
+            }
+            if (profile != null) {
+                this.profile = profile;
+            }
+        }
+
+        if (profile.getName() == null) {
+            // If we need textures, skip this check, as we will get it below anyways.
+            GameProfile profile = userCache.getProfile(this.profile.getId());
+            if (profile != null) {
+                this.profile = profile;
+            }
+        }
+        return this.profile.isComplete();
+    }
+
+    public boolean complete(boolean textures) {
+        MinecraftServer server = MinecraftServer.getServer();
+
+        boolean isOnlineMode = server.getOnlineMode() || (SpigotConfig.bungee && PaperConfig.bungeeOnlineMode);
+        boolean isCompleteFromCache = this.completeFromCache(true);
+        if (isOnlineMode && (!isCompleteFromCache || textures && !hasTextures())) {
+            GameProfile result = server.getSessionService().fillProfileProperties(profile, true);
+            if (result != null) {
+                this.profile = result;
+            }
+        }
+        return profile.isComplete() && (!isOnlineMode || !textures || hasTextures());
+    }
+
+    private static void copyProfileProperties(GameProfile source, GameProfile target) {
+        PropertyMap sourceProperties = source.getProperties();
+        if (sourceProperties.isEmpty()) {
+            return;
+        }
+        PropertyMap properties = target.getProperties();
+        properties.clear();
+
+        for (Property property : sourceProperties.values()) {
+            properties.put(property.getName(), property);
+        }
+    }
+
+    private static ProfileProperty toBukkit(Property property) {
+        return new ProfileProperty(property.getName(), property.getValue(), property.getSignature());
+    }
+
+    public static PlayerProfile asBukkitCopy(GameProfile gameProfile) {
+        CraftPlayerProfile profile = new CraftPlayerProfile(gameProfile.getId(), gameProfile.getName());
+        copyProfileProperties(gameProfile, profile.profile);
+        return profile;
+    }
+
+    public static PlayerProfile asBukkitMirror(GameProfile profile) {
+        return new CraftPlayerProfile(profile);
+    }
+
+    public static Property asAuthlib(ProfileProperty property) {
+        return new Property(property.getName(), property.getValue(), property.getSignature());
+    }
+
+    public static GameProfile asAuthlibCopy(PlayerProfile profile) {
+        CraftPlayerProfile craft = ((CraftPlayerProfile) profile);
+        return asAuthlib(craft.clone());
+    }
+
+    public static GameProfile asAuthlib(PlayerProfile profile) {
+        CraftPlayerProfile craft = ((CraftPlayerProfile) profile);
+        return craft.getGameProfile();
+    }
+
+    private class PropertySet extends AbstractSet<ProfileProperty> {
+
+        @Override
+        @Nonnull
+        public Iterator<ProfileProperty> iterator() {
+            return new ProfilePropertyIterator(profile.getProperties().values().iterator());
+        }
+
+        @Override
+        public int size() {
+            return profile.getProperties().size();
+        }
+
+        @Override
+        public boolean add(ProfileProperty property) {
+            setProperty(property);
+            return true;
+        }
+
+        @Override
+        public boolean addAll(Collection<? extends ProfileProperty> c) {
+            //noinspection unchecked
+            setProperties((Collection<ProfileProperty>) c);
+            return true;
+        }
+
+        @Override
+        public boolean contains(Object o) {
+            return o instanceof ProfileProperty && profile.getProperties().containsKey(((ProfileProperty) o).getName());
+        }
+
+        private class ProfilePropertyIterator implements Iterator<ProfileProperty> {
+            private final Iterator<Property> iterator;
+
+            ProfilePropertyIterator(Iterator<Property> iterator) {
+                this.iterator = iterator;
+            }
+
+            @Override
+            public boolean hasNext() {
+                return iterator.hasNext();
+            }
+
+            @Override
+            public ProfileProperty next() {
+                return toBukkit(iterator.next());
+            }
+
+            @Override
+            public void remove() {
+                iterator.remove();
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java
new file mode 100644
index 0000000000000000000000000000000000000000..25836b975b51af701d4b9523ab398fbf157b82fd
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java
@@ -0,0 +1,30 @@
+package com.destroystokyo.paper.profile;
+
+import com.mojang.authlib.Agent;
+import com.mojang.authlib.GameProfileRepository;
+import com.mojang.authlib.UserAuthentication;
+import com.mojang.authlib.minecraft.MinecraftSessionService;
+import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
+
+import java.net.Proxy;
+
+public class PaperAuthenticationService extends YggdrasilAuthenticationService {
+    public PaperAuthenticationService(Proxy proxy, String clientToken) {
+        super(proxy, clientToken);
+    }
+
+    @Override
+    public UserAuthentication createUserAuthentication(Agent agent) {
+        return new PaperUserAuthentication(this, agent);
+    }
+
+    @Override
+    public MinecraftSessionService createMinecraftSessionService() {
+        return new PaperMinecraftSessionService(this);
+    }
+
+    @Override
+    public GameProfileRepository createProfileRepository() {
+        return new PaperGameProfileRepository(this);
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java
new file mode 100644
index 0000000000000000000000000000000000000000..3bcdb8f93f1930ee53395470ffb3833e2bd75222
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java
@@ -0,0 +1,17 @@
+package com.destroystokyo.paper.profile;
+
+import com.mojang.authlib.Agent;
+import com.mojang.authlib.ProfileLookupCallback;
+import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
+import com.mojang.authlib.yggdrasil.YggdrasilGameProfileRepository;
+
+public class PaperGameProfileRepository extends YggdrasilGameProfileRepository {
+    public PaperGameProfileRepository(YggdrasilAuthenticationService authenticationService) {
+        super(authenticationService);
+    }
+
+    @Override
+    public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback) {
+        super.findProfilesByNames(names, agent, callback);
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b2a67423f57b70d316115e4525e3841a415b1cc
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java
@@ -0,0 +1,29 @@
+package com.destroystokyo.paper.profile;
+
+import com.mojang.authlib.GameProfile;
+import com.mojang.authlib.minecraft.MinecraftProfileTexture;
+import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
+import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService;
+
+import java.util.Map;
+
+public class PaperMinecraftSessionService extends YggdrasilMinecraftSessionService {
+    protected PaperMinecraftSessionService(YggdrasilAuthenticationService authenticationService) {
+        super(authenticationService);
+    }
+
+    @Override
+    public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getTextures(GameProfile profile, boolean requireSecure) {
+        return super.getTextures(profile, requireSecure);
+    }
+
+    @Override
+    public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) {
+        return super.fillProfileProperties(profile, requireSecure);
+    }
+
+    @Override
+    protected GameProfile fillGameProfile(GameProfile profile, boolean requireSecure) {
+        return super.fillGameProfile(profile, requireSecure);
+    }
+}
diff --git a/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java b/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java
new file mode 100644
index 0000000000000000000000000000000000000000..3aceb0ea8a1a3ed94dd8a9e954c52ecd341c6bd1
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/profile/PaperUserAuthentication.java
@@ -0,0 +1,11 @@
+package com.destroystokyo.paper.profile;
+
+import com.mojang.authlib.Agent;
+import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
+import com.mojang.authlib.yggdrasil.YggdrasilUserAuthentication;
+
+public class PaperUserAuthentication extends YggdrasilUserAuthentication {
+    public PaperUserAuthentication(YggdrasilAuthenticationService authenticationService, Agent agent) {
+        super(authenticationService, agent);
+    }
+}
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
index f9a7a1e9eea67bafb85c0ed88e96abb8e45f6c81..8ca1a4719d934db31d57b34cf7050acc5a1a7048 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
@@ -1,7 +1,10 @@
 package net.minecraft.server;
 
 import com.destroystokyo.paper.block.TargetBlockInfo;
+import com.destroystokyo.paper.profile.CraftPlayerProfile;
+import com.destroystokyo.paper.profile.PlayerProfile;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.mojang.authlib.GameProfile;
 import org.apache.commons.lang.exception.ExceptionUtils;
 import org.bukkit.Location;
 import org.bukkit.block.BlockFace;
@@ -329,6 +332,10 @@ public final class MCUtil {
         return run.get();
     }
 
+    public static PlayerProfile toBukkit(GameProfile profile) {
+        return CraftPlayerProfile.asBukkitMirror(profile);
+    }
+
     /**
      * Calculates distance between 2 entities
      * @param e1
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 87595425a358a13c8f2393619d51a981140556cf..4a513aaea587414cf6abc1c136e52420d59688d8 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1304,7 +1304,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
             DispenserRegistry.init();
             DispenserRegistry.c();
             File s = (File) optionset.valueOf("universe"); // CraftBukkit
-            YggdrasilAuthenticationService yggdrasilauthenticationservice = new YggdrasilAuthenticationService(Proxy.NO_PROXY, UUID.randomUUID().toString());
+            YggdrasilAuthenticationService yggdrasilauthenticationservice = new com.destroystokyo.paper.profile.PaperAuthenticationService(Proxy.NO_PROXY, UUID.randomUUID().toString()); // Paper
             MinecraftSessionService minecraftsessionservice = yggdrasilauthenticationservice.createMinecraftSessionService();
             GameProfileRepository gameprofilerepository = yggdrasilauthenticationservice.createProfileRepository();
             UserCache usercache = new UserCache(gameprofilerepository, new File(s, MinecraftServer.b.getName()));
@@ -1769,6 +1769,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
         this.H = i;
     }
 
+    public final MinecraftSessionService getSessionService() { return this.getMinecraftSessionService(); } // Paper - OBFHELPER
     public MinecraftSessionService getMinecraftSessionService() {
         return this.minecraftSessionService;
     }
diff --git a/src/main/java/net/minecraft/server/UserCache.java b/src/main/java/net/minecraft/server/UserCache.java
index 581199e6dcddb3692ccc6b6cf6c42fa5ef1f5e7e..a072222bc5b8b1b62990bce35034b09ebf270865 100644
--- a/src/main/java/net/minecraft/server/UserCache.java
+++ b/src/main/java/net/minecraft/server/UserCache.java
@@ -43,7 +43,7 @@ public class UserCache {
 
     public static final SimpleDateFormat a = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
     private static boolean c;
-    private final Map<String, UserCache.UserCacheEntry> d = new java.util.concurrent.ConcurrentHashMap<>(); // Paper
+    private final Map<String, UserCache.UserCacheEntry> d = new java.util.concurrent.ConcurrentHashMap<>();private final Map<String, UserCache.UserCacheEntry> nameCache = d; // Paper - OBFHELPER // Paper
     private final Map<UUID, UserCache.UserCacheEntry> e = new java.util.concurrent.ConcurrentHashMap<>(); // Paper
     private final Deque<GameProfile> f = new java.util.concurrent.LinkedBlockingDeque<GameProfile>(); // CraftBukkit
     private final GameProfileRepository g;
@@ -165,6 +165,13 @@ public class UserCache {
         return usercache_usercacheentry == null ? null : usercache_usercacheentry.a();
     }
 
+    // Paper start
+    @Nullable public GameProfile getProfileIfCached(String name) {
+        UserCache.UserCacheEntry entry = this.nameCache.get(name.toLowerCase(Locale.ROOT));
+        return entry == null ? null : entry.getProfile();
+    }
+    // Paper end
+
     @Nullable
     public GameProfile getProfile(UUID uuid) {
         UserCache.UserCacheEntry usercache_usercacheentry = (UserCache.UserCacheEntry) this.e.get(uuid);
@@ -273,7 +280,7 @@ public class UserCache {
 
     class UserCacheEntry {
 
-        private final GameProfile b;
+        private final GameProfile b;public GameProfile getProfile() { return b; } // Paper - OBFHELPER
         private final Date c;
 
         private UserCacheEntry(GameProfile gameprofile, Date date) {
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
index f01bd38d0b600a69224f610fd77a542ec6d1c322..95f4abddf57eb8c59cb5a5410b8d551d39f94fd7 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
@@ -200,6 +200,9 @@ import org.yaml.snakeyaml.error.MarkedYAMLException;
 
 import net.md_5.bungee.api.chat.BaseComponent; // Spigot
 
+import javax.annotation.Nullable; // Paper
+import javax.annotation.Nonnull; // Paper
+
 public final class CraftServer implements Server {
     private final String serverName = "Paper"; // Paper
     private final String serverVersion;
@@ -2154,5 +2157,24 @@ public final class CraftServer implements Server {
     public boolean suggestPlayerNamesWhenNullTabCompletions() {
         return com.destroystokyo.paper.PaperConfig.suggestPlayersWhenNullTabCompletions;
     }
+
+    @Override
+    public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull UUID uuid) {
+        return createProfile(uuid, null);
+    }
+
+    @Override
+    public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nonnull String name) {
+        return createProfile(null, name);
+    }
+
+    @Override
+    public com.destroystokyo.paper.profile.PlayerProfile createProfile(@Nullable UUID uuid, @Nullable String name) {
+        Player player = uuid != null ? Bukkit.getPlayer(uuid) : (name != null ? Bukkit.getPlayerExact(name) : null);
+        if (player != null) {
+            return new com.destroystokyo.paper.profile.CraftPlayerProfile((CraftPlayer)player);
+        }
+        return new com.destroystokyo.paper.profile.CraftPlayerProfile(uuid, name);
+    }
     // Paper end
 }
