From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar 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..d73de9eb89c4e3a748907f2da21a3072cc6b7f41 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java @@ -0,0 +1,296 @@ +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.apache.commons.lang3.Validate; +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) { + Validate.notNull(profile, "GameProfile cannot be null!"); + 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 getProperties() { + return properties; + } + + @Override + public void setProperties(Collection 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() { + MinecraftServer server = MinecraftServer.getServer(); + return completeFromCache(false, server.getOnlineMode() || (SpigotConfig.bungee && PaperConfig.bungeeOnlineMode)); + } + + public boolean completeFromCache(boolean onlineMode) { + return completeFromCache(false, onlineMode); + } + + public boolean completeFromCache(boolean lookupUUID, boolean onlineMode) { + MinecraftServer server = MinecraftServer.getServer(); + String name = profile.getName(); + UserCache userCache = server.getUserCache(); + if (profile.getId() == null) { + final GameProfile profile; + if (onlineMode) { + profile = lookupUUID ? 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) { + // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't + copyProfileProperties(this.profile, profile); + this.profile = profile; + } + } + + if ((profile.getName() == null || !hasTextures()) && profile.getId() != null) { + GameProfile profile = userCache.getProfile(this.profile.getId()); + if (profile != null) { + // if old has it, assume its newer, so overwrite, else use cached if it was set and ours wasn't + copyProfileProperties(this.profile, profile); + this.profile = profile; + } + } + return this.profile.isComplete(); + } + + public boolean complete(boolean textures) { + MinecraftServer server = MinecraftServer.getServer(); + return complete(textures, server.getOnlineMode() || (SpigotConfig.bungee && PaperConfig.bungeeOnlineMode)); + } + public boolean complete(boolean textures, boolean onlineMode) { + MinecraftServer server = MinecraftServer.getServer(); + + boolean isCompleteFromCache = this.completeFromCache(true, onlineMode); + if (onlineMode && (!isCompleteFromCache || textures && !hasTextures())) { + GameProfile result = server.getSessionService().fillProfileProperties(profile, true); + if (result != null) { + copyProfileProperties(result, this.profile, true); + } + if (this.profile.isComplete()) { + server.getUserCache().saveProfile(this.profile); + } + } + return profile.isComplete() && (!onlineMode || !textures || hasTextures()); + } + + private static void copyProfileProperties(GameProfile source, GameProfile target) { + copyProfileProperties(source, target, false); + } + + private static void copyProfileProperties(GameProfile source, GameProfile target, boolean clearTarget) { + PropertyMap sourceProperties = source.getProperties(); + PropertyMap targetProperties = target.getProperties(); + if (clearTarget) targetProperties.clear(); + if (sourceProperties.isEmpty()) { + return; + } + + for (Property property : sourceProperties.values()) { + targetProperties.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 { + + @Override + @Nonnull + public Iterator 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 c) { + //noinspection unchecked + setProperties((Collection) c); + return true; + } + + @Override + public boolean contains(Object o) { + return o instanceof ProfileProperty && profile.getProperties().containsKey(((ProfileProperty) o).getName()); + } + + private class ProfilePropertyIterator implements Iterator { + private final Iterator iterator; + + ProfilePropertyIterator(Iterator 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..ef9f55afd6bffa8c02c6820295223e5465eed91e --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/PaperAuthenticationService.java @@ -0,0 +1,31 @@ +package com.destroystokyo.paper.profile; + +import com.mojang.authlib.*; +import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService; +import com.mojang.authlib.yggdrasil.YggdrasilEnvironment; + +import java.net.Proxy; + +public class PaperAuthenticationService extends YggdrasilAuthenticationService { + private final Environment environment; + public PaperAuthenticationService(Proxy proxy, String clientToken) { + super(proxy, clientToken); + this.environment = (Environment)EnvironmentParser.getEnvironmentFromProperties().orElse(YggdrasilEnvironment.PROD);; + } + + @Override + public UserAuthentication createUserAuthentication(Agent agent) { + return new PaperUserAuthentication(this, agent); + } + + @Override + public MinecraftSessionService createMinecraftSessionService() { + return new PaperMinecraftSessionService(this, this.environment); + } + + @Override + public GameProfileRepository createProfileRepository() { + return new PaperGameProfileRepository(this, this.environment); + } +} 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..582c169c85ac66f1f9430f79042e4655f776c157 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/PaperGameProfileRepository.java @@ -0,0 +1,18 @@ +package com.destroystokyo.paper.profile; + +import com.mojang.authlib.Agent; +import com.mojang.authlib.Environment; +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, Environment environment) { + super(authenticationService, environment); + } + + @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..93d73c27340645c7502acafdc0b2cfbc1a759dd8 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/PaperMinecraftSessionService.java @@ -0,0 +1,30 @@ +package com.destroystokyo.paper.profile; + +import com.mojang.authlib.Environment; +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, Environment environment) { + super(authenticationService, environment); + } + + @Override + public Map 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 da7a325d070e194cd1664ed20dcb3a762c9a517a..797654c653ec6dc4d46b457cf8a6121b29eca7aa 100644 --- a/src/main/java/net/minecraft/server/MCUtil.java +++ b/src/main/java/net/minecraft/server/MCUtil.java @@ -1,8 +1,11 @@ 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 org.apache.commons.lang.exception.ExceptionUtils; +import com.mojang.authlib.GameProfile; import org.bukkit.Location; import org.bukkit.block.BlockFace; import org.bukkit.craftbukkit.CraftWorld; @@ -337,6 +340,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/Main.java b/src/main/java/net/minecraft/server/Main.java index d88946122e98abdefed54b29f510c7d4049fdff6..55c79f17a25f1bfe80cbc65e8c01ea1703f27538 100644 --- a/src/main/java/net/minecraft/server/Main.java +++ b/src/main/java/net/minecraft/server/Main.java @@ -87,7 +87,7 @@ public class Main { } File file = (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(file, MinecraftServer.b.getName())); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index b06bb3dccda23ebed1532e17f9699b9b6828d9d8..62ca208a95434ca1d8970b01f7291647e0b1f93f 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -1601,6 +1601,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant() { @Override @@ -181,7 +182,16 @@ public class TileEntitySkull extends TileEntity /*implements ITickable*/ { // Pa } else if (MinecraftServer.getServer() == null) { callback.apply(gameprofile); } else { - GameProfile profile = skinCache.getIfPresent(gameprofile.getName().toLowerCase(java.util.Locale.ROOT)); + // Paper start + com.destroystokyo.paper.profile.CraftPlayerProfile paperProfile = new com.destroystokyo.paper.profile.CraftPlayerProfile(gameprofile); + if (sync) { + // might complete by cache, but if not, go ahead and do it now, avoid the code below + paperProfile.complete(true, true); + } else { + paperProfile.completeFromCache(false, true); + } + GameProfile profile = paperProfile.getGameProfile(); + // Paper end if (profile != null && Iterables.getFirst(profile.getProperties().get("textures"), (Object) null) != null) { callback.apply(profile); @@ -190,7 +200,10 @@ public class TileEntitySkull extends TileEntity /*implements ITickable*/ { // Pa Callable callable = new Callable() { @Override public GameProfile call() { - final GameProfile profile = skinCache.getUnchecked(gameprofile.getName().toLowerCase(java.util.Locale.ROOT)); + // Paper start + paperProfile.complete(true, true); + final GameProfile profile = paperProfile.getGameProfile(); + // Paper end MinecraftServer.getServer().processQueue.add(new Runnable() { @Override public void run() { diff --git a/src/main/java/net/minecraft/server/UserCache.java b/src/main/java/net/minecraft/server/UserCache.java index 4f769211cf98c3da720a904da3dcdcd4c7611f0b..a038397028848edb4f43cd4f7262546666e32883 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 d = new java.util.concurrent.ConcurrentHashMap<>(); // Paper + private final Map d = new java.util.concurrent.ConcurrentHashMap<>();private final Map nameCache = d; // Paper - OBFHELPER // Paper private final Map e = new java.util.concurrent.ConcurrentHashMap<>(); // Paper private final Deque f = new java.util.concurrent.LinkedBlockingDeque(); // CraftBukkit private final GameProfileRepository g; @@ -93,6 +93,7 @@ public class UserCache { return UserCache.c; } + public void saveProfile(GameProfile gameprofile) { a(gameprofile); } // Paper - OBFHELPER public void a(GameProfile gameprofile) { this.a(gameprofile, (Date) null); } @@ -154,6 +155,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); @@ -262,7 +270,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 71f2a8cfc2e077ce7c576957fd1d36f6612a4ebf..4786a8eaca956435f2f76ee9d5fb9e66658c24a1 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -225,6 +225,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; @@ -2228,5 +2231,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 } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java index 4fb27cc7ed062696239f75b6f85ddb0a31866568..c31011ff91f4ea8368e3afbc5ec07eff84e93fe2 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java @@ -71,6 +71,13 @@ class CraftMetaSkull extends CraftMetaItem implements SkullMeta { } private void setProfile(GameProfile profile) { + // Paper start + if (profile != null) { + com.destroystokyo.paper.profile.CraftPlayerProfile paperProfile = new com.destroystokyo.paper.profile.CraftPlayerProfile(profile); + paperProfile.completeFromCache(false, true); + profile = paperProfile.getGameProfile(); + } + // Paper end this.profile = profile; this.serializedProfile = (profile == null) ? null : GameProfileSerializer.serialize(new NBTTagCompound(), profile); }