From b8a672dd94395ee3d91c9301cf597fe47f4a29c3 Mon Sep 17 00:00:00 2001 From: Aikar Date: Thu, 22 Mar 2018 21:40:57 -0400 Subject: [PATCH] Fix Profile Textures and expand PlayerProfile .complete() API I mistakenly thought .complete() also checked for textures, which was not the case So the logic was not working as desired. Also some undesired logic paths lead to textures of the logging in player being dropped, forcing us to always load the textures immediately again on login, leading to rate limits. Everythings now good the .complete() api now will default specify to also complete textures, but you may pass false to it to skip loading textures. --- .../0057-Basic-PlayerProfile-API.patch | 38 +++- .../0214-Basic-PlayerProfile-API.patch | 206 ++++++++++-------- .../0278-Player.setPlayerProfile-API.patch | 35 ++- 3 files changed, 175 insertions(+), 104 deletions(-) diff --git a/Spigot-API-Patches/0057-Basic-PlayerProfile-API.patch b/Spigot-API-Patches/0057-Basic-PlayerProfile-API.patch index f0cad73ad0..a4fb2c8939 100644 --- a/Spigot-API-Patches/0057-Basic-PlayerProfile-API.patch +++ b/Spigot-API-Patches/0057-Basic-PlayerProfile-API.patch @@ -1,4 +1,4 @@ -From 0952cf855ca2b8bf45741e73250656ad452532c8 Mon Sep 17 00:00:00 2001 +From 15f696b679672e955cf4f3186130eb9afa2e0df6 Mon Sep 17 00:00:00 2001 From: Aikar Date: Mon, 15 Jan 2018 21:46:46 -0500 Subject: [PATCH] Basic PlayerProfile API @@ -7,10 +7,10 @@ Provides basic elements of a PlayerProfile to be used by future API/events diff --git a/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java b/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java new file mode 100644 -index 00000000..0f44f98f +index 00000000..404fb468 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/PlayerProfile.java -@@ -0,0 +1,97 @@ +@@ -0,0 +1,125 @@ +package com.destroystokyo.paper.profile; + +import com.mojang.authlib.GameProfile; @@ -43,6 +43,13 @@ index 00000000..0f44f98f + @Nonnull Set getProperties(); + + /** ++ * Check if the Profile has the specified property ++ * @param property Property name to check ++ * @return If the property is set ++ */ ++ boolean hasProperty(String property); ++ ++ /** + * Sets a property. If the property already exists, the previous one will be replaced + * @param property Property to set. + */ @@ -98,9 +105,30 @@ index 00000000..0f44f98f + /** + * If this profile is not complete, then make the API call to complete it. + * This is a blocking operation and should be done asynchronously. -+ * @return if the profile is now complete ++ * ++ * This will also complete textures. If you do not want to load textures, use {{@link #complete(boolean)}} ++ * @return if the profile is now complete (if you get rate limited, this operation may fail) + */ -+ boolean complete(); ++ default boolean complete() { ++ return complete(true); ++ } ++ ++ /** ++ * If this profile is not complete, then make the API call to complete it. ++ * This is a blocking operation and should be done asynchronously. ++ * ++ * Optionally will also fill textures. ++ * @return if the profile is now complete (if you get rate limited, this operation may fail) ++ */ ++ boolean complete(boolean textures); ++ ++ /** ++ * Whether or not this Profile has textures associated to it ++ * @return If has a textures property ++ */ ++ default boolean hasTextures() { ++ return hasProperty("textures"); ++ } + + /** + * @deprecated Will be removed in 1.13 diff --git a/Spigot-Server-Patches/0214-Basic-PlayerProfile-API.patch b/Spigot-Server-Patches/0214-Basic-PlayerProfile-API.patch index 1017c8c094..b3428dcd21 100644 --- a/Spigot-Server-Patches/0214-Basic-PlayerProfile-API.patch +++ b/Spigot-Server-Patches/0214-Basic-PlayerProfile-API.patch @@ -1,4 +1,4 @@ -From ece9ffa638c05b83affc7321bbe17f54774ff340 Mon Sep 17 00:00:00 2001 +From f925d0e5462649589e4dc8c9faf378d90f4b5df8 Mon Sep 17 00:00:00 2001 From: Aikar Date: Mon, 15 Jan 2018 22:11:48 -0500 Subject: [PATCH] Basic PlayerProfile API @@ -6,10 +6,10 @@ Subject: [PATCH] Basic PlayerProfile API 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 000000000..af6a81586 +index 000000000..616a7b218 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/profile/CraftPlayerProfile.java -@@ -0,0 +1,248 @@ +@@ -0,0 +1,262 @@ +package com.destroystokyo.paper.profile; + +import com.destroystokyo.paper.PaperConfig; @@ -33,45 +33,36 @@ index 000000000..af6a81586 +public class CraftPlayerProfile implements PlayerProfile { + + private GameProfile profile; -+ private final PropertySet properties; ++ private final PropertySet properties = new PropertySet(); + + public CraftPlayerProfile(CraftPlayer player) { -+ GameProfile playerProfile = player.getHandle().getProfile(); -+ this.profile = new GameProfile(playerProfile.getId(), playerProfile.getName()); -+ copyProfileProperties(this.profile, this.profile); -+ this.properties = new PropertySet(); ++ this.profile = player.getHandle().getProfile(); + } + -+ /** -+ * Constructs a new Game Profile with the specified ID and name. -+ *

-+ * Either ID or name may be null/empty, but at least one must be filled. -+ * -+ * @param id Unique ID of the profile -+ * @param name Display name of the profile -+ * @throws IllegalArgumentException Both ID and name are either null or empty -+ */ + public CraftPlayerProfile(UUID id, String name) { -+ this(createGameProfile(id, name)); ++ this.profile = createGameProfile(id, name); ++ } ++ ++ public CraftPlayerProfile(GameProfile profile) { ++ this.profile = profile; + } + + private static GameProfile createGameProfile(UUID id, String name) { + new GameProfile(id, name); // Validate that both are not null + MinecraftServer server = MinecraftServer.getServer(); + UserCache userCache = server.getUserCache(); -+ GameProfile profile = null; -+ boolean isOnlineMode = server.getOnlineMode() || (SpigotConfig.bungee && PaperConfig.bungeeOnlineMode); -+ if (id == null && isOnlineMode) { -+ profile = userCache.getProfile(name); -+ } ++ GameProfile profile; + -+ if (profile == null && name == null && id != null) { ++ if (id == null) { ++ profile = getProfileByName(name); ++ if (profile != null) { ++ id = profile.getId(); ++ } ++ } else { + profile = userCache.getProfile(id); -+ } -+ -+ if (profile == null && id == null && name != null && !isOnlineMode) { -+ // Make an OfflinePlayer using an offline mode UUID since the name has no profile -+ id = UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)); ++ if (profile != null) { ++ name = profile.getName(); ++ } + } + + GameProfile resultProfile = new GameProfile(id, name); @@ -81,6 +72,19 @@ index 000000000..af6a81586 + return resultProfile; + } + ++ private static GameProfile getProfileByName(String name) { ++ final GameProfile profile; ++ final MinecraftServer server = MinecraftServer.getServer(); ++ boolean isOnlineMode = server.getOnlineMode() || (SpigotConfig.bungee && PaperConfig.bungeeOnlineMode); ++ if (isOnlineMode) { ++ profile = server.getUserCache().getProfile(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); ++ } ++ return profile; ++ } ++ + private static void copyProfileProperties(GameProfile source, GameProfile target) { + PropertyMap properties = target.getProperties(); + properties.clear(); @@ -89,11 +93,85 @@ index 000000000..af6a81586 + } + } + -+ public CraftPlayerProfile(GameProfile profile) { -+ this.profile = profile; -+ this.properties = new PropertySet(); ++ @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())); ++ } ++ ++ @Override ++ public void setProperties(Collection properties) { ++ properties.forEach(this::setProperty); ++ } ++ ++ @Override ++ public boolean removeProperty(String property) { ++ return !profile.getProperties().removeAll(property).isEmpty(); ++ } ++ ++ @Override ++ public void clearProperties() { ++ profile.getProperties().clear(); ++ } ++ ++ @Override ++ public boolean isComplete() { ++ return profile.isComplete(); ++ } ++ ++ public boolean complete(boolean textures) { ++ MinecraftServer server = MinecraftServer.getServer(); ++ String name = profile.getName(); ++ if (profile.getId() == null) { ++ profile = getProfileByName(name); ++ if (profile == null) { ++ throw new NullPointerException("Could not get UUID for Player " + name); ++ } ++ } ++ if (!profile.isComplete() || (textures && !hasTextures())) { ++ GameProfile result = server.getSessionService().fillProfileProperties(profile, true); ++ if (result != null) { ++ this.profile = result; ++ } ++ } ++ return profile.isComplete() && (!textures || hasTextures()); ++ } ++ ++ 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(); ++ } ++ ++ + public GameProfile getGameProfile() { + return profile; + } @@ -138,70 +216,6 @@ index 000000000..af6a81586 + return clone; + } + -+ @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())); -+ } -+ -+ @Override -+ public void setProperties(Collection properties) { -+ properties.forEach(this::setProperty); -+ } -+ -+ @Override -+ public boolean removeProperty(String property) { -+ return !profile.getProperties().removeAll(property).isEmpty(); -+ } -+ -+ @Override -+ public void clearProperties() { -+ profile.getProperties().clear(); -+ } -+ -+ @Override -+ public boolean isComplete() { -+ return profile.isComplete(); -+ } -+ -+ public boolean complete() { -+ if (!profile.isComplete()) { -+ GameProfile result = MinecraftServer.getServer().getSessionService().fillProfileProperties(profile, true); -+ if (result != null) { -+ this.profile = result; -+ } -+ } -+ return profile.isComplete(); -+ } -+ -+ 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 { + diff --git a/Spigot-Server-Patches/0278-Player.setPlayerProfile-API.patch b/Spigot-Server-Patches/0278-Player.setPlayerProfile-API.patch index d1233f6d55..fd1e94bd24 100644 --- a/Spigot-Server-Patches/0278-Player.setPlayerProfile-API.patch +++ b/Spigot-Server-Patches/0278-Player.setPlayerProfile-API.patch @@ -1,4 +1,4 @@ -From 7c932f5bcecf83c9dc4402042a9329fc1af62585 Mon Sep 17 00:00:00 2001 +From cebd684862b0e016056fc87f8ddff96dadbcd272 Mon Sep 17 00:00:00 2001 From: Aikar Date: Sun, 18 Mar 2018 12:29:48 -0400 Subject: [PATCH] Player.setPlayerProfile API @@ -18,8 +18,37 @@ index 4b82e43a8..35fde8b23 100644 private ItemStack bV; private final ItemCooldown bW; @Nullable +diff --git a/src/main/java/net/minecraft/server/LoginListener.java b/src/main/java/net/minecraft/server/LoginListener.java +index 2842956bf..8aeded425 100644 +--- a/src/main/java/net/minecraft/server/LoginListener.java ++++ b/src/main/java/net/minecraft/server/LoginListener.java +@@ -41,7 +41,7 @@ public class LoginListener implements PacketLoginInListener, ITickable { + public final NetworkManager networkManager; + private LoginListener.EnumProtocolState g; + private int h; +- private GameProfile i; ++ private GameProfile i; private void setGameProfile(GameProfile profile) { i = profile; } private GameProfile getGameProfile() { return i; } // Paper - OBFHELPER + private final String j; + private SecretKey loginKey; + private EntityPlayer l; +@@ -294,12 +294,12 @@ public class LoginListener implements PacketLoginInListener, ITickable { + final org.bukkit.craftbukkit.CraftServer server = LoginListener.this.server.server; + + // Paper start +- PlayerProfile profile = Bukkit.createProfile(uniqueId, playerName); ++ PlayerProfile profile = CraftPlayerProfile.asBukkitMirror(getGameProfile()); + AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId, profile); + server.getPluginManager().callEvent(asyncEvent); + profile = asyncEvent.getPlayerProfile(); +- profile.complete(); +- i = CraftPlayerProfile.asAuthlibCopy(profile); ++ profile.complete(true); ++ setGameProfile(CraftPlayerProfile.asAuthlib(profile)); + playerName = i.getName(); + uniqueId = i.getId(); + // Paper end diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 21631c588..9c894185b 100644 +index 21631c588..1e73a3308 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -1,6 +1,8 @@ @@ -82,7 +111,7 @@ index 21631c588..9c894185b 100644 + } + } + public PlayerProfile getPlayerProfile() { -+ return new CraftPlayerProfile(this); ++ return new CraftPlayerProfile(this).clone(); + } + // Paper end