From 7a108da7f391babab28590c54bc01302453733f6 Mon Sep 17 00:00:00 2001 From: md_5 Date: Sat, 12 Apr 2014 14:01:22 +1000 Subject: [PATCH] Remove some snapshot related patches in preparation for update. --- ...> 0123-Enable-Improved-Ping-Sending.patch} | 8 +- ...ix-several-occurances-of-missed-diff.patch | 136 - ...etOfflinePlayer-UUID-on-main-thread.patch} | 4 +- ...ragon-death-and-wither-spawn-sounds.patch} | 4 +- ...getting-ticked-after-being-queued-f.patch} | 2 +- ...-client-crashes-server-lists-and-Mo.patch} | 8 +- CraftBukkit-Patches/0128-1.7.8-support.patch | 3223 ----------------- .../0129-Convert-player-heads-async.patch | 316 -- 8 files changed, 13 insertions(+), 3688 deletions(-) rename CraftBukkit-Patches/{0124-Enable-Improved-Ping-Sending.patch => 0123-Enable-Improved-Ping-Sending.patch} (94%) delete mode 100644 CraftBukkit-Patches/0123-Fix-several-occurances-of-missed-diff.patch rename CraftBukkit-Patches/{0125-Prevent-getOfflinePlayer-UUID-on-main-thread.patch => 0124-Prevent-getOfflinePlayer-UUID-on-main-thread.patch} (90%) rename CraftBukkit-Patches/{0126-Configurable-dragon-death-and-wither-spawn-sounds.patch => 0125-Configurable-dragon-death-and-wither-spawn-sounds.patch} (97%) rename CraftBukkit-Patches/{0127-Fix-TileEntities-getting-ticked-after-being-queued-f.patch => 0126-Fix-TileEntities-getting-ticked-after-being-queued-f.patch} (95%) rename CraftBukkit-Patches/{0130-Display-Spigot-in-client-crashes-server-lists-and-Mo.patch => 0127-Display-Spigot-in-client-crashes-server-lists-and-Mo.patch} (80%) delete mode 100644 CraftBukkit-Patches/0128-1.7.8-support.patch delete mode 100644 CraftBukkit-Patches/0129-Convert-player-heads-async.patch diff --git a/CraftBukkit-Patches/0124-Enable-Improved-Ping-Sending.patch b/CraftBukkit-Patches/0123-Enable-Improved-Ping-Sending.patch similarity index 94% rename from CraftBukkit-Patches/0124-Enable-Improved-Ping-Sending.patch rename to CraftBukkit-Patches/0123-Enable-Improved-Ping-Sending.patch index c9d1f23718..7415d8016b 100644 --- a/CraftBukkit-Patches/0124-Enable-Improved-Ping-Sending.patch +++ b/CraftBukkit-Patches/0123-Enable-Improved-Ping-Sending.patch @@ -1,11 +1,11 @@ -From 0667d6da84d92d80b6be70145083fa4cc5bd913c Mon Sep 17 00:00:00 2001 +From cf42ff147f5671741937ce529c714d39aeadc797 Mon Sep 17 00:00:00 2001 From: Aikar Date: Sun, 24 Feb 2013 20:45:20 +1100 Subject: [PATCH] Enable Improved Ping Sending diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/java/net/minecraft/server/EntityPlayer.java -index 27f9dc9..5788872 100644 +index ca06f60..787c764 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java @@ -62,6 +62,7 @@ public class EntityPlayer extends EntityHuman implements ICrafting { @@ -17,7 +17,7 @@ index 27f9dc9..5788872 100644 // Spigot start public boolean collidesWithEntities = true; diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java -index 25810d3..bf353e0 100644 +index c4698d4..59ba67c 100644 --- a/src/main/java/net/minecraft/server/PlayerList.java +++ b/src/main/java/net/minecraft/server/PlayerList.java @@ -784,6 +784,8 @@ public abstract class PlayerList { @@ -61,5 +61,5 @@ index 25810d3..bf353e0 100644 public void sendAll(Packet packet) { -- -1.8.5.2.msysgit.0 +1.8.3.2 diff --git a/CraftBukkit-Patches/0123-Fix-several-occurances-of-missed-diff.patch b/CraftBukkit-Patches/0123-Fix-several-occurances-of-missed-diff.patch deleted file mode 100644 index c896e6fb33..0000000000 --- a/CraftBukkit-Patches/0123-Fix-several-occurances-of-missed-diff.patch +++ /dev/null @@ -1,136 +0,0 @@ -From 433062e42e703ec2032fcdef0845b3569c8635a7 Mon Sep 17 00:00:00 2001 -From: md_5 -Date: Wed, 26 Mar 2014 21:21:35 +1100 -Subject: [PATCH] Fix several occurances of missed diff. - - -diff --git a/src/main/java/net/minecraft/server/DedicatedServer.java b/src/main/java/net/minecraft/server/DedicatedServer.java -index e91d53f..22becf7 100644 ---- a/src/main/java/net/minecraft/server/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/DedicatedServer.java -@@ -182,7 +182,7 @@ public class DedicatedServer extends MinecraftServer implements IMinecraftServer - this.c((this.getMaxBuildHeight() + 8) / 16 * 16); - this.c(MathHelper.a(this.getMaxBuildHeight(), 64, 256)); - this.propertyManager.a("max-build-height", Integer.valueOf(this.getMaxBuildHeight())); -- h.info("Preparing level \"" + this.M() + "\""); -+ h.info("Preparing level \"" + this.N() + "\""); - this.a(this.N(), this.N(), k, worldtype, s2); - long i1 = System.nanoTime() - j; - String s3 = String.format("%.3fs", new Object[] { Double.valueOf((double) i1 / 1.0E9D)}); -diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java -index f53b183..dbf30cb 100644 ---- a/src/main/java/net/minecraft/server/EntityEnderDragon.java -+++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java -@@ -135,7 +135,7 @@ public class EntityEnderDragon extends EntityInsentient implements IComplex, IMo - d2 = this.locZ + (this.bj - this.locZ) / (double) this.bg; - d3 = MathHelper.g(this.bk - (double) this.yaw); - this.yaw = (float) ((double) this.yaw + d3 / (double) this.bg); -- this.pitch = (float) ((double) this.pitch + (this.bm - (double) this.pitch) / (double) this.bg); -+ this.pitch = (float) ((double) this.pitch + (this.bl - (double) this.pitch) / (double) this.bg); - --this.bg; - this.setPosition(d0, d1, d2); - this.b(this.yaw, this.pitch); -diff --git a/src/main/java/net/minecraft/server/EntityHuman.java b/src/main/java/net/minecraft/server/EntityHuman.java -index 1f0e29a..b0b1462 100644 ---- a/src/main/java/net/minecraft/server/EntityHuman.java -+++ b/src/main/java/net/minecraft/server/EntityHuman.java -@@ -78,7 +78,7 @@ public abstract class EntityHuman extends EntityLiving implements ICommandListen - ChunkCoordinates chunkcoordinates = world.getSpawn(); - - this.setPositionRotation((double) chunkcoordinates.x + 0.5D, (double) (chunkcoordinates.y + 1), (double) chunkcoordinates.z + 0.5D, 0.0F, 0.0F); -- this.az = 180.0F; -+ this.aZ = 180.0F; - this.maxFireTicks = 20; - } - -@@ -190,7 +190,7 @@ public abstract class EntityHuman extends EntityLiving implements ICommandListen - } - - if (d0 < -d3) { -- this.bu = this.by = this.locX; -+ this.bu = this.bx = this.locX; - } - - if (d2 < -d3) { -@@ -198,7 +198,7 @@ public abstract class EntityHuman extends EntityLiving implements ICommandListen - } - - if (d1 < -d3) { -- this.bv = this.bz = this.locY; -+ this.bv = this.by = this.locY; - } - - this.bx += d0 * 0.25D; -diff --git a/src/main/java/net/minecraft/server/EntityItem.java b/src/main/java/net/minecraft/server/EntityItem.java -index 0f1bcc7..5eb893f 100644 ---- a/src/main/java/net/minecraft/server/EntityItem.java -+++ b/src/main/java/net/minecraft/server/EntityItem.java -@@ -320,7 +320,7 @@ public class EntityItem extends Entity { - public ItemStack getItemStack() { - ItemStack itemstack = this.getDataWatcher().getItemStack(10); - -- return itemstack == null ? new ItemStack(Blocks.STONE) : itemstack; -+ return itemstack == null ? new ItemStack(Blocks.STONE) : itemstack; - } - - public void setItemStack(ItemStack itemstack) { -diff --git a/src/main/java/net/minecraft/server/EntityMinecartAbstract.java b/src/main/java/net/minecraft/server/EntityMinecartAbstract.java -index f565028..166206c 100644 ---- a/src/main/java/net/minecraft/server/EntityMinecartAbstract.java -+++ b/src/main/java/net/minecraft/server/EntityMinecartAbstract.java -@@ -199,7 +199,7 @@ public abstract class EntityMinecartAbstract extends Entity { - } - - if (this.locY < -64.0D) { -- this.G(); -+ this.F(); - } - - int i; -diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java -index b2cde75..b8d08e2 100644 ---- a/src/main/java/net/minecraft/server/PlayerConnection.java -+++ b/src/main/java/net/minecraft/server/PlayerConnection.java -@@ -353,7 +353,7 @@ public class PlayerConnection implements PacketPlayInListener { - } - - this.player.i(); -- this.player.W = 0.0F; -+ this.player.V = 0.0F; - this.player.setLocation(this.y, this.z, this.q, f2, f3); - if (!this.checkMovement) { - return; -diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java -index c4698d4..25810d3 100644 ---- a/src/main/java/net/minecraft/server/PlayerList.java -+++ b/src/main/java/net/minecraft/server/PlayerList.java -@@ -51,7 +51,7 @@ public abstract class PlayerList { - public IPlayerFileData playerFileData; // CraftBukkit - private -> public - public boolean hasWhitelist; // CraftBukkit - private -> public - protected int maxPlayers; -- protected int m; -+ private int m; - private EnumGamemode n; - private boolean o; - private int p; -diff --git a/src/main/java/net/minecraft/server/StatisticManager.java b/src/main/java/net/minecraft/server/StatisticManager.java -index e86e21b..418ec40 100644 ---- a/src/main/java/net/minecraft/server/StatisticManager.java -+++ b/src/main/java/net/minecraft/server/StatisticManager.java -@@ -8,11 +8,10 @@ public class StatisticManager { - - protected final Map a = Maps.newConcurrentMap(); - -- public StatisticManager() { -- } -+ public StatisticManager() {} - - public boolean a(Achievement achievement) { -- return this.getStatisticValue((Statistic) achievement) > 0; -+ return this.getStatisticValue(achievement) > 0; - } - - public boolean b(Achievement achievement) { --- -1.8.5.2.msysgit.0 - diff --git a/CraftBukkit-Patches/0125-Prevent-getOfflinePlayer-UUID-on-main-thread.patch b/CraftBukkit-Patches/0124-Prevent-getOfflinePlayer-UUID-on-main-thread.patch similarity index 90% rename from CraftBukkit-Patches/0125-Prevent-getOfflinePlayer-UUID-on-main-thread.patch rename to CraftBukkit-Patches/0124-Prevent-getOfflinePlayer-UUID-on-main-thread.patch index 70a33f94c9..1e0bceb45a 100644 --- a/CraftBukkit-Patches/0125-Prevent-getOfflinePlayer-UUID-on-main-thread.patch +++ b/CraftBukkit-Patches/0124-Prevent-getOfflinePlayer-UUID-on-main-thread.patch @@ -1,11 +1,11 @@ -From 199e63f001def1fb39642d5f6448544493ec37c7 Mon Sep 17 00:00:00 2001 +From 3f5a0704fe6de51b06e554c07f37237240d4022e Mon Sep 17 00:00:00 2001 From: md_5 Date: Sun, 30 Mar 2014 09:15:35 +1100 Subject: [PATCH] Prevent getOfflinePlayer(UUID) on main thread. diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 4c089ea..8961015 100644 +index 707410b..446b046 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -1341,6 +1341,7 @@ public final class CraftServer implements Server { diff --git a/CraftBukkit-Patches/0126-Configurable-dragon-death-and-wither-spawn-sounds.patch b/CraftBukkit-Patches/0125-Configurable-dragon-death-and-wither-spawn-sounds.patch similarity index 97% rename from CraftBukkit-Patches/0126-Configurable-dragon-death-and-wither-spawn-sounds.patch rename to CraftBukkit-Patches/0125-Configurable-dragon-death-and-wither-spawn-sounds.patch index b2c76976f4..0da0425eea 100644 --- a/CraftBukkit-Patches/0126-Configurable-dragon-death-and-wither-spawn-sounds.patch +++ b/CraftBukkit-Patches/0125-Configurable-dragon-death-and-wither-spawn-sounds.patch @@ -1,11 +1,11 @@ -From 8c8e29dbb30f6945a78a1a215a1e866421013bcf Mon Sep 17 00:00:00 2001 +From 3acd3a83d766bdd1a50b42110532e56131a5716c Mon Sep 17 00:00:00 2001 From: drXor Date: Sat, 29 Mar 2014 13:44:25 -0400 Subject: [PATCH] Configurable dragon death and wither spawn sounds diff --git a/src/main/java/net/minecraft/server/EntityEnderDragon.java b/src/main/java/net/minecraft/server/EntityEnderDragon.java -index dbf30cb..64b0992 100644 +index f53b183..9a8408c 100644 --- a/src/main/java/net/minecraft/server/EntityEnderDragon.java +++ b/src/main/java/net/minecraft/server/EntityEnderDragon.java @@ -550,7 +550,14 @@ public class EntityEnderDragon extends EntityInsentient implements IComplex, IMo diff --git a/CraftBukkit-Patches/0127-Fix-TileEntities-getting-ticked-after-being-queued-f.patch b/CraftBukkit-Patches/0126-Fix-TileEntities-getting-ticked-after-being-queued-f.patch similarity index 95% rename from CraftBukkit-Patches/0127-Fix-TileEntities-getting-ticked-after-being-queued-f.patch rename to CraftBukkit-Patches/0126-Fix-TileEntities-getting-ticked-after-being-queued-f.patch index 85433c7ea2..1661a92fd2 100644 --- a/CraftBukkit-Patches/0127-Fix-TileEntities-getting-ticked-after-being-queued-f.patch +++ b/CraftBukkit-Patches/0126-Fix-TileEntities-getting-ticked-after-being-queued-f.patch @@ -1,4 +1,4 @@ -From 21c02e2e18bf05f41a934f259d3c80547d5c24fb Mon Sep 17 00:00:00 2001 +From 4a11712fc763f0233db7c951d714cfb1b41e8555 Mon Sep 17 00:00:00 2001 From: FrozenBrain Date: Sun, 23 Mar 2014 01:49:13 +0100 Subject: [PATCH] Fix TileEntities getting ticked after being queued for diff --git a/CraftBukkit-Patches/0130-Display-Spigot-in-client-crashes-server-lists-and-Mo.patch b/CraftBukkit-Patches/0127-Display-Spigot-in-client-crashes-server-lists-and-Mo.patch similarity index 80% rename from CraftBukkit-Patches/0130-Display-Spigot-in-client-crashes-server-lists-and-Mo.patch rename to CraftBukkit-Patches/0127-Display-Spigot-in-client-crashes-server-lists-and-Mo.patch index 852c40bb56..a15c63dceb 100644 --- a/CraftBukkit-Patches/0130-Display-Spigot-in-client-crashes-server-lists-and-Mo.patch +++ b/CraftBukkit-Patches/0127-Display-Spigot-in-client-crashes-server-lists-and-Mo.patch @@ -1,4 +1,4 @@ -From ed6e47fa12d55ee06fcb40251756390f3e8fb6c3 Mon Sep 17 00:00:00 2001 +From 4b7d88914d7e2c7ec619697ee2d60dabea61aeb7 Mon Sep 17 00:00:00 2001 From: Thinkofdeath Date: Fri, 11 Apr 2014 11:16:34 +0100 Subject: [PATCH] Display 'Spigot' in client crashes, server lists and Mojang @@ -6,10 +6,10 @@ Subject: [PATCH] Display 'Spigot' in client crashes, server lists and Mojang diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 96b904c..b314181 100644 +index 8ce9dd7..cbf4ade 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -957,7 +957,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo +@@ -955,7 +955,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo } public String getServerModName() { @@ -19,5 +19,5 @@ index 96b904c..b314181 100644 public CrashReport b(CrashReport crashreport) { -- -1.8.5.2.msysgit.0 +1.8.3.2 diff --git a/CraftBukkit-Patches/0128-1.7.8-support.patch b/CraftBukkit-Patches/0128-1.7.8-support.patch deleted file mode 100644 index 31ad58d6e4..0000000000 --- a/CraftBukkit-Patches/0128-1.7.8-support.patch +++ /dev/null @@ -1,3223 +0,0 @@ -From 809c29fa77ca09648e0035f1b1861a13dace5442 Mon Sep 17 00:00:00 2001 -From: Thinkofdeath -Date: Thu, 3 Apr 2014 17:04:18 +0100 -Subject: [PATCH] 1.7.8 support - - -diff --git a/pom.xml b/pom.xml -index c8285e0..24d101c 100644 ---- a/pom.xml -+++ b/pom.xml -@@ -31,6 +31,10 @@ - repobo-snap - http://repo.bukkit.org/content/groups/public - -+ -+ vanilla -+ https://libraries.minecraft.net/ -+ - - - -@@ -114,6 +118,21 @@ - trove4j - 3.0.3 - -+ -+ org.apache.commons -+ commons-lang3 -+ 3.2.1 -+ -+ -+ commons-io -+ commons-io -+ 2.4 -+ -+ -+ commons-codec -+ commons-codec -+ 1.6 -+ - - - -diff --git a/src/main/java/net/minecraft/server/HandshakeListener.java b/src/main/java/net/minecraft/server/HandshakeListener.java -index 42539b4..490123f 100644 ---- a/src/main/java/net/minecraft/server/HandshakeListener.java -+++ b/src/main/java/net/minecraft/server/HandshakeListener.java -@@ -1,5 +1,6 @@ - package net.minecraft.server; - -+import net.minecraft.util.io.netty.util.AttributeKey; - import net.minecraft.util.io.netty.util.concurrent.GenericFutureListener; - - // CraftBukkit start -@@ -13,6 +14,7 @@ public class HandshakeListener implements PacketHandshakingInListener { - private static final HashMap throttleTracker = new HashMap(); - private static int throttleCounter = 0; - // CraftBukkit end -+ public static final AttributeKey protocolVersion = new AttributeKey( "protocolVersion" ); // Spigot - - private final MinecraftServer a; - private final NetworkManager b; -@@ -23,6 +25,12 @@ public class HandshakeListener implements PacketHandshakingInListener { - } - - public void a(PacketHandshakingInSetProtocol packethandshakinginsetprotocol) { -+ // Spigot start -+ b.m.attr( protocolVersion ).set( 4 ); -+ if (packethandshakinginsetprotocol.d() == 5) { -+ b.m.attr( protocolVersion ).set( 5 ); -+ } -+ // Spigot end - switch (ProtocolOrdinalWrapper.a[packethandshakinginsetprotocol.c().ordinal()]) { - case 1: - this.b.a(EnumProtocol.LOGIN); -@@ -62,8 +70,7 @@ public class HandshakeListener implements PacketHandshakingInListener { - org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); - } - // CraftBukkit end -- -- if (packethandshakinginsetprotocol.d() > 4) { -+ if (packethandshakinginsetprotocol.d() > 5) { // Spigot - chatcomponenttext = new ChatComponentText( org.spigotmc.SpigotConfig.outdatedServerMessage ); // Spigot - this.b.handle(new PacketLoginOutDisconnect(chatcomponenttext), new GenericFutureListener[0]); - this.b.close(chatcomponenttext); -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 8ce9dd7..34c0703 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -10,13 +10,13 @@ import java.util.ArrayList; - import java.util.Arrays; - import java.util.Collections; - import java.util.Date; --import java.util.Iterator; - import java.util.List; - import java.util.Random; - import java.util.UUID; - import java.util.concurrent.Callable; - import javax.imageio.ImageIO; - -+import org.spigotmc.authlib.yggdrasil.YggdrasilMinecraftSessionService; - import net.minecraft.util.com.google.common.base.Charsets; - import net.minecraft.util.com.mojang.authlib.GameProfile; - import net.minecraft.util.com.mojang.authlib.minecraft.MinecraftSessionService; -@@ -107,6 +107,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo - private static final int TICK_TIME = 1000000000 / TPS; - private static final int SAMPLE_INTERVAL = 100; - public final double[] recentTps = new double[ 3 ]; -+ public final org.spigotmc.authlib.minecraft.MinecraftSessionService newSessionService; - // Spigot end - - public MinecraftServer(OptionSet options, Proxy proxy) { // CraftBukkit - signature file -> OptionSet -@@ -117,6 +118,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo - this.n = new CommandDispatcher(); - // this.convertable = new WorldLoaderServer(file1); // CraftBukkit - moved to DedicatedServer.init - this.S = (new YggdrasilAuthenticationService(proxy, UUID.randomUUID().toString())).createMinecraftSessionService(); -+ newSessionService = new org.spigotmc.authlib.yggdrasil.YggdrasilAuthenticationService(proxy, UUID.randomUUID().toString()).createMinecraftSessionService(); - - // CraftBukkit start - this.options = options; -@@ -871,7 +873,7 @@ public abstract class MinecraftServer implements ICommandListener, Runnable, IMo - } - - public String getVersion() { -- return "1.7.5"; -+ return "1.7.8"; - } - - public int C() { -diff --git a/src/main/java/net/minecraft/server/NetworkManager.java b/src/main/java/net/minecraft/server/NetworkManager.java -index f6cca80..56bfe34 100644 ---- a/src/main/java/net/minecraft/server/NetworkManager.java -+++ b/src/main/java/net/minecraft/server/NetworkManager.java -@@ -35,7 +35,7 @@ public class NetworkManager extends SimpleChannelInboundHandler { - private final boolean j; - private final Queue k = Queues.newConcurrentLinkedQueue(); - private final Queue l = Queues.newConcurrentLinkedQueue(); -- private Channel m; -+ public Channel m; // Spigot - public SocketAddress n; // Spigot - public String spoofedUUID; // Spigot - private PacketListener o; -diff --git a/src/main/java/net/minecraft/server/Packet.java b/src/main/java/net/minecraft/server/Packet.java -index 592ffc5..190da32 100644 ---- a/src/main/java/net/minecraft/server/Packet.java -+++ b/src/main/java/net/minecraft/server/Packet.java -@@ -47,6 +47,12 @@ public abstract class Packet { - - public abstract void b(PacketDataSerializer packetdataserializer) throws IOException; // CraftBukkit - added throws - -+ // Spigot start -+ public void writeSnapshot(PacketDataSerializer packetDataSerializer) throws IOException { -+ b( packetDataSerializer ); -+ } -+ // Spigot end -+ - public abstract void handle(PacketListener packetlistener); - - public boolean a() { -diff --git a/src/main/java/net/minecraft/server/PacketEncoder.java b/src/main/java/net/minecraft/server/PacketEncoder.java -new file mode 100644 -index 0000000..ab00152 ---- /dev/null -+++ b/src/main/java/net/minecraft/server/PacketEncoder.java -@@ -0,0 +1,52 @@ -+package net.minecraft.server; -+ -+import java.io.IOException; -+ -+import net.minecraft.util.com.google.common.collect.BiMap; -+import net.minecraft.util.io.netty.buffer.ByteBuf; -+import net.minecraft.util.io.netty.channel.ChannelHandlerContext; -+import net.minecraft.util.io.netty.handler.codec.MessageToByteEncoder; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+import org.apache.logging.log4j.Marker; -+import org.apache.logging.log4j.MarkerManager; -+ -+public class PacketEncoder extends MessageToByteEncoder { -+ -+ private static final Logger a = LogManager.getLogger(); -+ private static final Marker b = MarkerManager.getMarker("PACKET_SENT", NetworkManager.b); -+ private final NetworkStatistics c; -+ -+ public PacketEncoder(NetworkStatistics networkstatistics) { -+ this.c = networkstatistics; -+ } -+ -+ protected void a(ChannelHandlerContext channelhandlercontext, Packet packet, ByteBuf bytebuf) throws IOException -+ { -+ Integer integer = (Integer) ((BiMap) channelhandlercontext.channel().attr(NetworkManager.f).get()).inverse().get(packet.getClass()); -+ -+ if (a.isDebugEnabled()) { -+ a.debug(b, "OUT: [{}:{}] {}[{}]", new Object[] { channelhandlercontext.channel().attr(NetworkManager.d).get(), integer, packet.getClass().getName(), packet.b()}); -+ } -+ -+ if (integer == null) { -+ throw new IOException("Can\'t serialize unregistered packet"); -+ } else { -+ PacketDataSerializer packetdataserializer = new PacketDataSerializer(bytebuf); -+ -+ packetdataserializer.b(integer.intValue()); -+ if ( channelhandlercontext.channel().attr( HandshakeListener.protocolVersion ).get() == 4) -+ { -+ packet.b( packetdataserializer ); -+ } else { -+ packet.writeSnapshot( packetdataserializer ); -+ } -+ this.c.b(integer.intValue(), (long) packetdataserializer.readableBytes()); -+ } -+ } -+ -+ protected void encode(ChannelHandlerContext channelhandlercontext, Object object, ByteBuf bytebuf) throws IOException -+ { -+ this.a(channelhandlercontext, (Packet) object, bytebuf); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/PacketLoginOutSuccess.java b/src/main/java/net/minecraft/server/PacketLoginOutSuccess.java -new file mode 100644 -index 0000000..3aa93cd ---- /dev/null -+++ b/src/main/java/net/minecraft/server/PacketLoginOutSuccess.java -@@ -0,0 +1,51 @@ -+package net.minecraft.server; -+ -+import net.minecraft.util.com.mojang.authlib.GameProfile; -+ -+import java.io.IOException; -+ -+public class PacketLoginOutSuccess extends Packet { -+ -+ private GameProfile a; -+ -+ public PacketLoginOutSuccess() {} -+ -+ public PacketLoginOutSuccess(GameProfile gameprofile) { -+ this.a = gameprofile; -+ } -+ -+ public void a(PacketDataSerializer packetdataserializer) throws IOException -+ { -+ String s = packetdataserializer.c(36); -+ String s1 = packetdataserializer.c(16); -+ -+ this.a = new GameProfile(s, s1); -+ } -+ -+ public void b(PacketDataSerializer packetdataserializer) throws IOException -+ { -+ packetdataserializer.a(this.a.getId()); -+ packetdataserializer.a(this.a.getName()); -+ } -+ -+ // Spigot start -+ @Override -+ public void writeSnapshot(PacketDataSerializer packetdataserializer) throws IOException -+ { -+ packetdataserializer.a( EntityHuman.a( this.a ).toString() ); -+ packetdataserializer.a( this.a.getName()); -+ } -+ // Spigot end -+ -+ public void a(PacketLoginOutListener packetloginoutlistener) { -+ packetloginoutlistener.a(this); -+ } -+ -+ public boolean a() { -+ return true; -+ } -+ -+ public void handle(PacketListener packetlistener) { -+ this.a((PacketLoginOutListener) packetlistener); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/PacketPlayOutNamedEntitySpawn.java b/src/main/java/net/minecraft/server/PacketPlayOutNamedEntitySpawn.java -index 8bab528..0884047 100644 ---- a/src/main/java/net/minecraft/server/PacketPlayOutNamedEntitySpawn.java -+++ b/src/main/java/net/minecraft/server/PacketPlayOutNamedEntitySpawn.java -@@ -2,6 +2,7 @@ package net.minecraft.server; - - import java.util.List; - -+import org.spigotmc.authlib.properties.Property; - import net.minecraft.util.com.mojang.authlib.GameProfile; - - import java.io.IOException; // CraftBukkit -@@ -60,6 +61,41 @@ public class PacketPlayOutNamedEntitySpawn extends Packet { - this.i.a(packetdataserializer); - } - -+ // Spigot start -+ @Override -+ public void writeSnapshot(PacketDataSerializer packetdataserializer) throws IOException -+ { // CraftBukkit - added throws -+ packetdataserializer.b( this.a ); -+ packetdataserializer.a( EntityHuman.a( this.b ).toString() ); -+ packetdataserializer.a( this.b.getName().length() > 16 ? this.b.getName().substring( 0, 16 ) : this.b.getName() ); // CraftBukkit - Limit name length to 16 characters -+ -+ if ( this.b instanceof ThreadPlayerLookupUUID.NewGameProfileWrapper ) -+ { -+ org.spigotmc.authlib.GameProfile newProfile = ((ThreadPlayerLookupUUID.NewGameProfileWrapper) b).newProfile; -+ packetdataserializer.b( newProfile.getProperties().size() ); -+ for ( String key : newProfile.getProperties().keys() ) -+ { -+ for ( Property prop : newProfile.getProperties().get( key ) ) -+ { -+ packetdataserializer.a( prop.getName() ); -+ packetdataserializer.a( prop.getValue() ); -+ packetdataserializer.a( prop.getSignature() ); -+ } -+ } -+ } else { -+ packetdataserializer.b( 0 ); -+ } -+ packetdataserializer.writeInt( this.c ); -+ packetdataserializer.writeInt( this.d ); -+ packetdataserializer.writeInt( this.e ); -+ packetdataserializer.writeByte( this.f ); -+ packetdataserializer.writeByte( this.g ); -+ packetdataserializer.writeShort( this.h ); -+ this.i.a( packetdataserializer ); -+ } -+ -+ // Spigot end -+ - public void a(PacketPlayOutListener packetplayoutlistener) { - packetplayoutlistener.a(this); - } -diff --git a/src/main/java/net/minecraft/server/PacketPlayOutTileEntityData.java b/src/main/java/net/minecraft/server/PacketPlayOutTileEntityData.java -new file mode 100644 -index 0000000..005f1fe ---- /dev/null -+++ b/src/main/java/net/minecraft/server/PacketPlayOutTileEntityData.java -@@ -0,0 +1,61 @@ -+package net.minecraft.server; -+ -+public class PacketPlayOutTileEntityData extends Packet { -+ -+ private int a; -+ private int b; -+ private int c; -+ private int d; -+ private NBTTagCompound e; -+ -+ public PacketPlayOutTileEntityData() {} -+ -+ public PacketPlayOutTileEntityData(int i, int j, int k, int l, NBTTagCompound nbttagcompound) { -+ this.a = i; -+ this.b = j; -+ this.c = k; -+ this.d = l; -+ this.e = nbttagcompound; -+ } -+ -+ public void a(PacketDataSerializer packetdataserializer) { -+ this.a = packetdataserializer.readInt(); -+ this.b = packetdataserializer.readShort(); -+ this.c = packetdataserializer.readInt(); -+ this.d = packetdataserializer.readUnsignedByte(); -+ this.e = packetdataserializer.b(); -+ } -+ -+ public void b(PacketDataSerializer packetdataserializer) { -+ packetdataserializer.writeInt(this.a); -+ packetdataserializer.writeShort(this.b); -+ packetdataserializer.writeInt(this.c); -+ packetdataserializer.writeByte((byte) this.d); -+ packetdataserializer.a(this.e); -+ } -+ -+ @Override -+ public void writeSnapshot(PacketDataSerializer packetdataserializer) -+ { -+ packetdataserializer.writeInt(this.a); -+ packetdataserializer.writeShort(this.b); -+ packetdataserializer.writeInt(this.c); -+ packetdataserializer.writeByte((byte) this.d); -+ if ( this.e.hasKey( "ExtraType" ) ) -+ { -+ NBTTagCompound profile = new NBTTagCompound(); -+ profile.setString( "Name", this.e.getString( "ExtraType" ) ); -+ profile.setString( "Id", "" ); -+ this.e.set( "Owner", profile ); -+ } -+ packetdataserializer.a(this.e); -+ } -+ -+ public void a(PacketPlayOutListener packetplayoutlistener) { -+ packetplayoutlistener.a(this); -+ } -+ -+ public void handle(PacketListener packetlistener) { -+ this.a((PacketPlayOutListener) packetlistener); -+ } -+} -diff --git a/src/main/java/net/minecraft/server/PacketStatusListener.java b/src/main/java/net/minecraft/server/PacketStatusListener.java -index f9da452..fa493ca 100644 ---- a/src/main/java/net/minecraft/server/PacketStatusListener.java -+++ b/src/main/java/net/minecraft/server/PacketStatusListener.java -@@ -4,6 +4,7 @@ import java.net.InetSocketAddress; - - // CraftBukkit start - import java.util.Iterator; -+import java.util.UUID; - - import org.bukkit.craftbukkit.util.CraftIconCache; - import org.bukkit.entity.Player; -@@ -117,13 +118,22 @@ public class PacketStatusListener implements PacketStatusInListener { - profiles = profiles.subList( 0, Math.min( profiles.size(), org.spigotmc.SpigotConfig.playerSample ) ); // Cap the sample to n (or less) displayed players, ie: Vanilla behaviour - } - // Spigot End -- playerSample.a(profiles.toArray(new GameProfile[profiles.size()])); -+ // Spigot start -+ GameProfile[] aProfiles = profiles.toArray( new GameProfile[ profiles.size() ] ); -+ if ( networkManager.m.attr( HandshakeListener.protocolVersion ).get() == 5 ) -+ { -+ for (int i = 0; i < aProfiles.length; i++) { -+ aProfiles[i] = new GameProfileWrapper( EntityHuman.a( aProfiles[i] ), aProfiles[i].getName() ); -+ } -+ } -+ // Spigot end -+ playerSample.a(aProfiles); - - ServerPing ping = new ServerPing(); - ping.setFavicon(event.icon.value); - ping.setMOTD(new ChatComponentText(event.getMotd())); - ping.setPlayerSample(playerSample); -- ping.setServerInfo(new ServerPingServerData(minecraftServer.getServerModName() + " " + minecraftServer.getVersion(), 4)); // TODO: Update when protocol changes -+ ping.setServerInfo(new ServerPingServerData(minecraftServer.getServerModName() + " " + minecraftServer.getVersion(), networkManager.m.attr( HandshakeListener.protocolVersion ).get())); // Spigot // TODO: Update when protocol changes - - this.networkManager.handle(new PacketStatusOutServerInfo(ping), new GenericFutureListener[0]); - // CraftBukkit end -@@ -132,4 +142,23 @@ public class PacketStatusListener implements PacketStatusInListener { - public void a(PacketStatusInPing packetstatusinping) { - this.networkManager.handle(new PacketStatusOutPong(packetstatusinping.c()), new GenericFutureListener[0]); - } -+ -+ -+ // Spigot start -+ private static class GameProfileWrapper extends GameProfile { -+ -+ private final UUID uuid; -+ -+ public GameProfileWrapper(UUID uuid, String name) { -+ super("", name); -+ this.uuid = uuid; -+ } -+ -+ @Override -+ public String getId() { -+ return uuid.toString(); -+ } -+ } -+ -+ // Spigot end - } -diff --git a/src/main/java/net/minecraft/server/ThreadPlayerLookupUUID.java b/src/main/java/net/minecraft/server/ThreadPlayerLookupUUID.java -index fe4502a..63101fb 100644 ---- a/src/main/java/net/minecraft/server/ThreadPlayerLookupUUID.java -+++ b/src/main/java/net/minecraft/server/ThreadPlayerLookupUUID.java -@@ -33,7 +33,9 @@ class ThreadPlayerLookupUUID extends Thread { - } - // Spigot End - String s = (new BigInteger(MinecraftEncryption.a(LoginListener.a(this.a), LoginListener.b(this.a).J().getPublic(), LoginListener.c(this.a)))).toString(16); -- LoginListener.a(this.a, LoginListener.b(this.a).at().hasJoinedServer(new GameProfile((String) null, LoginListener.d(this.a).getName()), s)); -+ //LoginListener.a(this.a, LoginListener.b(this.a).at().hasJoinedServer(new GameProfile((String) null, LoginListener.d(this.a).getName()), s)); -+ org.spigotmc.authlib.GameProfile profile = LoginListener.b(this.a).newSessionService.hasJoinedServer( new org.spigotmc.authlib.GameProfile( null, LoginListener.d(this.a).getName() ), s ); -+ LoginListener.a(this.a, new NewGameProfileWrapper( profile ) ); - if (LoginListener.d(this.a) != null) { - // Spigot Start - fireLoginEvents(); -@@ -95,4 +97,15 @@ class ThreadPlayerLookupUUID extends Thread { - } - // CraftBukkit end - } -+ -+ public static class NewGameProfileWrapper extends GameProfile { -+ -+ public org.spigotmc.authlib.GameProfile newProfile; -+ -+ public NewGameProfileWrapper(org.spigotmc.authlib.GameProfile newProfile) -+ { -+ super( newProfile.getId().toString().replaceAll( "-", "" ), newProfile.getName() ); -+ this.newProfile = newProfile; -+ } -+ } - } -diff --git a/src/main/java/org/spigotmc/authlib/Agent.java b/src/main/java/org/spigotmc/authlib/Agent.java -new file mode 100644 -index 0000000..873743d ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/Agent.java -@@ -0,0 +1,30 @@ -+package org.spigotmc.authlib; -+ -+public class Agent { -+ public static final Agent MINECRAFT = new Agent("Minecraft", 1); -+ public static final Agent SCROLLS = new Agent("Scrolls", 1); -+ -+ private final String name; -+ private final int version; -+ -+ public Agent(String name, int version) { -+ this.name = name; -+ this.version = version; -+ } -+ -+ public String getName() { -+ return name; -+ } -+ -+ public int getVersion() { -+ return version; -+ } -+ -+ @Override -+ public String toString() { -+ return "Agent{" + -+ "name='" + name + '\'' + -+ ", version=" + version + -+ '}'; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/AuthenticationService.java b/src/main/java/org/spigotmc/authlib/AuthenticationService.java -new file mode 100644 -index 0000000..4110e53 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/AuthenticationService.java -@@ -0,0 +1,33 @@ -+package org.spigotmc.authlib; -+ -+import org.spigotmc.authlib.minecraft.MinecraftSessionService; -+ -+public interface AuthenticationService { -+ /** -+ * Creates a relevant {@link org.spigotmc.authlib.UserAuthentication} designed for this authentication service. -+ *

-+ * Certain Authentication Services may have restrictions as to which {@link Agent}s are supported. -+ * Please consult their javadoc for more information. -+ * -+ * @param agent Game agent to authenticate for -+ * @throws java.lang.IllegalArgumentException Agent is null or not allowed for this AuthenticationService -+ * @return New user authenticator -+ */ -+ public UserAuthentication createUserAuthentication(Agent agent); -+ -+ /** -+ * Creates a relevant {@link org.spigotmc.authlib.minecraft.MinecraftSessionService} designed for this authentication service. -+ *

-+ * This is a Minecraft specific service and is not relevant to any other game agent. -+ * -+ * @return New minecraft session service -+ */ -+ public MinecraftSessionService createMinecraftSessionService(); -+ -+ /** -+ * Creates a relevant {@link org.spigotmc.authlib.GameProfileRepository} designed for this authentication service. -+ * -+ * @return New profile repository -+ */ -+ public GameProfileRepository createProfileRepository(); -+} -diff --git a/src/main/java/org/spigotmc/authlib/BaseAuthenticationService.java b/src/main/java/org/spigotmc/authlib/BaseAuthenticationService.java -new file mode 100644 -index 0000000..b3cb3bb ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/BaseAuthenticationService.java -@@ -0,0 +1,4 @@ -+package org.spigotmc.authlib; -+ -+public abstract class BaseAuthenticationService implements AuthenticationService { -+} -diff --git a/src/main/java/org/spigotmc/authlib/BaseUserAuthentication.java b/src/main/java/org/spigotmc/authlib/BaseUserAuthentication.java -new file mode 100644 -index 0000000..3bdcea2 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/BaseUserAuthentication.java -@@ -0,0 +1,268 @@ -+package org.spigotmc.authlib; -+ -+import org.spigotmc.authlib.properties.Property; -+import org.spigotmc.authlib.properties.PropertyMap; -+import org.spigotmc.authlib.util.UUIDTypeAdapter; -+import org.apache.commons.lang3.StringUtils; -+import org.apache.commons.lang3.Validate; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+ -+import java.util.ArrayList; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+ -+public abstract class BaseUserAuthentication implements UserAuthentication { -+ private static final Logger LOGGER = LogManager.getLogger(); -+ -+ protected static final String STORAGE_KEY_PROFILE_NAME = "displayName"; -+ protected static final String STORAGE_KEY_PROFILE_ID = "uuid"; -+ protected static final String STORAGE_KEY_PROFILE_PROPERTIES = "profileProperties"; -+ protected static final String STORAGE_KEY_USER_NAME = "username"; -+ protected static final String STORAGE_KEY_USER_ID = "userid"; -+ protected static final String STORAGE_KEY_USER_PROPERTIES = "userProperties"; -+ -+ private final AuthenticationService authenticationService; -+ private final PropertyMap userProperties = new PropertyMap(); -+ private String userid; -+ private String username; -+ private String password; -+ private GameProfile selectedProfile; -+ private UserType userType; -+ -+ protected BaseUserAuthentication(AuthenticationService authenticationService) { -+ Validate.notNull(authenticationService); -+ this.authenticationService = authenticationService; -+ } -+ -+ @Override -+ public boolean canLogIn() { -+ return !canPlayOnline() && StringUtils.isNotBlank(getUsername()) && StringUtils.isNotBlank(getPassword()); -+ } -+ -+ @Override -+ public void logOut() { -+ password = null; -+ userid = null; -+ setSelectedProfile(null); -+ getModifiableUserProperties().clear(); -+ setUserType(null); -+ } -+ -+ @Override -+ public boolean isLoggedIn() { -+ return getSelectedProfile() != null; -+ } -+ -+ @Override -+ public void setUsername(String username) { -+ if (isLoggedIn() && canPlayOnline()) { -+ throw new IllegalStateException("Cannot change username whilst logged in & online"); -+ } -+ -+ this.username = username; -+ } -+ -+ @Override -+ public void setPassword(String password) { -+ if (isLoggedIn() && canPlayOnline() && StringUtils.isNotBlank(password)) { -+ throw new IllegalStateException("Cannot set password whilst logged in & online"); -+ } -+ -+ this.password = password; -+ } -+ -+ protected String getUsername() { -+ return username; -+ } -+ -+ protected String getPassword() { -+ return password; -+ } -+ -+ @SuppressWarnings("unchecked") -+ @Override -+ public void loadFromStorage(Map credentials) { -+ logOut(); -+ -+ setUsername(String.valueOf(credentials.get(STORAGE_KEY_USER_NAME))); -+ -+ if (credentials.containsKey(STORAGE_KEY_USER_ID)) { -+ userid = String.valueOf(credentials.get(STORAGE_KEY_USER_ID)); -+ } else { -+ userid = username; -+ } -+ -+ if (credentials.containsKey(STORAGE_KEY_USER_PROPERTIES)) { -+ try { -+ List> list = (List>) credentials.get(STORAGE_KEY_USER_PROPERTIES); -+ -+ for (Map propertyMap : list) { -+ String name = propertyMap.get("name"); -+ String value = propertyMap.get("value"); -+ String signature = propertyMap.get("signature"); -+ -+ if (signature == null) { -+ getModifiableUserProperties().put(name, new Property(name, value)); -+ } else { -+ getModifiableUserProperties().put(name, new Property(name, value, signature)); -+ } -+ } -+ } catch (Throwable t) { -+ LOGGER.warn("Couldn't deserialize user properties", t); -+ } -+ } -+ -+ if (credentials.containsKey(STORAGE_KEY_PROFILE_NAME) && credentials.containsKey(STORAGE_KEY_PROFILE_ID)) { -+ GameProfile profile = new GameProfile(UUIDTypeAdapter.fromString(String.valueOf(credentials.get(STORAGE_KEY_PROFILE_ID))), String.valueOf(credentials.get(STORAGE_KEY_PROFILE_NAME))); -+ if (credentials.containsKey(STORAGE_KEY_PROFILE_PROPERTIES)) { -+ try { -+ List> list = (List>) credentials.get(STORAGE_KEY_PROFILE_PROPERTIES); -+ for (Map propertyMap : list) { -+ String name = propertyMap.get("name"); -+ String value = propertyMap.get("value"); -+ String signature = propertyMap.get("signature"); -+ -+ if (signature == null) { -+ profile.getProperties().put(name, new Property(name, value)); -+ } else { -+ profile.getProperties().put(name, new Property(name, value, signature)); -+ } -+ } -+ } catch (Throwable t) { -+ LOGGER.warn("Couldn't deserialize profile properties", t); -+ } -+ } -+ setSelectedProfile(profile); -+ } -+ } -+ -+ @Override -+ public Map saveForStorage() { -+ Map result = new HashMap(); -+ -+ if (getUsername() != null) { -+ result.put(STORAGE_KEY_USER_NAME, getUsername()); -+ } -+ if (getUserID() != null) { -+ result.put(STORAGE_KEY_USER_ID, getUserID()); -+ } else if (getUsername() != null) { -+ result.put(STORAGE_KEY_USER_NAME, getUsername()); -+ } -+ -+ if (!getUserProperties().isEmpty()) { -+ List> properties = new ArrayList>(); -+ for (Property userProperty : getUserProperties().values()) { -+ Map property = new HashMap(); -+ property.put("name", userProperty.getName()); -+ property.put("value", userProperty.getValue()); -+ property.put("signature", userProperty.getSignature()); -+ properties.add(property); -+ } -+ result.put(STORAGE_KEY_USER_PROPERTIES, properties); -+ } -+ -+ GameProfile selectedProfile = getSelectedProfile(); -+ if (selectedProfile != null) { -+ result.put(STORAGE_KEY_PROFILE_NAME, selectedProfile.getName()); -+ result.put(STORAGE_KEY_PROFILE_ID, selectedProfile.getId()); -+ -+ List> properties = new ArrayList>(); -+ for (Property profileProperty : selectedProfile.getProperties().values()) { -+ Map property = new HashMap(); -+ property.put("name", profileProperty.getName()); -+ property.put("value", profileProperty.getValue()); -+ property.put("signature", profileProperty.getSignature()); -+ properties.add(property); -+ } -+ -+ if (!properties.isEmpty()) { -+ result.put(STORAGE_KEY_PROFILE_PROPERTIES, properties); -+ } -+ } -+ -+ return result; -+ } -+ -+ protected void setSelectedProfile(GameProfile selectedProfile) { -+ this.selectedProfile = selectedProfile; -+ } -+ -+ @Override -+ public GameProfile getSelectedProfile() { -+ return selectedProfile; -+ } -+ -+ @Override -+ public String toString() { -+ StringBuilder result = new StringBuilder(); -+ -+ result.append(getClass().getSimpleName()); -+ result.append("{"); -+ -+ if (isLoggedIn()) { -+ result.append("Logged in as "); -+ result.append(getUsername()); -+ -+ if (getSelectedProfile() != null) { -+ result.append(" / "); -+ result.append(getSelectedProfile()); -+ result.append(" - "); -+ -+ if (canPlayOnline()) { -+ result.append("Online"); -+ } else { -+ result.append("Offline"); -+ } -+ } -+ } else { -+ result.append("Not logged in"); -+ } -+ -+ result.append("}"); -+ -+ return result.toString(); -+ } -+ -+ public AuthenticationService getAuthenticationService() { -+ return authenticationService; -+ } -+ -+ @Override -+ public String getUserID() { -+ return userid; -+ } -+ -+ @Override -+ public PropertyMap getUserProperties() { -+ if (isLoggedIn()) { -+ PropertyMap result = new PropertyMap(); -+ result.putAll(getModifiableUserProperties()); -+ return result; -+ } else { -+ return new PropertyMap(); -+ } -+ } -+ -+ protected PropertyMap getModifiableUserProperties() { -+ return userProperties; -+ } -+ -+ @Override -+ public UserType getUserType() { -+ if (isLoggedIn()) { -+ return userType == null ? UserType.LEGACY : userType; -+ } else { -+ return null; -+ } -+ } -+ -+ protected void setUserType(UserType userType) { -+ this.userType = userType; -+ } -+ -+ protected void setUserid(String userid) { -+ this.userid = userid; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/GameProfile.java b/src/main/java/org/spigotmc/authlib/GameProfile.java -new file mode 100644 -index 0000000..7e2d997 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/GameProfile.java -@@ -0,0 +1,106 @@ -+package org.spigotmc.authlib; -+ -+import org.spigotmc.authlib.properties.PropertyMap; -+import org.apache.commons.lang3.StringUtils; -+import org.apache.commons.lang3.builder.ToStringBuilder; -+ -+import java.util.UUID; -+ -+public class GameProfile { -+ private final UUID id; -+ private final String name; -+ private final PropertyMap properties = new PropertyMap(); -+ private boolean legacy; -+ -+ /** -+ * 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 java.lang.IllegalArgumentException Both ID and name are either null or empty -+ */ -+ public GameProfile(UUID id, String name) { -+ if (id == null && StringUtils.isBlank(name)) throw new IllegalArgumentException("Name and ID cannot both be blank"); -+ -+ this.id = id; -+ this.name = name; -+ } -+ -+ /** -+ * Gets the unique ID of this game profile. -+ *

-+ * This may be null for partial profile data if constructed manually. -+ * -+ * @return ID of the profile -+ */ -+ public UUID getId() { -+ return id; -+ } -+ -+ /** -+ * Gets the display name of this game profile. -+ *

-+ * This may be null for partial profile data if constructed manually. -+ * -+ * @return Name of the profile -+ */ -+ public String getName() { -+ return name; -+ } -+ -+ /** -+ * Returns any known properties about this game profile. -+ * -+ * @return Modifiable map of profile properties. -+ */ -+ public PropertyMap getProperties() { -+ return properties; -+ } -+ -+ /** -+ * Checks if this profile is complete. -+ *

-+ * A complete profile has no empty fields. Partial profiles may be constructed manually and used as input to methods. -+ * -+ * @return True if this profile is complete (as opposed to partial) -+ */ -+ public boolean isComplete() { -+ return id != null && StringUtils.isNotBlank(getName()); -+ } -+ -+ @Override -+ public boolean equals(Object o) { -+ if (this == o) return true; -+ if (o == null || getClass() != o.getClass()) return false; -+ -+ GameProfile that = (GameProfile) o; -+ -+ if (id != null ? !id.equals(that.id) : that.id != null) return false; -+ if (name != null ? !name.equals(that.name) : that.name != null) return false; -+ -+ return true; -+ } -+ -+ @Override -+ public int hashCode() { -+ int result = id != null ? id.hashCode() : 0; -+ result = 31 * result + (name != null ? name.hashCode() : 0); -+ return result; -+ } -+ -+ @Override -+ public String toString() { -+ return new ToStringBuilder(this) -+ .append("id", id) -+ .append("name", name) -+ .append("properties", properties) -+ .append("legacy", legacy) -+ .toString(); -+ } -+ -+ public boolean isLegacy() { -+ return legacy; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/GameProfileRepository.java b/src/main/java/org/spigotmc/authlib/GameProfileRepository.java -new file mode 100644 -index 0000000..83864b5 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/GameProfileRepository.java -@@ -0,0 +1,5 @@ -+package org.spigotmc.authlib; -+ -+public interface GameProfileRepository { -+ public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback); -+} -diff --git a/src/main/java/org/spigotmc/authlib/HttpAuthenticationService.java b/src/main/java/org/spigotmc/authlib/HttpAuthenticationService.java -new file mode 100644 -index 0000000..fb639d0 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/HttpAuthenticationService.java -@@ -0,0 +1,218 @@ -+package org.spigotmc.authlib; -+ -+import org.apache.commons.io.Charsets; -+import org.apache.commons.io.IOUtils; -+import org.apache.commons.lang3.Validate; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+ -+import java.io.IOException; -+import java.io.InputStream; -+import java.io.OutputStream; -+import java.io.UnsupportedEncodingException; -+import java.net.*; -+import java.util.Map; -+ -+public abstract class HttpAuthenticationService extends BaseAuthenticationService { -+ private static final Logger LOGGER = LogManager.getLogger(); -+ -+ private final Proxy proxy; -+ -+ protected HttpAuthenticationService(Proxy proxy) { -+ Validate.notNull(proxy); -+ this.proxy = proxy; -+ } -+ -+ /** -+ * Gets the proxy to be used with every HTTP(S) request. -+ * -+ * @return Proxy to be used. -+ */ -+ public Proxy getProxy() { -+ return proxy; -+ } -+ -+ protected HttpURLConnection createUrlConnection(URL url) throws IOException { -+ Validate.notNull(url); -+ LOGGER.debug("Opening connection to " + url); -+ HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy); -+ connection.setConnectTimeout(15000); -+ connection.setReadTimeout(15000); -+ connection.setUseCaches(false); -+ return connection; -+ } -+ -+ /** -+ * Performs a POST request to the specified URL and returns the result. -+ *

-+ * The POST data will be encoded in UTF-8 as the specified contentType. The response will be parsed as UTF-8. -+ * If the server returns an error but still provides a body, the body will be returned as normal. -+ * If the server returns an error without any body, a relevant {@link java.io.IOException} will be thrown. -+ * -+ * @param url URL to submit the POST request to -+ * @param post POST data in the correct format to be submitted -+ * @param contentType Content type of the POST data -+ * @return Raw text response from the server -+ * @throws IOException The request was not successful -+ */ -+ public String performPostRequest(URL url, String post, String contentType) throws IOException { -+ Validate.notNull(url); -+ Validate.notNull(post); -+ Validate.notNull(contentType); -+ HttpURLConnection connection = createUrlConnection(url); -+ byte[] postAsBytes = post.getBytes(Charsets.UTF_8); -+ -+ connection.setRequestProperty("Content-Type", contentType + "; charset=utf-8"); -+ connection.setRequestProperty("Content-Length", "" + postAsBytes.length); -+ connection.setDoOutput(true); -+ -+ LOGGER.debug("Writing POST data to " + url + ": " + post); -+ -+ OutputStream outputStream = null; -+ try { -+ outputStream = connection.getOutputStream(); -+ IOUtils.write(postAsBytes, outputStream); -+ } finally { -+ IOUtils.closeQuietly(outputStream); -+ } -+ -+ LOGGER.debug("Reading data from " + url); -+ -+ InputStream inputStream = null; -+ try { -+ inputStream = connection.getInputStream(); -+ String result = IOUtils.toString(inputStream, Charsets.UTF_8); -+ LOGGER.debug("Successful read, server response was " + connection.getResponseCode()); -+ LOGGER.debug("Response: " + result); -+ return result; -+ } catch (IOException e) { -+ IOUtils.closeQuietly(inputStream); -+ inputStream = connection.getErrorStream(); -+ -+ if (inputStream != null) { -+ LOGGER.debug("Reading error page from " + url); -+ String result = IOUtils.toString(inputStream, Charsets.UTF_8); -+ LOGGER.debug("Successful read, server response was " + connection.getResponseCode()); -+ LOGGER.debug("Response: " + result); -+ return result; -+ } else { -+ LOGGER.debug("Request failed", e); -+ throw e; -+ } -+ } finally { -+ IOUtils.closeQuietly(inputStream); -+ } -+ } -+ -+ /** -+ * Performs a GET request to the specified URL and returns the result. -+ *

-+ * The response will be parsed as UTF-8. -+ * If the server returns an error but still provides a body, the body will be returned as normal. -+ * If the server returns an error without any body, a relevant {@link java.io.IOException} will be thrown. -+ * -+ * @param url URL to submit the GET request to -+ * @return Raw text response from the server -+ * @throws IOException The request was not successful -+ */ -+ public String performGetRequest(URL url) throws IOException { -+ Validate.notNull(url); -+ HttpURLConnection connection = createUrlConnection(url); -+ -+ LOGGER.debug("Reading data from " + url); -+ -+ InputStream inputStream = null; -+ try { -+ inputStream = connection.getInputStream(); -+ String result = IOUtils.toString(inputStream, Charsets.UTF_8); -+ LOGGER.debug("Successful read, server response was " + connection.getResponseCode()); -+ LOGGER.debug("Response: " + result); -+ return result; -+ } catch (IOException e) { -+ IOUtils.closeQuietly(inputStream); -+ inputStream = connection.getErrorStream(); -+ -+ if (inputStream != null) { -+ LOGGER.debug("Reading error page from " + url); -+ String result = IOUtils.toString(inputStream, Charsets.UTF_8); -+ LOGGER.debug("Successful read, server response was " + connection.getResponseCode()); -+ LOGGER.debug("Response: " + result); -+ return result; -+ } else { -+ LOGGER.debug("Request failed", e); -+ throw e; -+ } -+ } finally { -+ IOUtils.closeQuietly(inputStream); -+ } -+ } -+ -+ /** -+ * Creates a {@link URL} with the specified string, throwing an {@link java.lang.Error} if the URL was malformed. -+ *

-+ * This is just a wrapper to allow URLs to be created in constants, where you know the URL is valid. -+ * -+ * @param url URL to construct -+ * @return URL constructed -+ */ -+ public static URL constantURL(String url) { -+ try { -+ return new URL(url); -+ } catch (MalformedURLException ex) { -+ throw new Error("Couldn't create constant for " + url, ex); -+ } -+ } -+ -+ /** -+ * Turns the specified Map into an encoded & escaped query -+ * -+ * @param query Map to convert into a text based query -+ * @return Resulting query. -+ */ -+ public static String buildQuery(Map query) { -+ if (query == null) return ""; -+ StringBuilder builder = new StringBuilder(); -+ -+ for (Map.Entry entry : query.entrySet()) { -+ if (builder.length() > 0) { -+ builder.append('&'); -+ } -+ -+ try { -+ builder.append(URLEncoder.encode(entry.getKey(), "UTF-8")); -+ } catch (UnsupportedEncodingException e) { -+ LOGGER.error("Unexpected exception building query", e); -+ } -+ -+ if (entry.getValue() != null) { -+ builder.append('='); -+ try { -+ builder.append(URLEncoder.encode(entry.getValue().toString(), "UTF-8")); -+ } catch (UnsupportedEncodingException e) { -+ LOGGER.error("Unexpected exception building query", e); -+ } -+ } -+ } -+ -+ return builder.toString(); -+ } -+ -+ /** -+ * Concatenates the given {@link java.net.URL} and query. -+ * -+ * @param url URL to base off -+ * @param query Query to append to URL -+ * @return URL constructed -+ */ -+ public static URL concatenateURL(URL url, String query) { -+ try { -+ if (url.getQuery() != null && url.getQuery().length() > 0) { -+ return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile() + "&" + query); -+ } else { -+ return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile() + "?" + query); -+ } -+ } catch (MalformedURLException ex) { -+ throw new IllegalArgumentException("Could not concatenate given URL with GET arguments!", ex); -+ } -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/HttpUserAuthentication.java b/src/main/java/org/spigotmc/authlib/HttpUserAuthentication.java -new file mode 100644 -index 0000000..1020391 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/HttpUserAuthentication.java -@@ -0,0 +1,12 @@ -+package org.spigotmc.authlib; -+ -+public abstract class HttpUserAuthentication extends BaseUserAuthentication { -+ protected HttpUserAuthentication(HttpAuthenticationService authenticationService) { -+ super(authenticationService); -+ } -+ -+ @Override -+ public HttpAuthenticationService getAuthenticationService() { -+ return (HttpAuthenticationService) super.getAuthenticationService(); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/ProfileLookupCallback.java b/src/main/java/org/spigotmc/authlib/ProfileLookupCallback.java -new file mode 100644 -index 0000000..5ec92d1 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/ProfileLookupCallback.java -@@ -0,0 +1,7 @@ -+package org.spigotmc.authlib; -+ -+public interface ProfileLookupCallback { -+ public void onProfileLookupSucceeded(GameProfile profile); -+ -+ public void onProfileLookupFailed(GameProfile profile, Exception exception); -+} -diff --git a/src/main/java/org/spigotmc/authlib/UserAuthentication.java b/src/main/java/org/spigotmc/authlib/UserAuthentication.java -new file mode 100644 -index 0000000..0f65242 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/UserAuthentication.java -@@ -0,0 +1,170 @@ -+package org.spigotmc.authlib; -+ -+import com.google.common.collect.Multimap; -+import org.spigotmc.authlib.exceptions.AuthenticationException; -+import org.spigotmc.authlib.properties.Property; -+import org.spigotmc.authlib.properties.PropertyMap; -+ -+import java.util.Map; -+ -+public interface UserAuthentication { -+ /** -+ * Checks if enough details are provided to attempt authentication. -+ *

-+ * The exact details required may depend on the service, but generally Username & Password should suffice. -+ * Attempting to call {@link #logIn()} when this method returns false will guarantee a failure. You may use -+ * this method to check if you can attempt a log in without altering the current state of the authentication. -+ * -+ * @return True if authentication may be attempted in this state -+ */ -+ boolean canLogIn(); -+ -+ /** -+ * Attempts authentication with the currently set details. -+ *

-+ * If {@link #canLogIn()} returned false, this method is guaranteed to fail. However, an appropriate exception -+ * will be raised informing you as to why it failed. The exact required credentials to authenticate varies on -+ * the service being used, but generally {@link #setUsername(String) username} and {@link #setPassword(String) password} are a safe -+ * bet to log a user in. -+ *

-+ * If the user is {@link #isLoggedIn() already logged in} this method will not fail early and will continue -+ * to reauthenticate the user. If the user is attempting to log in with a legacy username ("Steve") -+ * and that username is valid but migrated to a Mojang account ("steve@minecraft.net"), a {@link org.spigotmc.authlib.exceptions.UserMigratedException} -+ * will be thrown. -+ * -+ * @throws org.spigotmc.authlib.exceptions.AuthenticationUnavailableException Thrown when the servers return a malformed response, or are otherwise unavailable -+ * @throws org.spigotmc.authlib.exceptions.InvalidCredentialsException Thrown when the specified credentials are invalid -+ * @throws org.spigotmc.authlib.exceptions.UserMigratedException Thrown when attempting to authenticate with a {@link #setUsername(String) username} that has been migrated to an email address -+ * @throws org.spigotmc.authlib.exceptions.AuthenticationException Generic exception indicating that we could not authenticate the user -+ */ -+ void logIn() throws AuthenticationException; -+ -+ /** -+ * Logs this user out, clearing any local credentials. -+ */ -+ void logOut(); -+ -+ /** -+ * Checks if the user is currently logged in. -+ * -+ * @return True if the user is logged in -+ */ -+ boolean isLoggedIn(); -+ -+ /** -+ * Checks if the user {@link #isLoggedIn() is logged in}, has a valid {@link #getSelectedProfile() game profile} and has validated -+ * their session online. -+ * -+ * @return True if the user is allowed to play online -+ */ -+ boolean canPlayOnline(); -+ -+ /** -+ * Gets a list of valid {@link GameProfile GameProfiles} for this user. -+ *

-+ * Calling this method whilst the user is not {@link #isLoggedIn() logged in} will always return null. -+ * If the result of this method is an empty array or null and the user is logged in, the user is considered to not have purchased the game but -+ * may be allowed to play demo mode. -+ * -+ * @return An array of available game profiles, or null. -+ */ -+ GameProfile[] getAvailableProfiles(); -+ -+ /** -+ * Gets the currently selected {@link GameProfile} for this user. -+ *

-+ * Calling this method whilst the user is not {@link #isLoggedIn() logged in} or has no {@link #getAvailableProfiles() available profiles} will always return null. -+ * -+ * @return Users currently selected Game Profile -+ */ -+ GameProfile getSelectedProfile(); -+ -+ /** -+ * Attempts to select the specified {@link GameProfile}. -+ *

-+ * The user must be {@link #isLoggedIn() logged in}, have no {@link #getSelectedProfile() currently selected game profile} and the specified profile must -+ * be retrieved from {@link #getAvailableProfiles()}. -+ * -+ * @param profile The game profile to select. -+ * @throws java.lang.IllegalArgumentException Profile is null or did not come from {@link #getAvailableProfiles()} -+ * @throws org.spigotmc.authlib.exceptions.AuthenticationException User is not currently {@link #isLoggedIn() logged in}, -+ * or already has a {@link #getSelectedProfile() selected profile}, -+ * or the authentication service did not allow the profile change -+ * @throws org.spigotmc.authlib.exceptions.AuthenticationUnavailableException Thrown when the servers return a malformed response, or are otherwise unavailable -+ */ -+ void selectGameProfile(GameProfile profile) throws AuthenticationException; -+ -+ /** -+ * Tries to load any stored details that may be used for authentication from a given Map. -+ *

-+ * This may be used to load an approximation of the current state from a past {@link org.spigotmc.authlib.UserAuthentication} with {@link #saveForStorage()}. -+ * -+ * @param credentials Map to load credentials or state from -+ */ -+ void loadFromStorage(Map credentials); -+ -+ /** -+ * Saves any known credentials to a Map and returns the result. -+ *

-+ * This may be used to save an approximation of the current state for a future {@link org.spigotmc.authlib.UserAuthentication} with {@link #loadFromStorage(java.util.Map)}. -+ * -+ * @return Map containing any saved credentials and state for storage -+ */ -+ Map saveForStorage(); -+ -+ /** -+ * Sets the username to authenticate with for the next {@link #logIn()} call. -+ *

-+ * You may not call this method whilst the user is {@link #isLoggedIn() logged in}. -+ * -+ * @param username Username to authenticate with -+ * @throws java.lang.IllegalStateException User is already logged in -+ */ -+ void setUsername(String username); -+ -+ /** -+ * Sets the password to authenticate with for the next {@link #logIn()} call. -+ *

-+ * You may not call this method with a non-null and non-empty string whilst the user is {@link #isLoggedIn() logged in}. -+ * -+ * @param password Password to authenticate with -+ * @throws java.lang.IllegalStateException User is already logged in and the password is non-null & non-empty -+ */ -+ void setPassword(String password); -+ -+ /** -+ * Gets an authenticated token for use in authenticated API calls. -+ * -+ * @return Authenticated token for the current user, or null if not logged in. -+ */ -+ public String getAuthenticatedToken(); -+ -+ /** -+ * Gets the unique ID of the currently logged in user. -+ *

-+ * This method will return null if the user is not logged in. -+ * -+ * @return Unique ID of the currently logged in user, or null if not logged in -+ */ -+ public String getUserID(); -+ -+ /** -+ * Gets a Multimap of properties bound to the currently logged in user. -+ *

-+ * This method will return an empty Multimap if the user is not logged in. -+ *

-+ * The returned Multimap will ignore any changes. -+ * -+ * @return Multimap of user properties. -+ */ -+ public PropertyMap getUserProperties(); -+ -+ /** -+ * Gets the type of the currently logged in user. -+ *

-+ * This method will return null if the user is not logged in. -+ * -+ * @return Type of current logged in user, or null. -+ */ -+ public UserType getUserType(); -+} -diff --git a/src/main/java/org/spigotmc/authlib/UserType.java b/src/main/java/org/spigotmc/authlib/UserType.java -new file mode 100644 -index 0000000..6ca7eff ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/UserType.java -@@ -0,0 +1,30 @@ -+package org.spigotmc.authlib; -+ -+import java.util.HashMap; -+import java.util.Map; -+ -+public enum UserType { -+ LEGACY("legacy"), -+ MOJANG("mojang"); -+ -+ private static final Map BY_NAME = new HashMap(); -+ private final String name; -+ -+ private UserType(String name) { -+ this.name = name; -+ } -+ -+ public static UserType byName(String name) { -+ return BY_NAME.get(name.toLowerCase()); -+ } -+ -+ public String getName() { -+ return name; -+ } -+ -+ static { -+ for (UserType type : UserType.values()) { -+ BY_NAME.put(type.name, type); -+ } -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/exceptions/AuthenticationException.java b/src/main/java/org/spigotmc/authlib/exceptions/AuthenticationException.java -new file mode 100644 -index 0000000..5366bbf ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/exceptions/AuthenticationException.java -@@ -0,0 +1,18 @@ -+package org.spigotmc.authlib.exceptions; -+ -+public class AuthenticationException extends Exception { -+ public AuthenticationException() { -+ } -+ -+ public AuthenticationException(String message) { -+ super(message); -+ } -+ -+ public AuthenticationException(String message, Throwable cause) { -+ super(message, cause); -+ } -+ -+ public AuthenticationException(Throwable cause) { -+ super(cause); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/exceptions/AuthenticationUnavailableException.java b/src/main/java/org/spigotmc/authlib/exceptions/AuthenticationUnavailableException.java -new file mode 100644 -index 0000000..f953f2c ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/exceptions/AuthenticationUnavailableException.java -@@ -0,0 +1,21 @@ -+package org.spigotmc.authlib.exceptions; -+ -+import org.spigotmc.authlib.exceptions.AuthenticationException; -+ -+public class AuthenticationUnavailableException extends AuthenticationException -+{ -+ public AuthenticationUnavailableException() { -+ } -+ -+ public AuthenticationUnavailableException(String message) { -+ super(message); -+ } -+ -+ public AuthenticationUnavailableException(String message, Throwable cause) { -+ super(message, cause); -+ } -+ -+ public AuthenticationUnavailableException(Throwable cause) { -+ super(cause); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/exceptions/InvalidCredentialsException.java b/src/main/java/org/spigotmc/authlib/exceptions/InvalidCredentialsException.java -new file mode 100644 -index 0000000..edf8074 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/exceptions/InvalidCredentialsException.java -@@ -0,0 +1,21 @@ -+package org.spigotmc.authlib.exceptions; -+ -+import org.spigotmc.authlib.exceptions.AuthenticationException; -+ -+public class InvalidCredentialsException extends AuthenticationException -+{ -+ public InvalidCredentialsException() { -+ } -+ -+ public InvalidCredentialsException(String message) { -+ super(message); -+ } -+ -+ public InvalidCredentialsException(String message, Throwable cause) { -+ super(message, cause); -+ } -+ -+ public InvalidCredentialsException(Throwable cause) { -+ super(cause); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/exceptions/UserMigratedException.java b/src/main/java/org/spigotmc/authlib/exceptions/UserMigratedException.java -new file mode 100644 -index 0000000..1df195f ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/exceptions/UserMigratedException.java -@@ -0,0 +1,18 @@ -+package org.spigotmc.authlib.exceptions; -+ -+public class UserMigratedException extends InvalidCredentialsException { -+ public UserMigratedException() { -+ } -+ -+ public UserMigratedException(String message) { -+ super(message); -+ } -+ -+ public UserMigratedException(String message, Throwable cause) { -+ super(message, cause); -+ } -+ -+ public UserMigratedException(Throwable cause) { -+ super(cause); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/legacy/LegacyAuthenticationService.java b/src/main/java/org/spigotmc/authlib/legacy/LegacyAuthenticationService.java -new file mode 100644 -index 0000000..1be0c80 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/legacy/LegacyAuthenticationService.java -@@ -0,0 +1,49 @@ -+package org.spigotmc.authlib.legacy; -+ -+import org.spigotmc.authlib.Agent; -+import org.spigotmc.authlib.GameProfileRepository; -+import org.spigotmc.authlib.HttpAuthenticationService; -+import org.apache.commons.lang3.Validate; -+import org.spigotmc.authlib.legacy.LegacyUserAuthentication; -+ -+import java.net.Proxy; -+ -+public class LegacyAuthenticationService extends HttpAuthenticationService { -+ /** -+ * Constructs a new AuthenticationService using the legacy service. -+ *

-+ * The legacy authentication service only supports the Minecraft {@link Agent}. -+ * -+ * @param proxy Proxy to route all HTTP(s) requests through. -+ * @throws java.lang.IllegalArgumentException Proxy is null -+ */ -+ protected LegacyAuthenticationService(Proxy proxy) { -+ super(proxy); -+ } -+ -+ /** -+ * Creates a relevant {@link org.spigotmc.authlib.UserAuthentication} using the legacy servers. -+ *

-+ * The legacy authentication service only supports the Minecraft {@link Agent}. -+ * -+ * @param agent Game agent to authenticate for -+ * @throws java.lang.IllegalArgumentException Agent is null or not allowed for this AuthenticationService -+ * @return New user authenticator -+ */ -+ @Override -+ public LegacyUserAuthentication createUserAuthentication(Agent agent) { -+ Validate.notNull(agent); -+ if (agent != Agent.MINECRAFT) throw new IllegalArgumentException("Legacy authentication cannot handle anything but Minecraft"); -+ return new LegacyUserAuthentication(this); -+ } -+ -+ @Override -+ public LegacyMinecraftSessionService createMinecraftSessionService() { -+ return new LegacyMinecraftSessionService(this); -+ } -+ -+ @Override -+ public GameProfileRepository createProfileRepository() { -+ throw new UnsupportedOperationException("Legacy authentication service has no profile repository"); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/legacy/LegacyMinecraftSessionService.java b/src/main/java/org/spigotmc/authlib/legacy/LegacyMinecraftSessionService.java -new file mode 100644 -index 0000000..6ed1afe ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/legacy/LegacyMinecraftSessionService.java -@@ -0,0 +1,79 @@ -+package org.spigotmc.authlib.legacy; -+ -+import org.spigotmc.authlib.GameProfile; -+import org.spigotmc.authlib.exceptions.AuthenticationException; -+import org.spigotmc.authlib.exceptions.AuthenticationUnavailableException; -+import org.spigotmc.authlib.minecraft.HttpMinecraftSessionService; -+import org.spigotmc.authlib.minecraft.MinecraftProfileTexture; -+import org.spigotmc.authlib.legacy.LegacyAuthenticationService; -+ -+import java.io.IOException; -+import java.net.URL; -+import java.util.HashMap; -+import java.util.Map; -+ -+import static org.spigotmc.authlib.HttpAuthenticationService.*; -+ -+public class LegacyMinecraftSessionService extends HttpMinecraftSessionService { -+ private static final String BASE_URL = "http://session.minecraft.net/game/"; -+ private static final URL JOIN_URL = constantURL(BASE_URL + "joinserver.jsp"); -+ private static final URL CHECK_URL = constantURL(BASE_URL + "checkserver.jsp"); -+ -+ protected LegacyMinecraftSessionService(LegacyAuthenticationService authenticationService) { -+ super(authenticationService); -+ } -+ -+ @Override -+ public void joinServer(GameProfile profile, String authenticationToken, String serverId) throws AuthenticationException { -+ Map arguments = new HashMap(); -+ -+ arguments.put("user", profile.getName()); -+ arguments.put("sessionId", authenticationToken); -+ arguments.put("serverId", serverId); -+ -+ URL url = concatenateURL(JOIN_URL, buildQuery(arguments)); -+ -+ try { -+ String response = getAuthenticationService().performGetRequest(url); -+ -+ if (!response.equals("OK")) { -+ throw new AuthenticationException(response); -+ } -+ } catch (IOException e) { -+ throw new AuthenticationUnavailableException(e); -+ } -+ } -+ -+ @Override -+ public GameProfile hasJoinedServer(GameProfile user, String serverId) throws AuthenticationUnavailableException { -+ Map arguments = new HashMap(); -+ -+ arguments.put("user", user.getName()); -+ arguments.put("serverId", serverId); -+ -+ URL url = concatenateURL(CHECK_URL, buildQuery(arguments)); -+ -+ try { -+ String response = getAuthenticationService().performGetRequest(url); -+ -+ return response.equals("YES") ? user : null; -+ } catch (IOException e) { -+ throw new AuthenticationUnavailableException(e); -+ } -+ } -+ -+ @Override -+ public Map getTextures(GameProfile profile, boolean requireSecure) { -+ return new HashMap(); -+ } -+ -+ @Override -+ public GameProfile fillProfileProperties(GameProfile profile) { -+ return profile; -+ } -+ -+ @Override -+ public LegacyAuthenticationService getAuthenticationService() { -+ return (LegacyAuthenticationService) super.getAuthenticationService(); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/legacy/LegacyUserAuthentication.java b/src/main/java/org/spigotmc/authlib/legacy/LegacyUserAuthentication.java -new file mode 100644 -index 0000000..0dc670a ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/legacy/LegacyUserAuthentication.java -@@ -0,0 +1,117 @@ -+package org.spigotmc.authlib.legacy; -+ -+import org.spigotmc.authlib.GameProfile; -+import org.spigotmc.authlib.HttpAuthenticationService; -+import org.spigotmc.authlib.HttpUserAuthentication; -+import org.spigotmc.authlib.UserType; -+import org.spigotmc.authlib.exceptions.AuthenticationException; -+import org.spigotmc.authlib.exceptions.InvalidCredentialsException; -+import org.spigotmc.authlib.util.UUIDTypeAdapter; -+import org.apache.commons.lang3.StringUtils; -+ -+import java.io.IOException; -+import java.net.URL; -+import java.util.HashMap; -+import java.util.Map; -+ -+public class LegacyUserAuthentication extends HttpUserAuthentication { -+ private static final URL AUTHENTICATION_URL = HttpAuthenticationService.constantURL("https://login.minecraft.net"); -+ private static final int AUTHENTICATION_VERSION = 14; -+ -+ // 0 1 2 3 4 -+ // deprecated,deprecated,profile name,session id,profile id -+ private static final int RESPONSE_PART_PROFILE_NAME = 2; -+ private static final int RESPONSE_PART_SESSION_TOKEN = 3; -+ private static final int RESPONSE_PART_PROFILE_ID = 4; -+ -+ private String sessionToken; -+ -+ protected LegacyUserAuthentication(LegacyAuthenticationService authenticationService) { -+ super(authenticationService); -+ } -+ -+ @Override -+ public void logIn() throws AuthenticationException { -+ if (StringUtils.isBlank(getUsername())) { -+ throw new InvalidCredentialsException("Invalid username"); -+ } -+ if (StringUtils.isBlank(getPassword())) { -+ throw new InvalidCredentialsException("Invalid password"); -+ } -+ -+ Map args = new HashMap(); -+ args.put("user", getUsername()); -+ args.put("password", getPassword()); -+ args.put("version", AUTHENTICATION_VERSION); -+ String response; -+ -+ try { -+ response = getAuthenticationService().performPostRequest(AUTHENTICATION_URL, HttpAuthenticationService.buildQuery(args), "application/x-www-form-urlencoded").trim(); -+ } catch (IOException e) { -+ throw new AuthenticationException("Authentication server is not responding", e); -+ } -+ -+ String[] split = response.split(":"); -+ -+ if (split.length == 5) { -+ String profileId = split[RESPONSE_PART_PROFILE_ID]; -+ String profileName = split[RESPONSE_PART_PROFILE_NAME]; -+ String sessionToken = split[RESPONSE_PART_SESSION_TOKEN]; -+ -+ if (StringUtils.isBlank(profileId) || StringUtils.isBlank(profileName) || StringUtils.isBlank(sessionToken)) { -+ throw new AuthenticationException("Unknown response from authentication server: " + response); -+ } -+ -+ setSelectedProfile(new GameProfile(UUIDTypeAdapter.fromString(profileId), profileName)); -+ this.sessionToken = sessionToken; -+ setUserType(UserType.LEGACY); -+ } else { -+ throw new InvalidCredentialsException(response); -+ } -+ } -+ -+ @Override -+ public void logOut() { -+ super.logOut(); -+ sessionToken = null; -+ } -+ -+ @Override -+ public boolean canPlayOnline() { -+ return isLoggedIn() && getSelectedProfile() != null && getAuthenticatedToken() != null; -+ } -+ -+ @Override -+ public GameProfile[] getAvailableProfiles() { -+ if (getSelectedProfile() != null) { -+ return new GameProfile[] {getSelectedProfile()}; -+ } else { -+ return new GameProfile[0]; -+ } -+ } -+ -+ /** -+ * This method is not supported in the Legacy authentication service. -+ *

-+ * Attempts to call this method will fail. -+ */ -+ @Override -+ public void selectGameProfile(GameProfile profile) throws AuthenticationException { -+ throw new UnsupportedOperationException("Game profiles cannot be changed in the legacy authentication service"); -+ } -+ -+ @Override -+ public String getAuthenticatedToken() { -+ return sessionToken; -+ } -+ -+ @Override -+ public String getUserID() { -+ return getUsername(); -+ } -+ -+ @Override -+ public LegacyAuthenticationService getAuthenticationService() { -+ return (LegacyAuthenticationService) super.getAuthenticationService(); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/minecraft/BaseMinecraftSessionService.java b/src/main/java/org/spigotmc/authlib/minecraft/BaseMinecraftSessionService.java -new file mode 100644 -index 0000000..000ce45 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/minecraft/BaseMinecraftSessionService.java -@@ -0,0 +1,17 @@ -+package org.spigotmc.authlib.minecraft; -+ -+import org.spigotmc.authlib.AuthenticationService; -+import org.spigotmc.authlib.minecraft.MinecraftSessionService; -+ -+public abstract class BaseMinecraftSessionService implements MinecraftSessionService -+{ -+ private final AuthenticationService authenticationService; -+ -+ protected BaseMinecraftSessionService(AuthenticationService authenticationService) { -+ this.authenticationService = authenticationService; -+ } -+ -+ public AuthenticationService getAuthenticationService() { -+ return authenticationService; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/minecraft/HttpMinecraftSessionService.java b/src/main/java/org/spigotmc/authlib/minecraft/HttpMinecraftSessionService.java -new file mode 100644 -index 0000000..a3dc46b ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/minecraft/HttpMinecraftSessionService.java -@@ -0,0 +1,16 @@ -+package org.spigotmc.authlib.minecraft; -+ -+import org.spigotmc.authlib.HttpAuthenticationService; -+import org.spigotmc.authlib.minecraft.BaseMinecraftSessionService; -+ -+public abstract class HttpMinecraftSessionService extends BaseMinecraftSessionService -+{ -+ protected HttpMinecraftSessionService(HttpAuthenticationService authenticationService) { -+ super(authenticationService); -+ } -+ -+ @Override -+ public HttpAuthenticationService getAuthenticationService() { -+ return (HttpAuthenticationService) super.getAuthenticationService(); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/minecraft/MinecraftProfileTexture.java b/src/main/java/org/spigotmc/authlib/minecraft/MinecraftProfileTexture.java -new file mode 100644 -index 0000000..110f826 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/minecraft/MinecraftProfileTexture.java -@@ -0,0 +1,34 @@ -+package org.spigotmc.authlib.minecraft; -+ -+import org.apache.commons.io.FilenameUtils; -+import org.apache.commons.lang3.builder.ToStringBuilder; -+ -+public class MinecraftProfileTexture { -+ public enum Type { -+ SKIN, -+ CAPE, -+ ; -+ } -+ -+ private final String url; -+ -+ public MinecraftProfileTexture(String url) { -+ this.url = url; -+ } -+ -+ public String getUrl() { -+ return url; -+ } -+ -+ public String getHash() { -+ return FilenameUtils.getBaseName(url); -+ } -+ -+ @Override -+ public String toString() { -+ return new ToStringBuilder(this) -+ .append("url", url) -+ .append("hash", getHash()) -+ .toString(); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/minecraft/MinecraftSessionService.java b/src/main/java/org/spigotmc/authlib/minecraft/MinecraftSessionService.java -new file mode 100644 -index 0000000..0166693 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/minecraft/MinecraftSessionService.java -@@ -0,0 +1,60 @@ -+package org.spigotmc.authlib.minecraft; -+ -+import org.spigotmc.authlib.GameProfile; -+import org.spigotmc.authlib.exceptions.AuthenticationException; -+import org.spigotmc.authlib.exceptions.AuthenticationUnavailableException; -+import org.spigotmc.authlib.minecraft.MinecraftProfileTexture; -+ -+import java.util.Map; -+ -+public interface MinecraftSessionService { -+ /** -+ * Attempts to join the specified Minecraft server. -+ *

-+ * The {@link org.spigotmc.authlib.GameProfile} used to join with may be partial, but the exact requirements will vary on -+ * authentication service. If this method returns without throwing an exception, the join was successful and a subsequent call to -+ * {@link #hasJoinedServer(org.spigotmc.authlib.GameProfile, String)} will return true. -+ * -+ * @param profile Partial {@link org.spigotmc.authlib.GameProfile} to join as -+ * @param authenticationToken The {@link org.spigotmc.authlib.UserAuthentication#getAuthenticatedToken() authenticated token} of the user -+ * @param serverId The random ID of the server to join -+ * @throws org.spigotmc.authlib.exceptions.AuthenticationUnavailableException Thrown when the servers return a malformed response, or are otherwise unavailable -+ * @throws org.spigotmc.authlib.exceptions.InvalidCredentialsException Thrown when the specified authenticationToken is invalid -+ * @throws org.spigotmc.authlib.exceptions.AuthenticationException Generic exception indicating that we could not authenticate the user -+ */ -+ public void joinServer(GameProfile profile, String authenticationToken, String serverId) throws AuthenticationException; -+ -+ /** -+ * Checks if the specified user has joined a Minecraft server. -+ *

-+ * The {@link org.spigotmc.authlib.GameProfile} used to join with may be partial, but the exact requirements will vary on -+ * authentication service. -+ * -+ * @param user Partial {@link org.spigotmc.authlib.GameProfile} to check for -+ * @param serverId The random ID of the server to check for -+ * @throws org.spigotmc.authlib.exceptions.AuthenticationUnavailableException Thrown when the servers return a malformed response, or are otherwise unavailable -+ * @return Full game profile if the user had joined, otherwise null -+ */ -+ public GameProfile hasJoinedServer(GameProfile user, String serverId) throws AuthenticationUnavailableException; -+ -+ /** -+ * Gets a map of all known textures from a {@link org.spigotmc.authlib.GameProfile}. -+ *

-+ * If a profile contains invalid textures, they will not be returned. If a profile contains no textures, an empty map will be returned. -+ * -+ * @param profile Game profile to return textures from. -+ * @param requireSecure If true, requires the payload to be recent and securely fetched. -+ * @return Map of texture types to textures. -+ */ -+ public Map getTextures(GameProfile profile, boolean requireSecure); -+ -+ /** -+ * Fills a profile with all known properties from the session service. -+ *

-+ * The profile must have an ID. If no information is found, nothing will be done. -+ * -+ * @param profile Game profile to fill with properties. -+ * @return Filled profile for the previous user. -+ */ -+ public GameProfile fillProfileProperties(GameProfile profile); -+} -diff --git a/src/main/java/org/spigotmc/authlib/properties/Property.java b/src/main/java/org/spigotmc/authlib/properties/Property.java -new file mode 100644 -index 0000000..6b8609b ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/properties/Property.java -@@ -0,0 +1,53 @@ -+package org.spigotmc.authlib.properties; -+ -+import org.apache.commons.codec.binary.Base64; -+ -+import java.security.*; -+ -+public class Property { -+ private final String name; -+ private final String value; -+ private final String signature; -+ -+ public Property(String value, String name) { -+ this(value, name, null); -+ } -+ -+ public Property(String name, String value, String signature) { -+ this.name = name; -+ this.value = value; -+ this.signature = signature; -+ } -+ -+ public String getName() { -+ return name; -+ } -+ -+ public String getValue() { -+ return value; -+ } -+ -+ public String getSignature() { -+ return signature; -+ } -+ -+ public boolean hasSignature() { -+ return signature != null; -+ } -+ -+ public boolean isSignatureValid(PublicKey publicKey) { -+ try { -+ Signature signature = Signature.getInstance("SHA1withRSA"); -+ signature.initVerify(publicKey); -+ signature.update(value.getBytes()); -+ return signature.verify(Base64.decodeBase64(this.signature)); -+ } catch (NoSuchAlgorithmException e) { -+ e.printStackTrace(); -+ } catch (InvalidKeyException e) { -+ e.printStackTrace(); -+ } catch (SignatureException e) { -+ e.printStackTrace(); -+ } -+ return false; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/properties/PropertyMap.java b/src/main/java/org/spigotmc/authlib/properties/PropertyMap.java -new file mode 100644 -index 0000000..ef27ad0 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/properties/PropertyMap.java -@@ -0,0 +1,73 @@ -+package org.spigotmc.authlib.properties; -+ -+import com.google.common.collect.ForwardingMultimap; -+import com.google.common.collect.LinkedHashMultimap; -+import com.google.common.collect.Multimap; -+import com.google.gson.*; -+ -+import java.lang.reflect.Type; -+import java.util.Map; -+ -+public class PropertyMap extends ForwardingMultimap { -+ private final Multimap properties = LinkedHashMultimap.create(); -+ -+ @Override -+ protected Multimap delegate() { -+ return properties; -+ } -+ -+ public static class Serializer implements JsonSerializer, JsonDeserializer { -+ @Override -+ public PropertyMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { -+ final PropertyMap result = new PropertyMap(); -+ -+ if (json instanceof JsonObject) { -+ JsonObject object = (JsonObject) json; -+ -+ for (Map.Entry entry : object.entrySet()) { -+ if (entry.getValue() instanceof JsonArray) { -+ for (JsonElement element : ((JsonArray) entry.getValue())) { -+ result.put(entry.getKey(), new Property(entry.getKey(), element.getAsString())); -+ } -+ } -+ } -+ } else if (json instanceof JsonArray) { -+ for (JsonElement element : (JsonArray) json) { -+ if (element instanceof JsonObject) { -+ JsonObject object = (JsonObject) element; -+ String name = object.getAsJsonPrimitive("name").getAsString(); -+ String value = object.getAsJsonPrimitive("value").getAsString(); -+ -+ if (object.has("signature")) { -+ result.put(name, new Property(name, value, object.getAsJsonPrimitive("signature").getAsString())); -+ } else { -+ result.put(name, new Property(name, value)); -+ } -+ } -+ } -+ } -+ -+ return result; -+ } -+ -+ @Override -+ public JsonElement serialize(PropertyMap src, Type typeOfSrc, JsonSerializationContext context) { -+ JsonArray result = new JsonArray(); -+ -+ for (Property property : src.values()) { -+ JsonObject object = new JsonObject(); -+ -+ object.addProperty("name", property.getName()); -+ object.addProperty("value", property.getValue()); -+ -+ if (property.hasSignature()) { -+ object.addProperty("signature", property.getSignature()); -+ } -+ -+ result.add(object); -+ } -+ -+ return result; -+ } -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/util/UUIDTypeAdapter.java b/src/main/java/org/spigotmc/authlib/util/UUIDTypeAdapter.java -new file mode 100644 -index 0000000..8c3516d ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/util/UUIDTypeAdapter.java -@@ -0,0 +1,28 @@ -+package org.spigotmc.authlib.util; -+ -+import com.google.gson.TypeAdapter; -+import com.google.gson.stream.JsonReader; -+import com.google.gson.stream.JsonWriter; -+ -+import java.io.IOException; -+import java.util.UUID; -+ -+public class UUIDTypeAdapter extends TypeAdapter { -+ @Override -+ public void write(JsonWriter out, UUID value) throws IOException { -+ out.value(fromUUID(value)); -+ } -+ -+ @Override -+ public UUID read(JsonReader in) throws IOException { -+ return fromString(in.nextString()); -+ } -+ -+ public static String fromUUID(UUID value) { -+ return value.toString().replace("-", ""); -+ } -+ -+ public static UUID fromString(String input) { -+ return UUID.fromString(input.replaceFirst("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5")); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/ProfileIncompleteException.java b/src/main/java/org/spigotmc/authlib/yggdrasil/ProfileIncompleteException.java -new file mode 100644 -index 0000000..125916a ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/ProfileIncompleteException.java -@@ -0,0 +1,18 @@ -+package org.spigotmc.authlib.yggdrasil; -+ -+public class ProfileIncompleteException extends RuntimeException { -+ public ProfileIncompleteException() { -+ } -+ -+ public ProfileIncompleteException(String message) { -+ super(message); -+ } -+ -+ public ProfileIncompleteException(String message, Throwable cause) { -+ super(message, cause); -+ } -+ -+ public ProfileIncompleteException(Throwable cause) { -+ super(cause); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/ProfileNotFoundException.java b/src/main/java/org/spigotmc/authlib/yggdrasil/ProfileNotFoundException.java -new file mode 100644 -index 0000000..66ba35e ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/ProfileNotFoundException.java -@@ -0,0 +1,18 @@ -+package org.spigotmc.authlib.yggdrasil; -+ -+public class ProfileNotFoundException extends RuntimeException { -+ public ProfileNotFoundException() { -+ } -+ -+ public ProfileNotFoundException(String message) { -+ super(message); -+ } -+ -+ public ProfileNotFoundException(String message, Throwable cause) { -+ super(message, cause); -+ } -+ -+ public ProfileNotFoundException(Throwable cause) { -+ super(cause); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilAuthenticationService.java b/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilAuthenticationService.java -new file mode 100644 -index 0000000..b4c1a6b ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilAuthenticationService.java -@@ -0,0 +1,99 @@ -+package org.spigotmc.authlib.yggdrasil; -+ -+import com.google.gson.*; -+import org.spigotmc.authlib.*; -+import org.spigotmc.authlib.exceptions.AuthenticationException; -+import org.spigotmc.authlib.exceptions.AuthenticationUnavailableException; -+import org.spigotmc.authlib.exceptions.InvalidCredentialsException; -+import org.spigotmc.authlib.exceptions.UserMigratedException; -+import org.spigotmc.authlib.minecraft.MinecraftSessionService; -+import org.spigotmc.authlib.properties.PropertyMap; -+import org.spigotmc.authlib.yggdrasil.YggdrasilUserAuthentication; -+import org.spigotmc.authlib.yggdrasil.response.Response; -+import org.spigotmc.authlib.util.UUIDTypeAdapter; -+import org.apache.commons.lang3.StringUtils; -+ -+import java.io.IOException; -+import java.lang.reflect.Type; -+import java.net.Proxy; -+import java.net.URL; -+import java.util.UUID; -+ -+public class YggdrasilAuthenticationService extends HttpAuthenticationService { -+ private final String clientToken; -+ private final Gson gson; -+ -+ public YggdrasilAuthenticationService(Proxy proxy, String clientToken) { -+ super(proxy); -+ this.clientToken = clientToken; -+ GsonBuilder builder = new GsonBuilder(); -+ builder.registerTypeAdapter(GameProfile.class, new GameProfileSerializer()); -+ builder.registerTypeAdapter(PropertyMap.class, new PropertyMap.Serializer()); -+ builder.registerTypeAdapter(UUID.class, new UUIDTypeAdapter()); -+ gson = builder.create(); -+ } -+ -+ @Override -+ public UserAuthentication createUserAuthentication(Agent agent) { -+ return new YggdrasilUserAuthentication(this, agent); -+ } -+ -+ @Override -+ public MinecraftSessionService createMinecraftSessionService() { -+ return new YggdrasilMinecraftSessionService(this); -+ } -+ -+ @Override -+ public GameProfileRepository createProfileRepository() { -+ return new YggdrasilGameProfileRepository(this); -+ } -+ -+ protected T makeRequest(URL url, Object input, Class classOfT) throws AuthenticationException { -+ try { -+ String jsonResult = input == null ? performGetRequest(url) : performPostRequest(url, gson.toJson(input), "application/json"); -+ T result = gson.fromJson(jsonResult, classOfT); -+ -+ if (result == null) return null; -+ -+ if (StringUtils.isNotBlank(result.getError())) { -+ if ("UserMigratedException".equals(result.getCause())) { -+ throw new UserMigratedException(result.getErrorMessage()); -+ } else if (result.getError().equals("ForbiddenOperationException")) { -+ throw new InvalidCredentialsException(result.getErrorMessage()); -+ } else { -+ throw new AuthenticationException(result.getErrorMessage()); -+ } -+ } -+ -+ return result; -+ } catch (IOException e) { -+ throw new AuthenticationUnavailableException("Cannot contact authentication server", e); -+ } catch (IllegalStateException e) { -+ throw new AuthenticationUnavailableException("Cannot contact authentication server", e); -+ } catch (JsonParseException e) { -+ throw new AuthenticationUnavailableException("Cannot contact authentication server", e); -+ } -+ } -+ -+ public String getClientToken() { -+ return clientToken; -+ } -+ -+ private static class GameProfileSerializer implements JsonSerializer, JsonDeserializer { -+ @Override -+ public GameProfile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { -+ JsonObject object = (JsonObject) json; -+ UUID id = object.has("id") ? context.deserialize(object.get("id"), UUID.class) : null; -+ String name = object.has("name") ? object.getAsJsonPrimitive("name").getAsString() : null; -+ return new GameProfile(id, name); -+ } -+ -+ @Override -+ public JsonElement serialize(GameProfile src, Type typeOfSrc, JsonSerializationContext context) { -+ JsonObject result = new JsonObject(); -+ if (src.getId() != null) result.add("id", context.serialize(src.getId())); -+ if (src.getName() != null) result.addProperty("name", src.getName()); -+ return result; -+ } -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilGameProfileRepository.java b/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilGameProfileRepository.java -new file mode 100644 -index 0000000..0fc52cc ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilGameProfileRepository.java -@@ -0,0 +1,136 @@ -+package org.spigotmc.authlib.yggdrasil; -+ -+import com.google.common.base.Strings; -+import com.google.common.collect.Sets; -+import org.spigotmc.authlib.*; -+import org.spigotmc.authlib.exceptions.AuthenticationException; -+import org.spigotmc.authlib.yggdrasil.ProfileNotFoundException; -+import org.spigotmc.authlib.yggdrasil.YggdrasilAuthenticationService; -+import org.spigotmc.authlib.yggdrasil.response.ProfileSearchResultsResponse; -+import org.apache.commons.lang3.builder.ToStringBuilder; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+ -+import java.util.Set; -+ -+public class YggdrasilGameProfileRepository implements GameProfileRepository { -+ private static final Logger LOGGER = LogManager.getLogger(); -+ private static final String BASE_URL = "https://api.mojang.com/"; -+ private static final String SEARCH_PAGE_URL = BASE_URL + "profiles/page/"; -+ private static final int MAX_FAIL_COUNT = 3; -+ private static final int DELAY_BETWEEN_PAGES = 100; -+ private static final int DELAY_BETWEEN_FAILURES = 750; -+ -+ private final YggdrasilAuthenticationService authenticationService; -+ -+ public YggdrasilGameProfileRepository(YggdrasilAuthenticationService authenticationService) { -+ this.authenticationService = authenticationService; -+ } -+ -+ @Override -+ public void findProfilesByNames(String[] names, Agent agent, ProfileLookupCallback callback) { -+ Set criteria = Sets.newHashSet(); -+ -+ for (String name : names) { -+ if (!Strings.isNullOrEmpty(name)) { -+ criteria.add(new ProfileCriteria(name, agent)); -+ } -+ } -+ -+ Exception exception = null; -+ Set request = Sets.newHashSet(criteria); -+ int page = 1; -+ int failCount = 0; -+ -+ while (!criteria.isEmpty()) { -+ try { -+ ProfileSearchResultsResponse response = authenticationService.makeRequest(HttpAuthenticationService.constantURL(SEARCH_PAGE_URL + page), request, ProfileSearchResultsResponse.class); -+ failCount = 0; -+ exception = null; -+ -+ if (response.getSize() == 0 || response.getProfiles().length == 0) { -+ LOGGER.debug("Page {} returned empty, aborting search", page); -+ break; -+ } else { -+ LOGGER.debug("Page {} returned {} results of {}, parsing", page, response.getProfiles().length, response.getSize()); -+ -+ for (GameProfile profile : response.getProfiles()) { -+ LOGGER.debug("Successfully looked up profile {}", profile); -+ criteria.remove(new ProfileCriteria(profile.getName(), agent)); -+ callback.onProfileLookupSucceeded(profile); -+ } -+ -+ LOGGER.debug("Page {} successfully parsed", page); -+ page++; -+ -+ try { -+ Thread.sleep(DELAY_BETWEEN_PAGES); -+ } catch (InterruptedException ignored) {} -+ } -+ } catch (AuthenticationException e) { -+ exception = e; -+ failCount++; -+ -+ if (failCount == MAX_FAIL_COUNT) { -+ break; -+ } else { -+ try { -+ Thread.sleep(DELAY_BETWEEN_FAILURES); -+ } catch (InterruptedException ignored) {} -+ } -+ } -+ } -+ -+ if (criteria.isEmpty()) { -+ LOGGER.debug("Successfully found every profile requested"); -+ } else { -+ LOGGER.debug("{} profiles were missing from search results", criteria.size()); -+ if (exception == null) { -+ exception = new ProfileNotFoundException("Server did not find the requested profile"); -+ } -+ for (ProfileCriteria profileCriteria : criteria) { -+ callback.onProfileLookupFailed(new GameProfile(null, profileCriteria.getName()), exception); -+ } -+ } -+ } -+ -+ private class ProfileCriteria { -+ private final String name; -+ private final String agent; -+ -+ private ProfileCriteria(String name, Agent agent) { -+ this.name = name; -+ this.agent = agent.getName(); -+ } -+ -+ public String getName() { -+ return name; -+ } -+ -+ public String getAgent() { -+ return agent; -+ } -+ -+ @Override -+ public boolean equals(Object o) { -+ if (this == o) return true; -+ if (o == null || getClass() != o.getClass()) return false; -+ ProfileCriteria that = (ProfileCriteria) o; -+ return agent.equals(that.agent) && name.toLowerCase().equals(that.name.toLowerCase()); -+ } -+ -+ @Override -+ public int hashCode() { -+ return 31 * name.toLowerCase().hashCode() + agent.hashCode(); -+ } -+ -+ @Override -+ public String toString() { -+ return new ToStringBuilder(this) -+ .append("agent", agent) -+ .append("name", name) -+ .toString(); -+ } -+ } -+ -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilMinecraftSessionService.java b/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilMinecraftSessionService.java -new file mode 100644 -index 0000000..dc2a6f8 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilMinecraftSessionService.java -@@ -0,0 +1,177 @@ -+package org.spigotmc.authlib.yggdrasil; -+ -+import com.google.common.collect.Iterables; -+import com.google.gson.Gson; -+import com.google.gson.GsonBuilder; -+import com.google.gson.JsonParseException; -+import net.minecraft.util.org.apache.commons.io.Charsets; -+import org.spigotmc.authlib.GameProfile; -+import org.spigotmc.authlib.HttpAuthenticationService; -+import org.spigotmc.authlib.exceptions.AuthenticationException; -+import org.spigotmc.authlib.exceptions.AuthenticationUnavailableException; -+import org.spigotmc.authlib.minecraft.HttpMinecraftSessionService; -+import org.spigotmc.authlib.minecraft.MinecraftProfileTexture; -+import org.spigotmc.authlib.properties.Property; -+import org.spigotmc.authlib.yggdrasil.YggdrasilAuthenticationService; -+import org.spigotmc.authlib.yggdrasil.request.JoinMinecraftServerRequest; -+import org.spigotmc.authlib.yggdrasil.response.HasJoinedMinecraftServerResponse; -+import org.spigotmc.authlib.yggdrasil.response.MinecraftProfilePropertiesResponse; -+import org.spigotmc.authlib.yggdrasil.response.MinecraftTexturesPayload; -+import org.spigotmc.authlib.yggdrasil.response.Response; -+import org.spigotmc.authlib.util.UUIDTypeAdapter; -+import org.apache.commons.codec.binary.Base64; -+import org.apache.commons.io.IOUtils; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+ -+import java.net.URL; -+import java.security.KeyFactory; -+import java.security.PublicKey; -+import java.security.spec.X509EncodedKeySpec; -+import java.util.*; -+ -+public class YggdrasilMinecraftSessionService extends HttpMinecraftSessionService { -+ private static final Logger LOGGER = LogManager.getLogger(); -+ private static final String BASE_URL = "https://sessionserver.mojang.com/session/minecraft/"; -+ private static final URL JOIN_URL = HttpAuthenticationService.constantURL(BASE_URL + "join"); -+ private static final URL CHECK_URL = HttpAuthenticationService.constantURL(BASE_URL + "hasJoined"); -+ -+ private final PublicKey publicKey; -+ private final Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create(); -+ -+ protected YggdrasilMinecraftSessionService(YggdrasilAuthenticationService authenticationService) { -+ super(authenticationService); -+ -+ try { -+ X509EncodedKeySpec spec = new X509EncodedKeySpec(IOUtils.toByteArray(YggdrasilMinecraftSessionService.class.getResourceAsStream("/yggdrasil_session_pubkey.der"))); -+ KeyFactory keyFactory = KeyFactory.getInstance("RSA"); -+ publicKey = keyFactory.generatePublic(spec); -+ } catch (Exception e) { -+ throw new Error("Missing/invalid yggdrasil public key!"); -+ } -+ } -+ -+ @Override -+ public void joinServer(GameProfile profile, String authenticationToken, String serverId) throws AuthenticationException { -+ JoinMinecraftServerRequest request = new JoinMinecraftServerRequest(); -+ request.accessToken = authenticationToken; -+ request.selectedProfile = profile.getId(); -+ request.serverId = serverId; -+ -+ getAuthenticationService().makeRequest(JOIN_URL, request, Response.class); -+ } -+ -+ @Override -+ public GameProfile hasJoinedServer(GameProfile user, String serverId) throws AuthenticationUnavailableException { -+ Map arguments = new HashMap(); -+ -+ arguments.put("username", user.getName()); -+ arguments.put("serverId", serverId); -+ -+ URL url = HttpAuthenticationService.concatenateURL(CHECK_URL, HttpAuthenticationService.buildQuery(arguments)); -+ -+ try { -+ HasJoinedMinecraftServerResponse response = getAuthenticationService().makeRequest(url, null, HasJoinedMinecraftServerResponse.class); -+ -+ if (response != null && response.getId() != null) { -+ GameProfile result = new GameProfile(response.getId(), user.getName()); -+ -+ if (response.getProperties() != null) { -+ result.getProperties().putAll(response.getProperties()); -+ } -+ -+ return result; -+ } else { -+ return null; -+ } -+ } catch (AuthenticationUnavailableException e) { -+ throw e; -+ } catch (AuthenticationException e) { -+ return null; -+ } -+ } -+ -+ @Override -+ public Map getTextures(GameProfile profile, boolean requireSecure) { -+ Property textureProperty = Iterables.getFirst(profile.getProperties().get("textures"), null); -+ if (textureProperty == null) return new HashMap(); -+ -+ if (!textureProperty.hasSignature()) { -+ LOGGER.error("Signature is missing from textures payload"); -+ return new HashMap(); -+ } -+ -+ if (!textureProperty.isSignatureValid(publicKey)) { -+ LOGGER.error("Textures payload has been tampered with (signature invalid)"); -+ return new HashMap(); -+ } -+ -+ MinecraftTexturesPayload result; -+ try { -+ String json = new String(Base64.decodeBase64(textureProperty.getValue()), Charsets.UTF_8); -+ result = gson.fromJson(json, MinecraftTexturesPayload.class); -+ } catch (JsonParseException e) { -+ LOGGER.error("Could not decode textures payload", e); -+ return new HashMap(); -+ } -+ -+ if (result.getProfileId() == null || !result.getProfileId().equals(profile.getId())) { -+ LOGGER.error("Decrypted textures payload was for another user (expected id {} but was for {})", profile.getId(), result.getProfileId()); -+ return new HashMap(); -+ } -+ -+ if (result.getProfileName() == null || !result.getProfileName().equals(profile.getName())) { -+ LOGGER.error("Decrypted textures payload was for another user (expected name {} but was for {})", profile.getName(), result.getProfileName()); -+ return new HashMap(); -+ } -+ -+ if (requireSecure) { -+ if (result.isPublic()) { -+ LOGGER.error("Decrypted textures payload was public but we require secure data"); -+ return new HashMap(); -+ } -+ -+ Calendar limit = Calendar.getInstance(); -+ limit.add(Calendar.DATE, -1); -+ Date validFrom = new Date(result.getTimestamp()); -+ -+ if (validFrom.before(limit.getTime())) { -+ LOGGER.error("Decrypted textures payload is too old ({0}, but we need it to be at least {1})", validFrom, limit); -+ return new HashMap(); -+ } -+ } -+ -+ return result.getTextures() == null ? new HashMap() : result.getTextures(); -+ } -+ -+ @Override -+ public GameProfile fillProfileProperties(GameProfile profile) { -+ if (profile.getId() == null) { -+ return profile; -+ } -+ -+ try { -+ URL url = HttpAuthenticationService.constantURL(BASE_URL + "profile/" + UUIDTypeAdapter.fromUUID(profile.getId())); -+ MinecraftProfilePropertiesResponse response = getAuthenticationService().makeRequest(url, null, MinecraftProfilePropertiesResponse.class); -+ -+ if (response == null) { -+ LOGGER.debug("Couldn't fetch profile properties for " + profile + " as the profile does not exist"); -+ return profile; -+ } else { -+ LOGGER.debug("Successfully fetched profile properties for " + profile); -+ GameProfile result = new GameProfile(response.getId(), response.getName()); -+ result.getProperties().putAll(response.getProperties()); -+ profile.getProperties().putAll(response.getProperties()); -+ return result; -+ } -+ } catch (AuthenticationException e) { -+ LOGGER.warn("Couldn't look up profile properties for " + profile, e); -+ return profile; -+ } -+ } -+ -+ @Override -+ public YggdrasilAuthenticationService getAuthenticationService() { -+ return (YggdrasilAuthenticationService) super.getAuthenticationService(); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilUserAuthentication.java b/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilUserAuthentication.java -new file mode 100644 -index 0000000..d1b3183 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/YggdrasilUserAuthentication.java -@@ -0,0 +1,257 @@ -+package org.spigotmc.authlib.yggdrasil; -+ -+import org.spigotmc.authlib.*; -+import org.spigotmc.authlib.exceptions.AuthenticationException; -+import org.spigotmc.authlib.exceptions.InvalidCredentialsException; -+import org.spigotmc.authlib.GameProfile; -+import org.spigotmc.authlib.yggdrasil.request.AuthenticationRequest; -+import org.spigotmc.authlib.yggdrasil.request.RefreshRequest; -+import org.spigotmc.authlib.yggdrasil.response.AuthenticationResponse; -+import org.spigotmc.authlib.yggdrasil.response.RefreshResponse; -+import org.spigotmc.authlib.yggdrasil.response.User; -+import org.apache.commons.lang3.ArrayUtils; -+import org.apache.commons.lang3.StringUtils; -+import org.apache.logging.log4j.LogManager; -+import org.apache.logging.log4j.Logger; -+ -+import java.net.URL; -+import java.util.*; -+ -+public class YggdrasilUserAuthentication extends HttpUserAuthentication { -+ private static final Logger LOGGER = LogManager.getLogger(); -+ private static final String BASE_URL = "https://authserver.mojang.com/"; -+ private static final URL ROUTE_AUTHENTICATE = HttpAuthenticationService.constantURL(BASE_URL + "authenticate"); -+ private static final URL ROUTE_REFRESH = HttpAuthenticationService.constantURL(BASE_URL + "refresh"); -+ private static final URL ROUTE_VALIDATE = HttpAuthenticationService.constantURL(BASE_URL + "validate"); -+ private static final URL ROUTE_INVALIDATE = HttpAuthenticationService.constantURL(BASE_URL + "invalidate"); -+ private static final URL ROUTE_SIGNOUT = HttpAuthenticationService.constantURL(BASE_URL + "signout"); -+ -+ private static final String STORAGE_KEY_ACCESS_TOKEN = "accessToken"; -+ -+ private final Agent agent; -+ private GameProfile[] profiles; -+ private String accessToken; -+ private boolean isOnline; -+ -+ public YggdrasilUserAuthentication(YggdrasilAuthenticationService authenticationService, Agent agent) { -+ super(authenticationService); -+ this.agent = agent; -+ } -+ -+ @Override -+ public boolean canLogIn() { -+ return !canPlayOnline() && StringUtils.isNotBlank(getUsername()) && (StringUtils.isNotBlank(getPassword()) || StringUtils.isNotBlank(getAuthenticatedToken())); -+ } -+ -+ @Override -+ public void logIn() throws AuthenticationException { -+ if (StringUtils.isBlank(getUsername())) { -+ throw new InvalidCredentialsException("Invalid username"); -+ } -+ -+ if (StringUtils.isNotBlank(getAuthenticatedToken())) { -+ logInWithToken(); -+ } else if (StringUtils.isNotBlank(getPassword())) { -+ logInWithPassword(); -+ } else { -+ throw new InvalidCredentialsException("Invalid password"); -+ } -+ } -+ -+ protected void logInWithPassword() throws AuthenticationException { -+ if (StringUtils.isBlank(getUsername())) { -+ throw new InvalidCredentialsException("Invalid username"); -+ } -+ if (StringUtils.isBlank(getPassword())) { -+ throw new InvalidCredentialsException("Invalid password"); -+ } -+ -+ LOGGER.info("Logging in with username & password"); -+ -+ AuthenticationRequest request = new AuthenticationRequest(this, getUsername(), getPassword()); -+ AuthenticationResponse response = getAuthenticationService().makeRequest(ROUTE_AUTHENTICATE, request, AuthenticationResponse.class); -+ -+ if (!response.getClientToken().equals(getAuthenticationService().getClientToken())) { -+ throw new AuthenticationException("Server requested we change our client token. Don't know how to handle this!"); -+ } -+ -+ if (response.getSelectedProfile() != null) { -+ setUserType(response.getSelectedProfile().isLegacy() ? UserType.LEGACY : UserType.MOJANG); -+ } else if (ArrayUtils.isNotEmpty(response.getAvailableProfiles())) { -+ setUserType(response.getAvailableProfiles()[0].isLegacy() ? UserType.LEGACY : UserType.MOJANG); -+ } -+ -+ User user = response.getUser(); -+ -+ if (user != null && user.getId() != null) { -+ setUserid(user.getId()); -+ } else { -+ setUserid(getUsername()); -+ } -+ -+ isOnline = true; -+ accessToken = response.getAccessToken(); -+ profiles = response.getAvailableProfiles(); -+ setSelectedProfile(response.getSelectedProfile()); -+ getModifiableUserProperties().clear(); -+ -+ updateUserProperties(user); -+ } -+ -+ protected void updateUserProperties(User user) { -+ if (user == null) return; -+ -+ if (user.getProperties() != null) { -+ getModifiableUserProperties().putAll(user.getProperties()); -+ } -+ } -+ -+ protected void logInWithToken() throws AuthenticationException { -+ if (StringUtils.isBlank(getUserID())) { -+ if (StringUtils.isBlank(getUsername())) { -+ setUserid(getUsername()); -+ } else { -+ throw new InvalidCredentialsException("Invalid uuid & username"); -+ } -+ } -+ if (StringUtils.isBlank(getAuthenticatedToken())) { -+ throw new InvalidCredentialsException("Invalid access token"); -+ } -+ -+ LOGGER.info("Logging in with access token"); -+ -+ RefreshRequest request = new RefreshRequest(this); -+ RefreshResponse response = getAuthenticationService().makeRequest(ROUTE_REFRESH, request, RefreshResponse.class); -+ -+ if (!response.getClientToken().equals(getAuthenticationService().getClientToken())) { -+ throw new AuthenticationException("Server requested we change our client token. Don't know how to handle this!"); -+ } -+ -+ if (response.getSelectedProfile() != null) { -+ setUserType(response.getSelectedProfile().isLegacy() ? UserType.LEGACY : UserType.MOJANG); -+ } else if (ArrayUtils.isNotEmpty(response.getAvailableProfiles())) { -+ setUserType(response.getAvailableProfiles()[0].isLegacy() ? UserType.LEGACY : UserType.MOJANG); -+ } -+ -+ if (response.getUser() != null && response.getUser().getId() != null) { -+ setUserid(response.getUser().getId()); -+ } else { -+ setUserid(getUsername()); -+ } -+ -+ isOnline = true; -+ accessToken = response.getAccessToken(); -+ profiles = response.getAvailableProfiles(); -+ setSelectedProfile(response.getSelectedProfile()); -+ getModifiableUserProperties().clear(); -+ -+ updateUserProperties(response.getUser()); -+ } -+ -+ @Override -+ public void logOut() { -+ super.logOut(); -+ -+ accessToken = null; -+ profiles = null; -+ isOnline = false; -+ } -+ -+ @Override -+ public GameProfile[] getAvailableProfiles() { -+ return profiles; -+ } -+ -+ @Override -+ public boolean isLoggedIn() { -+ return StringUtils.isNotBlank(accessToken); -+ } -+ -+ @Override -+ public boolean canPlayOnline() { -+ return isLoggedIn() && getSelectedProfile() != null && isOnline; -+ } -+ -+ @Override -+ public void selectGameProfile(GameProfile profile) throws AuthenticationException { -+ if (!isLoggedIn()) { -+ throw new AuthenticationException("Cannot change game profile whilst not logged in"); -+ } -+ if (getSelectedProfile() != null) { -+ throw new AuthenticationException("Cannot change game profile. You must log out and back in."); -+ } -+ if (profile == null || !ArrayUtils.contains(profiles, profile)) { -+ throw new IllegalArgumentException("Invalid profile '" + profile + "'"); -+ } -+ -+ RefreshRequest request = new RefreshRequest(this, profile); -+ RefreshResponse response = getAuthenticationService().makeRequest(ROUTE_REFRESH, request, RefreshResponse.class); -+ -+ if (!response.getClientToken().equals(getAuthenticationService().getClientToken())) { -+ throw new AuthenticationException("Server requested we change our client token. Don't know how to handle this!"); -+ } -+ -+ isOnline = true; -+ accessToken = response.getAccessToken(); -+ setSelectedProfile(response.getSelectedProfile()); -+ } -+ -+ @Override -+ public void loadFromStorage(Map credentials) { -+ super.loadFromStorage(credentials); -+ -+ accessToken = String.valueOf(credentials.get(STORAGE_KEY_ACCESS_TOKEN)); -+ } -+ -+ @Override -+ public Map saveForStorage() { -+ Map result = super.saveForStorage(); -+ -+ if (StringUtils.isNotBlank(getAuthenticatedToken())) { -+ result.put(STORAGE_KEY_ACCESS_TOKEN, getAuthenticatedToken()); -+ } -+ -+ return result; -+ } -+ -+ /** -+ * @deprecated -+ */ -+ @Deprecated -+ public String getSessionToken() { -+ if (isLoggedIn() && getSelectedProfile() != null && canPlayOnline()) { -+ return String.format("token:%s:%s", getAuthenticatedToken(), getSelectedProfile().getId()); -+ } else { -+ return null; -+ } -+ } -+ -+ @Override -+ public String getAuthenticatedToken() { -+ return accessToken; -+ } -+ -+ public Agent getAgent() { -+ return agent; -+ } -+ -+ @Override -+ public String toString() { -+ return "YggdrasilAuthenticationService{" + -+ "agent=" + agent + -+ ", profiles=" + Arrays.toString(profiles) + -+ ", selectedProfile=" + getSelectedProfile() + -+ ", username='" + getUsername() + '\''+ -+ ", isLoggedIn=" + isLoggedIn() + -+ ", userType=" + getUserType() + -+ ", canPlayOnline=" + canPlayOnline() + -+ ", accessToken='" + accessToken + '\'' + -+ ", clientToken='" + getAuthenticationService().getClientToken() + '\'' + -+ '}'; -+ } -+ -+ @Override -+ public YggdrasilAuthenticationService getAuthenticationService() { -+ return (YggdrasilAuthenticationService) super.getAuthenticationService(); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/request/AuthenticationRequest.java b/src/main/java/org/spigotmc/authlib/yggdrasil/request/AuthenticationRequest.java -new file mode 100644 -index 0000000..f0457a4 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/request/AuthenticationRequest.java -@@ -0,0 +1,19 @@ -+package org.spigotmc.authlib.yggdrasil.request; -+ -+import org.spigotmc.authlib.Agent; -+import org.spigotmc.authlib.yggdrasil.YggdrasilUserAuthentication; -+ -+public class AuthenticationRequest { -+ private Agent agent; -+ private String username; -+ private String password; -+ private String clientToken; -+ private boolean requestUser = true; -+ -+ public AuthenticationRequest(YggdrasilUserAuthentication authenticationService, String username, String password) { -+ this.agent = authenticationService.getAgent(); -+ this.username = username; -+ this.clientToken = authenticationService.getAuthenticationService().getClientToken(); -+ this.password = password; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/request/InvalidateRequest.java b/src/main/java/org/spigotmc/authlib/yggdrasil/request/InvalidateRequest.java -new file mode 100644 -index 0000000..75ee0f3 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/request/InvalidateRequest.java -@@ -0,0 +1,13 @@ -+package org.spigotmc.authlib.yggdrasil.request; -+ -+import org.spigotmc.authlib.yggdrasil.YggdrasilUserAuthentication; -+ -+public class InvalidateRequest { -+ private String accessToken; -+ private String clientToken; -+ -+ public InvalidateRequest(YggdrasilUserAuthentication authenticationService) { -+ this.accessToken = authenticationService.getAuthenticatedToken(); -+ this.clientToken = authenticationService.getAuthenticationService().getClientToken(); -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/request/JoinMinecraftServerRequest.java b/src/main/java/org/spigotmc/authlib/yggdrasil/request/JoinMinecraftServerRequest.java -new file mode 100644 -index 0000000..a9ff35c ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/request/JoinMinecraftServerRequest.java -@@ -0,0 +1,9 @@ -+package org.spigotmc.authlib.yggdrasil.request; -+ -+import java.util.UUID; -+ -+public class JoinMinecraftServerRequest { -+ public String accessToken; -+ public UUID selectedProfile; -+ public String serverId; -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/request/RefreshRequest.java b/src/main/java/org/spigotmc/authlib/yggdrasil/request/RefreshRequest.java -new file mode 100644 -index 0000000..4f52290 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/request/RefreshRequest.java -@@ -0,0 +1,21 @@ -+package org.spigotmc.authlib.yggdrasil.request; -+ -+import org.spigotmc.authlib.GameProfile; -+import org.spigotmc.authlib.yggdrasil.YggdrasilUserAuthentication; -+ -+public class RefreshRequest { -+ private String clientToken; -+ private String accessToken; -+ private GameProfile selectedProfile; -+ private boolean requestUser = true; -+ -+ public RefreshRequest(YggdrasilUserAuthentication authenticationService) { -+ this(authenticationService, null); -+ } -+ -+ public RefreshRequest(YggdrasilUserAuthentication authenticationService, GameProfile profile) { -+ this.clientToken = authenticationService.getAuthenticationService().getClientToken(); -+ this.accessToken = authenticationService.getAuthenticatedToken(); -+ this.selectedProfile = profile; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/response/AuthenticationResponse.java b/src/main/java/org/spigotmc/authlib/yggdrasil/response/AuthenticationResponse.java -new file mode 100644 -index 0000000..0a03b1b ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/response/AuthenticationResponse.java -@@ -0,0 +1,35 @@ -+package org.spigotmc.authlib.yggdrasil.response; -+ -+import org.spigotmc.authlib.GameProfile; -+import org.spigotmc.authlib.yggdrasil.response.Response; -+import org.spigotmc.authlib.yggdrasil.response.User; -+ -+public class AuthenticationResponse extends Response -+{ -+ private String accessToken; -+ private String clientToken; -+ private GameProfile selectedProfile; -+ private GameProfile[] availableProfiles; -+ private User user; -+ -+ public String getAccessToken() { -+ return accessToken; -+ } -+ -+ public String getClientToken() { -+ return clientToken; -+ } -+ -+ public GameProfile[] getAvailableProfiles() { -+ return availableProfiles; -+ } -+ -+ public GameProfile getSelectedProfile() { -+ return selectedProfile; -+ } -+ -+ public User getUser() { -+ return user; -+ } -+ -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/response/HasJoinedMinecraftServerResponse.java b/src/main/java/org/spigotmc/authlib/yggdrasil/response/HasJoinedMinecraftServerResponse.java -new file mode 100644 -index 0000000..f93acf0 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/response/HasJoinedMinecraftServerResponse.java -@@ -0,0 +1,20 @@ -+package org.spigotmc.authlib.yggdrasil.response; -+ -+import org.spigotmc.authlib.properties.PropertyMap; -+import org.spigotmc.authlib.yggdrasil.response.Response; -+ -+import java.util.UUID; -+ -+public class HasJoinedMinecraftServerResponse extends Response -+{ -+ private UUID id; -+ private PropertyMap properties; -+ -+ public UUID getId() { -+ return id; -+ } -+ -+ public PropertyMap getProperties() { -+ return properties; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/response/MinecraftProfilePropertiesResponse.java b/src/main/java/org/spigotmc/authlib/yggdrasil/response/MinecraftProfilePropertiesResponse.java -new file mode 100644 -index 0000000..4cf76e5 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/response/MinecraftProfilePropertiesResponse.java -@@ -0,0 +1,25 @@ -+package org.spigotmc.authlib.yggdrasil.response; -+ -+import org.spigotmc.authlib.properties.PropertyMap; -+import org.spigotmc.authlib.yggdrasil.response.Response; -+ -+import java.util.UUID; -+ -+public class MinecraftProfilePropertiesResponse extends Response -+{ -+ private UUID id; -+ private String name; -+ private PropertyMap properties; -+ -+ public UUID getId() { -+ return id; -+ } -+ -+ public String getName() { -+ return name; -+ } -+ -+ public PropertyMap getProperties() { -+ return properties; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/response/MinecraftTexturesPayload.java b/src/main/java/org/spigotmc/authlib/yggdrasil/response/MinecraftTexturesPayload.java -new file mode 100644 -index 0000000..72293ce ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/response/MinecraftTexturesPayload.java -@@ -0,0 +1,34 @@ -+package org.spigotmc.authlib.yggdrasil.response; -+ -+import org.spigotmc.authlib.minecraft.MinecraftProfileTexture; -+ -+import java.util.Map; -+import java.util.UUID; -+ -+public class MinecraftTexturesPayload { -+ private long timestamp; -+ private UUID profileId; -+ private String profileName; -+ private boolean isPublic; -+ private Map textures; -+ -+ public long getTimestamp() { -+ return timestamp; -+ } -+ -+ public UUID getProfileId() { -+ return profileId; -+ } -+ -+ public String getProfileName() { -+ return profileName; -+ } -+ -+ public boolean isPublic() { -+ return isPublic; -+ } -+ -+ public Map getTextures() { -+ return textures; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/response/ProfileSearchResultsResponse.java b/src/main/java/org/spigotmc/authlib/yggdrasil/response/ProfileSearchResultsResponse.java -new file mode 100644 -index 0000000..2c10758 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/response/ProfileSearchResultsResponse.java -@@ -0,0 +1,18 @@ -+package org.spigotmc.authlib.yggdrasil.response; -+ -+import org.spigotmc.authlib.GameProfile; -+import org.spigotmc.authlib.yggdrasil.response.Response; -+ -+public class ProfileSearchResultsResponse extends Response -+{ -+ private GameProfile[] profiles; -+ private int size; -+ -+ public GameProfile[] getProfiles() { -+ return profiles; -+ } -+ -+ public int getSize() { -+ return size; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/response/RefreshResponse.java b/src/main/java/org/spigotmc/authlib/yggdrasil/response/RefreshResponse.java -new file mode 100644 -index 0000000..d64667b ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/response/RefreshResponse.java -@@ -0,0 +1,34 @@ -+package org.spigotmc.authlib.yggdrasil.response; -+ -+import org.spigotmc.authlib.GameProfile; -+import org.spigotmc.authlib.yggdrasil.response.Response; -+import org.spigotmc.authlib.yggdrasil.response.User; -+ -+public class RefreshResponse extends Response -+{ -+ private String accessToken; -+ private String clientToken; -+ private GameProfile selectedProfile; -+ private GameProfile[] availableProfiles; -+ private User user; -+ -+ public String getAccessToken() { -+ return accessToken; -+ } -+ -+ public String getClientToken() { -+ return clientToken; -+ } -+ -+ public GameProfile[] getAvailableProfiles() { -+ return availableProfiles; -+ } -+ -+ public GameProfile getSelectedProfile() { -+ return selectedProfile; -+ } -+ -+ public User getUser() { -+ return user; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/response/Response.java b/src/main/java/org/spigotmc/authlib/yggdrasil/response/Response.java -new file mode 100644 -index 0000000..73c9da8 ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/response/Response.java -@@ -0,0 +1,19 @@ -+package org.spigotmc.authlib.yggdrasil.response; -+ -+public class Response { -+ private String error; -+ private String errorMessage; -+ private String cause; -+ -+ public String getError() { -+ return error; -+ } -+ -+ public String getCause() { -+ return cause; -+ } -+ -+ public String getErrorMessage() { -+ return errorMessage; -+ } -+} -diff --git a/src/main/java/org/spigotmc/authlib/yggdrasil/response/User.java b/src/main/java/org/spigotmc/authlib/yggdrasil/response/User.java -new file mode 100644 -index 0000000..e4893fd ---- /dev/null -+++ b/src/main/java/org/spigotmc/authlib/yggdrasil/response/User.java -@@ -0,0 +1,16 @@ -+package org.spigotmc.authlib.yggdrasil.response; -+ -+import org.spigotmc.authlib.properties.PropertyMap; -+ -+public class User { -+ private String id; -+ private PropertyMap properties; -+ -+ public String getId() { -+ return id; -+ } -+ -+ public PropertyMap getProperties() { -+ return properties; -+ } -+} -diff --git a/src/main/resources/yggdrasil_session_pubkey.der b/src/main/resources/yggdrasil_session_pubkey.der -new file mode 100644 -index 0000000000000000000000000000000000000000..9c79a3aa4771da1f15af37a2af0898f878ad816f -GIT binary patch -literal 550 -zcmV+>0@?jAf&wBi4F(A+hDe6@4FLfG1potr0uKN%f&vNxf&u{m%20R*skxUv>`)yD7%qARQ>bP36CtP}x@~Z9*VkPYUS6pWrZtT#q)8GCbf0*5+4Pyw;#Tq!Aq{bDO|Iw<43LnG*#4;?Z&LM71zXE@J`@_dMl`?qQ1 -z@3qR;mm=XG_-{G#4vhRxmZiQslrTtB_&7&0ECnD9)gZ}j`e&Je+s -Date: Wed, 9 Apr 2014 13:29:57 +0100 -Subject: [PATCH] Convert player heads async - - -diff --git a/src/main/java/net/minecraft/server/Chunk.java b/src/main/java/net/minecraft/server/Chunk.java -index 90f32ed..6c2e905 100644 ---- a/src/main/java/net/minecraft/server/Chunk.java -+++ b/src/main/java/net/minecraft/server/Chunk.java -@@ -513,6 +513,13 @@ public class Chunk { - if (tileentity != null) { - tileentity.u(); - } -+ -+ // Spigot start -+ if ( tileentity instanceof TileEntitySkull ) -+ { -+ org.spigotmc.HeadConverter.convertHead( (TileEntitySkull) tileentity ); -+ } -+ // Spigot end - } - - this.n = true; -diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java -index fb262bc..4c086d3 100644 ---- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java -+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java -@@ -378,6 +378,12 @@ public class ChunkRegionLoader implements IChunkLoader, IAsyncChunkSaver { - TileEntity tileentity = TileEntity.c(nbttagcompound4); - - if (tileentity != null) { -+ // Spigot start -+ if ( tileentity instanceof TileEntitySkull ) -+ { -+ org.spigotmc.HeadConverter.convertHead( (TileEntitySkull) tileentity ); -+ } -+ // Spigot end - chunk.a(tileentity); - } - } -diff --git a/src/main/java/net/minecraft/server/ItemSkull.java b/src/main/java/net/minecraft/server/ItemSkull.java -index 6b2bf9b..5a74142 100644 ---- a/src/main/java/net/minecraft/server/ItemSkull.java -+++ b/src/main/java/net/minecraft/server/ItemSkull.java -@@ -68,6 +68,7 @@ public class ItemSkull extends Item { - ((TileEntitySkull) tileentity).setSkullType(itemstack.getData(), s); - ((TileEntitySkull) tileentity).setRotation(i1); - ((BlockSkull) Blocks.SKULL).a(world, i, j, k, (TileEntitySkull) tileentity); -+ org.spigotmc.HeadConverter.convertHead( (TileEntitySkull) tileentity ); // Spigot - } - - --itemstack.count; -diff --git a/src/main/java/net/minecraft/server/PacketPlayOutTileEntityData.java b/src/main/java/net/minecraft/server/PacketPlayOutTileEntityData.java -index 005f1fe..4780c53 100644 ---- a/src/main/java/net/minecraft/server/PacketPlayOutTileEntityData.java -+++ b/src/main/java/net/minecraft/server/PacketPlayOutTileEntityData.java -@@ -1,5 +1,7 @@ - package net.minecraft.server; - -+import net.minecraft.util.com.mojang.authlib.GameProfile; -+ - public class PacketPlayOutTileEntityData extends Packet { - - private int a; -@@ -41,12 +43,14 @@ public class PacketPlayOutTileEntityData extends Packet { - packetdataserializer.writeShort(this.b); - packetdataserializer.writeInt(this.c); - packetdataserializer.writeByte((byte) this.d); -- if ( this.e.hasKey( "ExtraType" ) ) -+ if ( this.e.hasKey( "ExtraType" ) && !this.e.hasKey( "Owner" ) ) - { - NBTTagCompound profile = new NBTTagCompound(); - profile.setString( "Name", this.e.getString( "ExtraType" ) ); - profile.setString( "Id", "" ); - this.e.set( "Owner", profile ); -+ } else { -+ this.e.remove( "ExtraType" ); - } - packetdataserializer.a(this.e); - } -diff --git a/src/main/java/net/minecraft/server/TileEntitySkull.java b/src/main/java/net/minecraft/server/TileEntitySkull.java -index b241cfe..925e017 100644 ---- a/src/main/java/net/minecraft/server/TileEntitySkull.java -+++ b/src/main/java/net/minecraft/server/TileEntitySkull.java -@@ -5,6 +5,7 @@ public class TileEntitySkull extends TileEntity { - private int a; - private int i; - private String j = ""; -+ private NBTTagCompound owner = null; - - public TileEntitySkull() {} - -@@ -13,6 +14,7 @@ public class TileEntitySkull extends TileEntity { - nbttagcompound.setByte("SkullType", (byte) (this.a & 255)); - nbttagcompound.setByte("Rot", (byte) (this.i & 255)); - nbttagcompound.setString("ExtraType", this.j); -+ if ( owner != null ) nbttagcompound.set( "Owner", owner ); - } - - public void a(NBTTagCompound nbttagcompound) { -@@ -22,6 +24,10 @@ public class TileEntitySkull extends TileEntity { - if (nbttagcompound.hasKeyOfType("ExtraType", 8)) { - this.j = nbttagcompound.getString("ExtraType"); - } -+ if ( nbttagcompound.hasKey( "Owner" ) ) -+ { -+ owner = nbttagcompound.getCompound( "Owner" ); -+ } - } - - public Packet getUpdatePacket() { -diff --git a/src/main/java/org/spigotmc/HeadConverter.java b/src/main/java/org/spigotmc/HeadConverter.java -new file mode 100644 -index 0000000..ad0454a ---- /dev/null -+++ b/src/main/java/org/spigotmc/HeadConverter.java -@@ -0,0 +1,195 @@ -+package org.spigotmc; -+ -+import com.google.common.base.Charsets; -+import com.google.common.util.concurrent.ThreadFactoryBuilder; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonParser; -+import net.minecraft.server.EntityHuman; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.NBTBase; -+import net.minecraft.server.NBTTagCompound; -+import net.minecraft.server.NBTTagList; -+import net.minecraft.server.TileEntitySkull; -+import net.minecraft.util.com.mojang.authlib.GameProfile; -+import org.bukkit.Bukkit; -+import org.spigotmc.authlib.properties.Property; -+ -+import java.io.IOException; -+import java.io.InputStreamReader; -+import java.io.OutputStream; -+import java.net.HttpURLConnection; -+import java.net.MalformedURLException; -+import java.net.URL; -+import java.util.Set; -+import java.util.UUID; -+import java.util.concurrent.Executor; -+import java.util.concurrent.Executors; -+ -+public class HeadConverter -+{ -+ private static final Executor executor = Executors.newFixedThreadPool( 3, -+ new ThreadFactoryBuilder() -+ .setNameFormat( "Head Conversion Thread - %1$d" ) -+ .build() -+ ); -+ private static boolean hasWarned = false; -+ -+ public static void convertHead(final TileEntitySkull head) -+ { -+ if ( head.getSkullType() != 3 ) -+ { -+ return; -+ } -+ final int x = head.x; -+ final int y = head.y; -+ final int z = head.z; -+ final String name = head.getExtraType(); -+ final NBTTagCompound tag = new NBTTagCompound(); -+ head.b( tag ); -+ if ( tag.hasKey( "Owner" ) && tag.getCompound( "Owner" ).hasKey( "Properties" ) ) -+ { -+ // Validate the head -+ org.spigotmc.authlib.GameProfile profile = getProfile( tag.getCompound( "Owner" ) ); -+ if ( MinecraftServer.getServer().newSessionService.getTextures( profile, false ).size() == 0 ) { -+ tag.remove( "Owner" ); -+ head.a( tag ); -+ } else -+ { -+ return; -+ } -+ } -+ -+ executor.execute( new Runnable() -+ { -+ @Override -+ public void run() -+ { -+ HttpURLConnection connection = null; -+ try -+ { -+ // Firstly convert name -> uuid -+ URL accountsAPI = new URL( "https://api.mojang.com/profiles/page/1" ); -+ -+ connection = (HttpURLConnection) accountsAPI.openConnection(); -+ connection.setRequestProperty( "Content-Type", "application/json" ); -+ connection.setRequestMethod( "POST" ); -+ connection.setDoInput( true ); -+ connection.setDoOutput( true ); -+ -+ OutputStream outputStream = connection.getOutputStream(); -+ outputStream.write( ( "[{\"name\":\"" + name + -+ "\", \"agent\":\"minecraft\"}]" ).getBytes( Charsets.UTF_8 ) ); -+ outputStream.flush(); -+ outputStream.close(); -+ -+ InputStreamReader inputStreamReader = new InputStreamReader( connection.getInputStream() ); -+ JsonObject response; -+ try -+ { -+ response = new JsonParser().parse( inputStreamReader ) -+ .getAsJsonObject(); -+ } finally -+ { -+ inputStreamReader.close(); -+ } -+ if ( response.get( "size" ).getAsInt() != 1 || -+ response.getAsJsonArray( "profiles" ).size() != 1 ) -+ { -+ return; -+ } -+ String uuid = response.getAsJsonArray( "profiles" ) -+ .get( 0 ).getAsJsonObject() -+ .get( "id" ).getAsString(); -+ String correctedName = response.getAsJsonArray( "profiles" ) -+ .get( 0 ).getAsJsonObject() -+ .get( "name" ).getAsString(); -+ -+ NBTTagCompound owner = new NBTTagCompound(); -+ GameProfile gameProfile = new GameProfile( uuid, correctedName ); -+ owner.setString( "Name", correctedName ); -+ owner.setString( "Id", EntityHuman.a( gameProfile ).toString() ); -+ -+ NBTTagCompound properties = new NBTTagCompound(); -+ -+ // Now to lookup the textures -+ org.spigotmc.authlib.GameProfile newStyleProfile = new org.spigotmc.authlib.GameProfile( -+ EntityHuman.a( gameProfile ), -+ gameProfile.getName() ); -+ MinecraftServer.getServer().newSessionService.fillProfileProperties( newStyleProfile ); -+ -+ if ( newStyleProfile.getProperties().size() < 1) -+ { -+ return; -+ } -+ -+ -+ for ( String key : newStyleProfile.getProperties().keys() ) -+ { -+ NBTTagList propList = new NBTTagList(); -+ for ( Property prop : newStyleProfile.getProperties().get( key ) ) -+ { -+ NBTTagCompound nprop = new NBTTagCompound(); -+ nprop.setString( "Signature", prop.getSignature() ); -+ nprop.setString( "Value", prop.getValue() ); -+ propList.add( nprop ); -+ } -+ properties.set( key, propList ); -+ } -+ -+ owner.set( "Properties", properties ); -+ tag.set( "Owner", owner ); -+ -+ // Update the tile entity -+ MinecraftServer.getServer().processQueue.add( new Runnable() -+ { -+ @Override -+ public void run() -+ { -+ head.a( tag ); -+ // Send the updated version -+ MinecraftServer.getServer().getPlayerList().sendPacketNearby( -+ x, y, z, head.getWorld().spigotConfig.viewDistance * 16, head.getWorld().worldData.j(), -+ head.getUpdatePacket() ); -+ } -+ } ); -+ -+ } catch ( MalformedURLException e ) -+ { -+ e.printStackTrace(); -+ } catch ( IOException e ) -+ { -+ if (!hasWarned) -+ { -+ hasWarned = true; -+ Bukkit.getLogger().warning( "Error connecting to Mojang servers, cannot convert player heads" ); -+ } -+ } finally -+ { -+ if ( connection != null ) -+ { -+ connection.disconnect(); -+ } -+ } -+ } -+ } ); -+ } -+ -+ private static org.spigotmc.authlib.GameProfile getProfile(NBTTagCompound owner) -+ { -+ org.spigotmc.authlib.GameProfile profile = new org.spigotmc.authlib.GameProfile( -+ UUID.fromString( owner.getString( "Id" ) ), owner.getString( "Name" ) ); -+ -+ NBTTagCompound properties = owner.getCompound( "Properties" ); -+ for (String key : (Set) properties.c()) -+ { -+ NBTTagList props = properties.getList( key, 10 ); -+ for (int i = 0; i < props.size(); i++) { -+ NBTTagCompound prop = props.get( i ); -+ profile.getProperties().put( key, new Property( key, prop.getString( "Value" ), prop.getString( "Signature" ) ) ); -+ } -+ } -+ return profile; -+ } -+} --- -1.8.5.2.msysgit.0 -