From 4efc939384fd110152d57ffc5f276be538f57bb1 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..5483641 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
@@ -33,7 +34,29 @@ public class PacketPlayOutNamedEntitySpawn extends Packet {
this.h = itemstack == null ? 0 : Item.b(itemstack.getItem());
this.i = entityhuman.getDataWatcher();
+
+ // Spigot start - Validate Profile
+ validate();
+ }
+
+ private void validate()
+ {
+ if ( MinecraftServer.getServer().getOnlineMode() )
+ {
+ if ( this.b instanceof ThreadPlayerLookupUUID.NewGameProfileWrapper )
+ {
+ org.spigotmc.authlib.GameProfile newProfile = ( (ThreadPlayerLookupUUID.NewGameProfileWrapper) this.b ).newProfile;
+ if ( MinecraftServer.getServer().newSessionService.getTextures( newProfile, true ).size() == 0 )
+ {
+ throw new IllegalArgumentException( "PacketPlayOutNamedEntitySpawn requires a valid profile in online mode" );
+ }
+ } else
+ {
+ throw new IllegalArgumentException( "PacketPlayOutNamedEntitySpawn requires a valid profile in online mode" );
+ }
+ }
}
+ // Spigot end
public void a(PacketDataSerializer packetdataserializer) throws IOException { // CraftBukkit - added throws
this.a = packetdataserializer.a();
@@ -60,6 +83,42 @@ public class PacketPlayOutNamedEntitySpawn extends Packet {
this.i.a(packetdataserializer);
}
+ // Spigot start
+ @Override
+ public void writeSnapshot(PacketDataSerializer packetdataserializer) throws IOException
+ { // CraftBukkit - added throws
+ validate();
+ 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