diff --git a/README.md b/README.md
index f25db233..526deac2 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,6 @@
--------
MCProtocolLib is a simple library for communicating with a Minecraft client/server. It aims to allow people to make custom bots, clients, or servers for Minecraft easily.
-The library is split into two packages, org.spacehq.mc.auth and org.spacehq.mc.protocol. The auth package contains some classes to work with Mojang's auth servers and the protocol package contains the protocol library.
Example Code
diff --git a/example/org/spacehq/mc/protocol/test/Test.java b/example/org/spacehq/mc/protocol/test/Test.java
index 865b34ee..fc36faaa 100644
--- a/example/org/spacehq/mc/protocol/test/Test.java
+++ b/example/org/spacehq/mc/protocol/test/Test.java
@@ -9,7 +9,11 @@ import org.spacehq.mc.protocol.ServerLoginHandler;
import org.spacehq.mc.protocol.data.game.values.entity.player.GameMode;
import org.spacehq.mc.protocol.data.game.values.setting.Difficulty;
import org.spacehq.mc.protocol.data.game.values.world.WorldType;
-import org.spacehq.mc.protocol.data.message.*;
+import org.spacehq.mc.protocol.data.message.ChatColor;
+import org.spacehq.mc.protocol.data.message.ChatFormat;
+import org.spacehq.mc.protocol.data.message.Message;
+import org.spacehq.mc.protocol.data.message.MessageStyle;
+import org.spacehq.mc.protocol.data.message.TextMessage;
import org.spacehq.mc.protocol.data.status.PlayerInfo;
import org.spacehq.mc.protocol.data.status.ServerStatusInfo;
import org.spacehq.mc.protocol.data.status.VersionInfo;
@@ -24,6 +28,7 @@ import org.spacehq.packetlib.Server;
import org.spacehq.packetlib.Session;
import org.spacehq.packetlib.event.server.ServerAdapter;
import org.spacehq.packetlib.event.server.SessionAddedEvent;
+import org.spacehq.packetlib.event.server.SessionRemovedEvent;
import org.spacehq.packetlib.event.session.DisconnectedEvent;
import org.spacehq.packetlib.event.session.PacketReceivedEvent;
import org.spacehq.packetlib.event.session.SessionAdapter;
@@ -78,6 +83,15 @@ public class Test {
}
});
}
+
+ @Override
+ public void sessionRemoved(SessionRemovedEvent event) {
+ MinecraftProtocol protocol = (MinecraftProtocol) event.getSession().getPacketProtocol();
+ if(protocol.getMode() == ProtocolMode.GAME) {
+ System.out.println("Closing server.");
+ event.getServer().close();
+ }
+ }
});
server.bind();
diff --git a/pom.xml b/pom.xml
index 1ec89c61..afab8f2c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -76,22 +76,15 @@
compile
- com.google.code.gson
- gson
- 2.2.4
+ org.spacehq
+ mcauthlib
+ 1.0
clean install
${basedir}/src/main/java
-
-
-
- ${basedir}/src/main/resources
- false
-
-
org.apache.maven.plugins
diff --git a/src/main/java/org/spacehq/mc/auth/GameProfile.java b/src/main/java/org/spacehq/mc/auth/GameProfile.java
deleted file mode 100644
index 3b6996ce..00000000
--- a/src/main/java/org/spacehq/mc/auth/GameProfile.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package org.spacehq.mc.auth;
-
-import org.spacehq.mc.auth.properties.PropertyMap;
-
-import java.util.UUID;
-
-public class GameProfile {
-
- private UUID id;
- private String name;
- private PropertyMap properties = new PropertyMap();
- private boolean legacy;
-
- public GameProfile(String id, String name) {
- this(id == null || id.equals("") ? null : UUID.fromString(id), name);
- }
-
- public GameProfile(UUID id, String name) {
- if(id == null && (name == null || name.equals(""))) {
- throw new IllegalArgumentException("Name and ID cannot both be blank");
- } else {
- this.id = id;
- this.name = name;
- }
- }
-
- public UUID getId() {
- return this.id;
- }
-
- public String getIdAsString() {
- return this.id != null ? this.id.toString() : "";
- }
-
- public String getName() {
- return this.name;
- }
-
- public PropertyMap getProperties() {
- return this.properties;
- }
-
- public boolean isLegacy() {
- return this.legacy;
- }
-
- public boolean isComplete() {
- return this.id != null && this.name != null && !this.name.equals("");
- }
-
- @Override
- public boolean equals(Object o) {
- if(this == o) {
- return true;
- } else if(o != null && this.getClass() == o.getClass()) {
- GameProfile that = (GameProfile) o;
- return (this.id != null ? this.id.equals(that.id) : that.id == null) && (this.name != null ? this.name.equals(that.name) : that.name == null);
- } else {
- return false;
- }
- }
-
- @Override
- public int hashCode() {
- int result = this.id != null ? this.id.hashCode() : 0;
- result = 31 * result + (this.name != null ? this.name.hashCode() : 0);
- return result;
- }
-
- @Override
- public String toString() {
- return "GameProfile{id=" + this.id + ", name=" + this.name + ", properties=" + this.properties + ", legacy=" + this.legacy + "}";
- }
-
-}
diff --git a/src/main/java/org/spacehq/mc/auth/GameProfileRepository.java b/src/main/java/org/spacehq/mc/auth/GameProfileRepository.java
deleted file mode 100644
index 9455e751..00000000
--- a/src/main/java/org/spacehq/mc/auth/GameProfileRepository.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package org.spacehq.mc.auth;
-
-import org.spacehq.mc.auth.exception.AuthenticationException;
-import org.spacehq.mc.auth.exception.ProfileNotFoundException;
-import org.spacehq.mc.auth.response.ProfileSearchResultsResponse;
-import org.spacehq.mc.util.URLUtils;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.UUID;
-
-public class GameProfileRepository {
-
- private static final String BASE_URL = "https://api.mojang.com/";
- private static final String SEARCH_PAGE_URL = "https://api.mojang.com/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;
-
- public void findProfilesByNames(String[] names, ProfileLookupCallback callback) {
- Set criteria = new HashSet();
- for(String name : names) {
- if(name != null && !name.isEmpty()) {
- criteria.add(new ProfileCriteria(name));
- }
- }
-
- Set request = new HashSet(criteria);
- int page = 1;
- Exception error = null;
- int failCount = 0;
- while(failCount < MAX_FAIL_COUNT && !criteria.isEmpty()) {
- try {
- ProfileSearchResultsResponse response = (ProfileSearchResultsResponse) URLUtils.makeRequest(URLUtils.constantURL("https://api.mojang.com/profiles/page/" + page), request, ProfileSearchResultsResponse.class);
- failCount = 0;
- error = null;
- if(response.getSize() != 0 && response.getProfiles().length != 0) {
- for(GameProfile profile : response.getProfiles()) {
- criteria.remove(new ProfileCriteria(profile.getName()));
- callback.onProfileLookupSucceeded(profile);
- }
-
- page++;
- try {
- Thread.sleep(DELAY_BETWEEN_PAGES);
- } catch(InterruptedException ignored) {
- }
- }
- } catch(AuthenticationException e) {
- error = e;
- failCount++;
- try {
- Thread.sleep(DELAY_BETWEEN_FAILURES);
- } catch(InterruptedException ignored) {
- }
- }
- }
-
- if(!criteria.isEmpty()) {
- if(error == null) {
- error = new ProfileNotFoundException("Server did not find the requested profile");
- }
-
- for(ProfileCriteria profileCriteria : criteria) {
- callback.onProfileLookupFailed(new GameProfile((UUID) null, profileCriteria.getName()), error);
- }
- }
- }
-
-}
diff --git a/src/main/java/org/spacehq/mc/auth/ProfileCriteria.java b/src/main/java/org/spacehq/mc/auth/ProfileCriteria.java
deleted file mode 100644
index 5219c9d0..00000000
--- a/src/main/java/org/spacehq/mc/auth/ProfileCriteria.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.spacehq.mc.auth;
-
-public class ProfileCriteria {
-
- private final String name;
-
- public ProfileCriteria(String name) {
- this.name = name;
- }
-
- public String getName() {
- return this.name;
- }
-
- public boolean equals(Object o) {
- if(this == o) {
- return true;
- } else if(o != null && this.getClass() == o.getClass()) {
- ProfileCriteria that = (ProfileCriteria) o;
- return this.name.toLowerCase().equals(that.name.toLowerCase());
- } else {
- return false;
- }
- }
-
- public int hashCode() {
- return 31 * this.name.toLowerCase().hashCode();
- }
-
- public String toString() {
- return "GameProfileRepository{name=" + this.name + "}";
- }
-
-}
diff --git a/src/main/java/org/spacehq/mc/auth/ProfileLookupCallback.java b/src/main/java/org/spacehq/mc/auth/ProfileLookupCallback.java
deleted file mode 100644
index 1a0fc77e..00000000
--- a/src/main/java/org/spacehq/mc/auth/ProfileLookupCallback.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.spacehq.mc.auth;
-
-public interface ProfileLookupCallback {
-
- public void onProfileLookupSucceeded(GameProfile profile);
-
- public void onProfileLookupFailed(GameProfile profile, Exception e);
-
-}
diff --git a/src/main/java/org/spacehq/mc/auth/ProfileTexture.java b/src/main/java/org/spacehq/mc/auth/ProfileTexture.java
deleted file mode 100644
index 27f8be66..00000000
--- a/src/main/java/org/spacehq/mc/auth/ProfileTexture.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.spacehq.mc.auth;
-
-public class ProfileTexture {
-
- private String url;
-
- public ProfileTexture(String url) {
- this.url = url;
- }
-
- public String getUrl() {
- return this.url;
- }
-
- public String getHash() {
- String url = this.url.endsWith("/") ? this.url.substring(0, this.url.length() - 1) : this.url;
- int slash = url.lastIndexOf("/");
- int dot = url.lastIndexOf(".", slash);
- return url.substring(slash + 1, dot != -1 ? dot : url.length());
- }
-
- @Override
- public String toString() {
- return "ProfileTexture{url=" + this.url + ", hash=" + this.getHash() + "}";
- }
-
-}
diff --git a/src/main/java/org/spacehq/mc/auth/ProfileTextureType.java b/src/main/java/org/spacehq/mc/auth/ProfileTextureType.java
deleted file mode 100644
index d42cc451..00000000
--- a/src/main/java/org/spacehq/mc/auth/ProfileTextureType.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.spacehq.mc.auth;
-
-public enum ProfileTextureType {
-
- SKIN,
- CAPE;
-
-}
diff --git a/src/main/java/org/spacehq/mc/auth/SessionService.java b/src/main/java/org/spacehq/mc/auth/SessionService.java
deleted file mode 100644
index c02a0e32..00000000
--- a/src/main/java/org/spacehq/mc/auth/SessionService.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package org.spacehq.mc.auth;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import org.spacehq.mc.auth.exception.*;
-import org.spacehq.mc.auth.properties.Property;
-import org.spacehq.mc.auth.request.JoinServerRequest;
-import org.spacehq.mc.auth.response.HasJoinedResponse;
-import org.spacehq.mc.auth.response.MinecraftProfilePropertiesResponse;
-import org.spacehq.mc.auth.response.MinecraftTexturesPayload;
-import org.spacehq.mc.auth.response.Response;
-import org.spacehq.mc.auth.serialize.UUIDSerializer;
-import org.spacehq.mc.util.Base64;
-import org.spacehq.mc.util.IOUtils;
-import org.spacehq.mc.util.URLUtils;
-
-import java.net.URL;
-import java.security.KeyFactory;
-import java.security.PublicKey;
-import java.security.spec.X509EncodedKeySpec;
-import java.util.*;
-
-public class SessionService {
-
- private static final String BASE_URL = "https://sessionserver.mojang.com/session/minecraft/";
- private static final URL JOIN_URL = URLUtils.constantURL(BASE_URL + "join");
- private static final URL CHECK_URL = URLUtils.constantURL(BASE_URL + "hasJoined");
-
- private static final PublicKey SIGNATURE_KEY;
- private static final Gson GSON = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDSerializer()).create();
-
- static {
- try {
- X509EncodedKeySpec spec = new X509EncodedKeySpec(IOUtils.toByteArray(SessionService.class.getResourceAsStream("/yggdrasil_session_pubkey.der")));
- KeyFactory keyFactory = KeyFactory.getInstance("RSA");
- SIGNATURE_KEY = keyFactory.generatePublic(spec);
- } catch(Exception var4) {
- throw new ExceptionInInitializerError("Missing/invalid yggdrasil public key.");
- }
- }
-
- public void joinServer(GameProfile profile, String authenticationToken, String serverId) throws AuthenticationException {
- JoinServerRequest request = new JoinServerRequest(authenticationToken, profile.getId(), serverId);
- URLUtils.makeRequest(JOIN_URL, request, Response.class);
- }
-
- public GameProfile hasJoinedServer(GameProfile user, String serverId) throws AuthenticationUnavailableException {
- Map arguments = new HashMap();
- arguments.put("username", user.getName());
- arguments.put("serverId", serverId);
- URL url = URLUtils.concatenateURL(CHECK_URL, URLUtils.buildQuery(arguments));
- try {
- HasJoinedResponse response = URLUtils.makeRequest(url, null, HasJoinedResponse.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;
- }
- }
-
- public Map getTextures(GameProfile profile, boolean requireSecure) throws PropertyException {
- Property textures = profile.getProperties().get("textures");
- if(textures != null) {
- if(!textures.hasSignature()) {
- throw new ProfileTextureException("Signature is missing from textures payload.");
- }
-
- if(!textures.isSignatureValid(SIGNATURE_KEY)) {
- throw new ProfileTextureException("Textures payload has been tampered with. (signature invalid)");
- }
-
- MinecraftTexturesPayload result;
- try {
- String json = new String(Base64.decode(textures.getValue().getBytes("UTF-8")));
- result = GSON.fromJson(json, MinecraftTexturesPayload.class);
- } catch(Exception e) {
- throw new ProfileTextureException("Could not decode texture payload.", e);
- }
-
- if(result.getProfileId() == null || !result.getProfileId().equals(profile.getId())) {
- throw new ProfileTextureException("Decrypted textures payload was for another user. (expected id " + profile.getId() + " but was for " + result.getProfileId() + ")");
- }
-
- if(result.getProfileName() == null || !result.getProfileName().equals(profile.getName())) {
- throw new ProfileTextureException("Decrypted textures payload was for another user. (expected name " + profile.getName() + " but was for " + result.getProfileName() + ")");
- }
- if(requireSecure) {
- if(result.isPublic()) {
- throw new ProfileTextureException("Decrypted textures payload was public when secure data is required.");
- }
-
- Calendar limit = Calendar.getInstance();
- limit.add(5, -1);
- Date validFrom = new Date(result.getTimestamp());
- if(validFrom.before(limit.getTime())) {
- throw new ProfileTextureException("Decrypted textures payload is too old. (" + validFrom + ", needs to be at least " + limit + ")");
- }
- }
-
- return result.getTextures() == null ? new HashMap() : result.getTextures();
- }
-
- return new HashMap();
- }
-
- public GameProfile fillProfileProperties(GameProfile profile) throws ProfileException {
- if(profile.getId() == null) {
- return profile;
- }
-
- try {
- URL url = URLUtils.constantURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDSerializer.fromUUID(profile.getId()));
- MinecraftProfilePropertiesResponse response = URLUtils.makeRequest(url, null, MinecraftProfilePropertiesResponse.class);
- if(response == null) {
- throw new ProfileNotFoundException("Couldn't fetch profile properties for " + profile + " as the profile does not exist.");
- }
-
- GameProfile result = new GameProfile(response.getId(), response.getName());
- result.getProperties().putAll(response.getProperties());
- profile.getProperties().putAll(response.getProperties());
- return result;
- } catch(AuthenticationException e) {
- throw new ProfileLookupException("Couldn't look up profile properties for " + profile, e);
- }
- }
-
- @Override
- public String toString() {
- return "SessionService{}";
- }
-
-}
diff --git a/src/main/java/org/spacehq/mc/auth/UserAuthentication.java b/src/main/java/org/spacehq/mc/auth/UserAuthentication.java
deleted file mode 100644
index 45564453..00000000
--- a/src/main/java/org/spacehq/mc/auth/UserAuthentication.java
+++ /dev/null
@@ -1,345 +0,0 @@
-package org.spacehq.mc.auth;
-
-import org.spacehq.mc.auth.exception.AuthenticationException;
-import org.spacehq.mc.auth.exception.InvalidCredentialsException;
-import org.spacehq.mc.auth.exception.PropertyDeserializeException;
-import org.spacehq.mc.auth.properties.Property;
-import org.spacehq.mc.auth.properties.PropertyMap;
-import org.spacehq.mc.auth.request.AuthenticationRequest;
-import org.spacehq.mc.auth.request.RefreshRequest;
-import org.spacehq.mc.auth.response.AuthenticationResponse;
-import org.spacehq.mc.auth.response.RefreshResponse;
-import org.spacehq.mc.auth.response.User;
-import org.spacehq.mc.util.URLUtils;
-import org.spacehq.mc.auth.serialize.UUIDSerializer;
-
-import java.net.URL;
-import java.util.*;
-
-public class UserAuthentication {
-
- private static final String BASE_URL = "https://authserver.mojang.com/";
- private static final URL ROUTE_AUTHENTICATE = URLUtils.constantURL(BASE_URL + "authenticate");
- private static final URL ROUTE_REFRESH = URLUtils.constantURL(BASE_URL + "refresh");
- private static final String STORAGE_KEY_PROFILE_NAME = "displayName";
- private static final String STORAGE_KEY_PROFILE_ID = "uuid";
- private static final String STORAGE_KEY_PROFILE_PROPERTIES = "profileProperties";
- private static final String STORAGE_KEY_USER_NAME = "username";
- private static final String STORAGE_KEY_USER_ID = "userid";
- private static final String STORAGE_KEY_USER_PROPERTIES = "userProperties";
- private static final String STORAGE_KEY_ACCESS_TOKEN = "accessToken";
-
- private String clientToken;
- private PropertyMap userProperties = new PropertyMap();
- private String userId;
- private String username;
- private String password;
- private String accessToken;
- private boolean isOnline;
- private List profiles = new ArrayList();
- private GameProfile selectedProfile;
- private UserType userType;
-
- public UserAuthentication(String clientToken) {
- if(clientToken == null) {
- throw new IllegalArgumentException("ClientToken cannot be null.");
- }
-
- this.clientToken = clientToken;
- }
-
- public String getClientToken() {
- return this.clientToken;
- }
-
- public String getUserID() {
- return this.userId;
- }
-
- public String getAccessToken() {
- return this.accessToken;
- }
-
- public List getAvailableProfiles() {
- return this.profiles;
- }
-
- public GameProfile getSelectedProfile() {
- return this.selectedProfile;
- }
-
- public UserType getUserType() {
- return this.isLoggedIn() ? (this.userType == null ? UserType.LEGACY : this.userType) : null;
- }
-
- public PropertyMap getUserProperties() {
- return this.isLoggedIn() ? new PropertyMap(this.userProperties) : new PropertyMap();
- }
-
- public boolean isLoggedIn() {
- return this.accessToken != null && !this.accessToken.equals("");
- }
-
- public boolean canPlayOnline() {
- return this.isLoggedIn() && this.getSelectedProfile() != null && this.isOnline;
- }
-
- public boolean canLogIn() {
- return !this.canPlayOnline() && this.username != null && !this.username.equals("") && ((this.password != null && !this.password.equals("")) || (this.accessToken != null && !this.accessToken.equals("")));
- }
-
- public void setUsername(String username) {
- if(this.isLoggedIn() && this.canPlayOnline()) {
- throw new IllegalStateException("Cannot change username whilst logged in & online");
- } else {
- this.username = username;
- }
- }
-
- public void setPassword(String password) {
- if(this.isLoggedIn() && this.canPlayOnline() && this.password != null && !this.password.equals("")) {
- throw new IllegalStateException("Cannot set password whilst logged in & online");
- } else {
- this.password = password;
- }
- }
-
- public void setAccessToken(String accessToken) {
- if(this.isLoggedIn() && this.canPlayOnline()) {
- throw new IllegalStateException("Cannot change accessToken whilst logged in & online");
- } else {
- this.accessToken = accessToken;
- }
- }
-
- public void loadFromStorage(Map credentials) throws PropertyDeserializeException {
- this.logout();
- this.setUsername((String) credentials.get(STORAGE_KEY_USER_NAME));
- if(credentials.containsKey(STORAGE_KEY_USER_ID)) {
- this.userId = (String) credentials.get(STORAGE_KEY_USER_ID);
- } else {
- this.userId = this.username;
- }
-
- if(credentials.containsKey(STORAGE_KEY_USER_PROPERTIES)) {
- try {
- List