authentication reconstruction

This commit is contained in:
huanghongxun
2018-02-22 14:12:55 +08:00
parent 6e202341ad
commit 6c4231a6b2
58 changed files with 880 additions and 982 deletions

View File

@@ -17,8 +17,6 @@
*/
package org.jackhuang.hmcl.auth;
import java.net.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -33,6 +31,12 @@ public abstract class Account {
*/
public abstract String getUsername();
/**
*
* @return the character name
*/
public abstract String getCharacter();
/**
* @return the UUID
*/
@@ -40,28 +44,12 @@ public abstract class Account {
/**
* log in.
* @param selector selects a character
* @return the specific player's info.
* @throws AuthenticationException if server error occurred.
*/
public AuthInfo logIn(MultiCharacterSelector selector) throws AuthenticationException {
return logIn(selector, Proxy.NO_PROXY);
}
public abstract AuthInfo logIn() throws AuthenticationException;
/**
* log in.
* @param selector selects a character
* @param proxy by which connect to the server
* @return the specific player's info.
* @throws AuthenticationException if server error occurred.
*/
public abstract AuthInfo logIn(MultiCharacterSelector selector, Proxy proxy) throws AuthenticationException;
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password) throws AuthenticationException {
return logInWithPassword(selector, password, Proxy.NO_PROXY);
}
public abstract AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException;
public abstract AuthInfo logInWithPassword(String password) throws AuthenticationException;
public abstract boolean canPlayOffline();
@@ -73,24 +61,7 @@ public abstract class Account {
public abstract void logOut();
protected abstract Map<Object, Object> toStorageImpl();
public final Map<Object, Object> toStorage() {
Map<Object, Object> storage = toStorageImpl();
if (!getProperties().isEmpty())
storage.put("properties", getProperties());
return storage;
}
private final Map<Object, Object> properties = new HashMap<>();
/**
* To save some necessary extra information here.
* @return the property map.
*/
public final Map<Object, Object> getProperties() {
return properties;
}
public abstract Map<Object, Object> toStorage();
public abstract void clearCache();
}

View File

@@ -17,8 +17,7 @@
*/
package org.jackhuang.hmcl.auth;
import org.jackhuang.hmcl.util.Lang;
import java.net.Proxy;
import java.util.Map;
/**
@@ -27,23 +26,7 @@ import java.util.Map;
*/
public abstract class AccountFactory<T extends Account> {
public final T fromUsername(String username) {
return fromUsername(username, "", null);
}
public abstract T create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException;
public final T fromUsername(String username, String password) {
return fromUsername(username, password, null);
}
public abstract T fromUsername(String username, String password, Object additionalData);
protected abstract T fromStorageImpl(Map<Object, Object> storage);
public final T fromStorage(Map<Object, Object> storage) {
T account = fromStorageImpl(storage);
Map<?, ?> properties = Lang.get(storage, "properties", Map.class).orElse(null);
if (properties == null) return account;
account.getProperties().putAll(properties);
return account;
}
public abstract T fromStorage(Map<Object, Object> storage, Proxy proxy);
}

View File

@@ -17,10 +17,8 @@
*/
package org.jackhuang.hmcl.auth;
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
/**
*
@@ -31,42 +29,32 @@ public final class AuthInfo {
private final String username;
private final String userId;
private final String authToken;
private final String accessToken;
private final UserType userType;
private final String userProperties;
private final String userPropertyMap;
private final Arguments arguments;
public AuthInfo(String username, String userId, String authToken) {
this(username, userId, authToken, UserType.LEGACY);
public AuthInfo(String username, String userId, String accessToken) {
this(username, userId, accessToken, UserType.LEGACY);
}
public AuthInfo(String username, String userId, String authToken, UserType userType) {
this(username, userId, authToken, userType, "{}");
public AuthInfo(String username, String userId, String accessToken, UserType userType) {
this(username, userId, accessToken, userType, "{}");
}
public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties) {
this(username, userId, authToken, userType, userProperties, "{}");
public AuthInfo(String username, String userId, String accessToken, UserType userType, String userProperties) {
this(username, userId, accessToken, userType, userProperties, null);
}
public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties, String userPropertyMap) {
this(username, userId, authToken, userType, userProperties, userPropertyMap, null);
}
public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties, String userPropertyMap, Arguments arguments) {
public AuthInfo(String username, String userId, String accessToken, UserType userType, String userProperties, Arguments arguments) {
this.username = username;
this.userId = userId;
this.authToken = authToken;
this.accessToken = accessToken;
this.userType = userType;
this.userProperties = userProperties;
this.userPropertyMap = userPropertyMap;
this.arguments = arguments;
}
public AuthInfo(GameProfile profile, String authToken, UserType userType, String userProperties) {
this(profile.getName(), UUIDTypeAdapter.fromUUID(profile.getId()), authToken, userType, userProperties);
}
public String getUsername() {
return username;
}
@@ -75,8 +63,8 @@ public final class AuthInfo {
return userId;
}
public String getAuthToken() {
return authToken;
public String getAccessToken() {
return accessToken;
}
public UserType getUserType() {
@@ -93,21 +81,11 @@ public final class AuthInfo {
return userProperties;
}
/**
* Properties of this user.
* Don't know the difference between user properties and user property map.
*
* @return the user property map in JSON.
*/
public String getUserPropertyMap() {
return userPropertyMap;
}
public Arguments getArguments() {
return arguments;
}
public AuthInfo setArguments(Arguments arguments) {
return new AuthInfo(username, userId, authToken, userType, userProperties, userPropertyMap, arguments);
return new AuthInfo(username, userId, accessToken, userType, userProperties, arguments);
}
}

View File

@@ -25,7 +25,7 @@ import java.util.List;
* This interface is for your application to open a GUI for user to choose the character
* when a having-multi-character yggdrasil account is being logging in..
*/
public interface MultiCharacterSelector {
public interface CharacterSelector {
/**
* Select one of {@code names} GameProfiles to login.
@@ -35,5 +35,5 @@ public interface MultiCharacterSelector {
*/
GameProfile select(Account account, List<GameProfile> names) throws NoSelectedCharacterException;
MultiCharacterSelector DEFAULT = (account, names) -> names.stream().findFirst().orElseThrow(() -> new NoSelectedCharacterException(account));
CharacterSelector DEFAULT = (account, names) -> names.stream().findFirst().orElseThrow(() -> new NoSelectedCharacterException(account));
}

View File

@@ -22,14 +22,4 @@ package org.jackhuang.hmcl.auth;
* @author huangyuhui
*/
public final class InvalidCredentialsException extends AuthenticationException {
private final Account account;
public InvalidCredentialsException(Account account) {
this.account = account;
}
public Account getAccount() {
return account;
}
}

View File

@@ -21,15 +21,4 @@ package org.jackhuang.hmcl.auth;
* throws if wrong password.
*/
public class InvalidPasswordException extends AuthenticationException {
private final Account account;
public InvalidPasswordException(Account account) {
super();
this.account = account;
}
public Account getAccount() {
return account;
}
}

View File

@@ -17,22 +17,9 @@
*/
package org.jackhuang.hmcl.auth;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
/**
*
* @author huangyuhui
*/
public class InvalidTokenException extends AuthenticationException {
private final YggdrasilAccount account;
public InvalidTokenException(YggdrasilAccount account) {
super();
this.account = account;
}
public YggdrasilAccount getAccount() {
return account;
}
}

View File

@@ -18,10 +18,10 @@
package org.jackhuang.hmcl.auth;
/**
* This exception gets threw when a monitor of {@link MultiCharacterSelector} cannot select a
* This exception gets threw when a monitor of {@link CharacterSelector} cannot select a
* valid character.
*
* @see org.jackhuang.hmcl.auth.MultiCharacterSelector
* @see CharacterSelector
* @author huangyuhui
*/
public final class NoSelectedCharacterException extends AuthenticationException {

View File

@@ -0,0 +1,18 @@
package org.jackhuang.hmcl.auth;
public class ServerResponseMalformedException extends AuthenticationException {
public ServerResponseMalformedException() {
}
public ServerResponseMalformedException(String message) {
super(message);
}
public ServerResponseMalformedException(String message, Throwable cause) {
super(message, cause);
}
public ServerResponseMalformedException(Throwable cause) {
super(cause);
}
}

View File

@@ -24,7 +24,7 @@ import java.util.List;
/**
* Select character by name.
*/
public class SpecificCharacterSelector implements MultiCharacterSelector {
public class SpecificCharacterSelector implements CharacterSelector {
private final String id;
/**

View File

@@ -15,18 +15,20 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
package org.jackhuang.hmcl.auth.authlibinjector;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.MultiCharacterSelector;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.util.ExceptionalSupplier;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.NetworkUtils;
import java.net.Proxy;
import java.util.Base64;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -35,21 +37,30 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
private final String serverBaseURL;
private final ExceptionalSupplier<String, ?> injectorJarPath;
public AuthlibInjectorAccount(ExceptionalSupplier<String, ?> injectorJarPath, String serverBaseURL, String username) {
super(serverBaseURL + "authserver/", serverBaseURL + "sessionserver/", username);
protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier<String, ?> injectorJarPath, String username, String clientToken, String character, YggdrasilSession session) {
super(service, username, clientToken, character, session);
this.injectorJarPath = injectorJarPath;
this.serverBaseURL = serverBaseURL;
}
@Override
public AuthInfo logIn(MultiCharacterSelector selector, Proxy proxy) throws AuthenticationException {
public AuthInfo logIn() throws AuthenticationException {
return inject(super::logIn);
}
@Override
protected AuthInfo logInWithPassword(String password, CharacterSelector selector) throws AuthenticationException {
return inject(() -> super.logInWithPassword(password, selector));
}
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> supplier) throws AuthenticationException {
// Authlib Injector recommends launchers to pre-fetch the server basic information before launched the game to save time.
GetTask getTask = new GetTask(NetworkUtils.toURL(serverBaseURL));
AtomicBoolean flag = new AtomicBoolean(true);
Thread thread = Lang.thread(() -> flag.set(getTask.test()));
AuthInfo info = super.logIn(selector, proxy);
AuthInfo info = supplier.get();
try {
thread.join();
@@ -66,32 +77,9 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
}
@Override
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException {
// Authlib Injector recommends launchers to pre-fetch the server basic information before launched the game to save time.
GetTask getTask = new GetTask(NetworkUtils.toURL(serverBaseURL));
AtomicBoolean flag = new AtomicBoolean(true);
Thread thread = Lang.thread(() -> flag.set(getTask.test()));
AuthInfo info = super.logInWithPassword(selector, password, proxy);
try {
thread.join();
String arg = "-javaagent:" + injectorJarPath.get() + "=" + serverBaseURL;
Arguments arguments = Arguments.addJVMArguments(null, arg);
if (flag.get())
arguments = Arguments.addJVMArguments(arguments, "-Dorg.to2mbn.authlibinjector.config.prefetched=" + new String(Base64.getEncoder().encode(getTask.getResult().getBytes())));
return info.setArguments(arguments);
} catch (Exception e) {
throw new AuthenticationException("Unable to get authlib injector jar path", e);
}
}
@Override
public Map<Object, Object> toStorageImpl() {
Map<Object, Object> map = super.toStorageImpl();
map.put(STORAGE_KEY_SERVER_BASE_URL, serverBaseURL);
public Map<Object, Object> toStorage() {
Map<Object, Object> map = super.toStorage();
map.put("serverBaseURL", serverBaseURL);
return map;
}
@@ -99,5 +87,4 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
return serverBaseURL;
}
public static final String STORAGE_KEY_SERVER_BASE_URL = "serverBaseURL";
}

View File

@@ -0,0 +1,58 @@
package org.jackhuang.hmcl.auth.authlibinjector;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
import org.jackhuang.hmcl.util.ExceptionalSupplier;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import java.net.Proxy;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjectorAccount> {
private final ExceptionalSupplier<String, ?> injectorJarPathSupplier;
public AuthlibInjectorAccountFactory(ExceptionalSupplier<String, ?> injectorJarPathSupplier) {
this.injectorJarPathSupplier = injectorJarPathSupplier;
}
@Override
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object serverBaseURL, Proxy proxy) throws AuthenticationException {
Objects.requireNonNull(selector);
Objects.requireNonNull(username);
Objects.requireNonNull(password);
Objects.requireNonNull(proxy);
if (!(serverBaseURL instanceof String) || !NetworkUtils.isURL((String) serverBaseURL))
throw new IllegalArgumentException("Additional data should be server base url string for authlib injector accounts.");
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider((String) serverBaseURL), proxy),
(String) serverBaseURL, injectorJarPathSupplier, username, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), null, null);
account.logInWithPassword(password, selector);
return account;
}
@Override
public AuthlibInjectorAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
Objects.requireNonNull(storage);
Objects.requireNonNull(proxy);
String username = Lang.get(storage, "username", String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have username"));
String clientToken = Lang.get(storage, "clientToken", String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have client token."));
String character = Lang.get(storage, "clientToken", String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have selected character name."));
String apiRoot = Lang.get(storage, "serverBaseURL", String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have API root."));
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(apiRoot), proxy),
apiRoot, injectorJarPathSupplier, username, clientToken, character, YggdrasilSession.fromStorage(storage));
}
}

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
package org.jackhuang.hmcl.auth.authlibinjector;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.Immutable;

View File

@@ -0,0 +1,42 @@
package org.jackhuang.hmcl.auth.authlibinjector;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilProvider;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import java.net.URL;
import java.util.UUID;
public class AuthlibInjectorProvider implements YggdrasilProvider {
private final String apiRoot;
public AuthlibInjectorProvider(String apiRoot) {
this.apiRoot = apiRoot;
}
@Override
public URL getAuthenticationURL() {
return NetworkUtils.toURL(apiRoot + "authserver/authenticate");
}
@Override
public URL getRefreshmentURL() {
return NetworkUtils.toURL(apiRoot + "authserver/refresh");
}
@Override
public URL getValidationURL() {
return NetworkUtils.toURL(apiRoot + "authserver/validate");
}
@Override
public URL getInvalidationURL() {
return NetworkUtils.toURL(apiRoot + "authserver/invalidate");
}
@Override
public URL getProfilePropertiesURL(UUID uuid) {
return NetworkUtils.toURL(apiRoot + "sessionserver/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid));
}
}

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
package org.jackhuang.hmcl.auth.authlibinjector;
public class AuthlibInjectorServerInfo {
private final String serverIp;

View File

@@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
package org.jackhuang.hmcl.auth.authlibinjector;
public class AuthlibInjectorServerResponse {

View File

@@ -15,14 +15,16 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth;
package org.jackhuang.hmcl.auth.offline;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import java.net.Proxy;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@@ -58,7 +60,12 @@ public class OfflineAccount extends Account {
}
@Override
public AuthInfo logIn(MultiCharacterSelector selector, Proxy proxy) throws AuthenticationException {
public String getCharacter() {
return username;
}
@Override
public AuthInfo logIn() throws AuthenticationException {
if (StringUtils.isBlank(username) || StringUtils.isBlank(uuid))
throw new AuthenticationException("Username cannot be empty");
@@ -66,8 +73,8 @@ public class OfflineAccount extends Account {
}
@Override
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException {
return logIn(selector, proxy);
public AuthInfo logInWithPassword(String password) throws AuthenticationException {
return logIn();
}
@Override
@@ -86,7 +93,7 @@ public class OfflineAccount extends Account {
}
@Override
public Map<Object, Object> toStorageImpl() {
public Map<Object, Object> toStorage() {
return Lang.mapOf(
new Pair<>("uuid", uuid),
new Pair<>("username", username)

View File

@@ -15,10 +15,14 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth;
package org.jackhuang.hmcl.auth.offline;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.Lang;
import java.net.Proxy;
import java.util.Map;
/**
@@ -32,21 +36,18 @@ public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
}
@Override
public OfflineAccount fromUsername(String username, String password, Object additionalData) {
public OfflineAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) {
return new OfflineAccount(username, getUUIDFromUserName(username));
}
@Override
public OfflineAccount fromStorageImpl(Map<Object, Object> storage) {
Object username = storage.get("username");
if (username == null || !(username instanceof String))
throw new IllegalStateException("Offline account configuration malformed.");
public OfflineAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
String username = Lang.get(storage, "username", String.class)
.orElseThrow(() -> new IllegalStateException("Offline account configuration malformed."));
String uuid = Lang.get(storage, "uuid", String.class)
.orElse(getUUIDFromUserName(username));
Object uuid = storage.get("uuid");
if (uuid == null || !(uuid instanceof String))
uuid = getUUIDFromUserName((String) username);
return new OfflineAccount((String) username, (String) uuid);
return new OfflineAccount(username, uuid);
}
private static String getUUIDFromUserName(String username) {

View File

@@ -1,74 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair;
import java.util.Map;
/**
*
* @author huangyuhui
*/
public final class AuthenticationRequest {
/**
* The user name of Minecraft account.
*/
private final String username;
/**
* The password of Minecraft account.
*/
private final String password;
/**
* The client token of this game.
*/
private final String clientToken;
private final Map<String, Object> agent = Lang.mapOf(
new Pair<>("name", "minecraft"),
new Pair<>("version", 1));
private final boolean requestUser = true;
public AuthenticationRequest(String username, String password, String clientToken) {
this.username = username;
this.password = password;
this.clientToken = clientToken;
}
public String getUsername() {
return username;
}
public String getClientToken() {
return clientToken;
}
public Map<String, Object> getAgent() {
return agent;
}
public boolean isRequestUser() {
return requestUser;
}
}

View File

@@ -21,30 +21,26 @@ package org.jackhuang.hmcl.auth.yggdrasil;
*
* @author huangyuhui
*/
public final class AuthenticationResponse {
final class AuthenticationResponse extends ErrorResponse {
private final String accessToken;
private final String clientToken;
private final GameProfile selectedProfile;
private final GameProfile[] availableProfiles;
private final User user;
private final String error;
private final String errorMessage;
private final String cause;
public AuthenticationResponse() {
this(null, null, null, null, null, null, null, null);
}
public AuthenticationResponse(String accessToken, String clientToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user, String error, String errorMessage, String cause) {
super(error, errorMessage, cause);
this.accessToken = accessToken;
this.clientToken = clientToken;
this.selectedProfile = selectedProfile;
this.availableProfiles = availableProfiles;
this.user = user;
this.error = error;
this.errorMessage = errorMessage;
this.cause = cause;
}
public String getAccessToken() {
@@ -67,16 +63,4 @@ public final class AuthenticationResponse {
return user;
}
public String getError() {
return error;
}
public String getErrorMessage() {
return errorMessage;
}
public String getCause() {
return cause;
}
}

View File

@@ -1,59 +0,0 @@
package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.util.ExceptionalSupplier;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorAccount.STORAGE_KEY_SERVER_BASE_URL;
import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*;
public class AuthlibInjectorAccountFactory extends AccountFactory<YggdrasilAccount> {
private final ExceptionalSupplier<String, ?> injectorJarPathSupplier;
public AuthlibInjectorAccountFactory(ExceptionalSupplier<String, ?> injectorJarPathSupplier) {
this.injectorJarPathSupplier = injectorJarPathSupplier;
}
@Override
public AuthlibInjectorAccount fromUsername(String username, String password, Object additionalData) {
if (!(additionalData instanceof String) || !NetworkUtils.isURL((String) additionalData))
throw new IllegalArgumentException("Additional data should be server base url string for authlib injector accounts.");
AuthlibInjectorAccount account = new AuthlibInjectorAccount(injectorJarPathSupplier, (String) additionalData, username);
account.setPassword(password);
return account;
}
@Override
public AuthlibInjectorAccount fromStorageImpl(Map<Object, Object> storage) {
String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME));
String serverBaseURL = Lang.get(storage, STORAGE_KEY_SERVER_BASE_URL, String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_SERVER_BASE_URL));
AuthlibInjectorAccount account = new AuthlibInjectorAccount(injectorJarPathSupplier, serverBaseURL, username);
account.setUserId(Lang.get(storage, STORAGE_KEY_USER_ID, String.class).orElse(username));
account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class).orElse(null));
account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_CLIENT_TOKEN)));
Lang.get(storage, STORAGE_KEY_USER_PROPERTIES, List.class)
.ifPresent(account.getUserProperties()::fromList);
Optional<String> profileId = Lang.get(storage, STORAGE_KEY_PROFILE_ID, String.class);
Optional<String> profileName = Lang.get(storage, STORAGE_KEY_PROFILE_NAME, String.class);
GameProfile profile = null;
if (profileId.isPresent() && profileName.isPresent()) {
profile = new GameProfile(UUIDTypeAdapter.fromString(profileId.get()), profileName.get());
Lang.get(storage, STORAGE_KEY_PROFILE_PROPERTIES, List.class)
.ifPresent(profile.getProperties()::fromList);
}
account.setSelectedProfile(profile);
return account;
}
}

View File

@@ -0,0 +1,29 @@
package org.jackhuang.hmcl.auth.yggdrasil;
class ErrorResponse {
private final String error;
private final String errorMessage;
private final String cause;
public ErrorResponse() {
this(null, null, null);
}
public ErrorResponse(String error, String errorMessage, String cause) {
this.error = error;
this.errorMessage = errorMessage;
this.cause = cause;
}
public String getError() {
return error;
}
public String getErrorMessage() {
return errorMessage;
}
public String getCause() {
return cause;
}
}

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.auth.yggdrasil;
import com.google.gson.*;
import org.jackhuang.hmcl.auth.UserType;
import org.jackhuang.hmcl.util.Immutable;
import java.lang.reflect.Type;
@@ -40,7 +41,11 @@ public final class GameProfile {
}
public GameProfile(UUID id, String name) {
this(id, name, new PropertyMap(), false);
this(id, name, new PropertyMap());
}
public GameProfile(UUID id, String name, PropertyMap properties) {
this(id, name, properties, false);
}
public GameProfile(UUID id, String name, PropertyMap properties, boolean legacy) {
@@ -66,6 +71,10 @@ public final class GameProfile {
return legacy;
}
public UserType getUserType() {
return UserType.fromLegacy(isLegacy());
}
public static class Serializer implements JsonSerializer<GameProfile>, JsonDeserializer<GameProfile> {
public static final Serializer INSTANCE = new Serializer();

View File

@@ -0,0 +1,36 @@
package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import java.net.URL;
import java.util.UUID;
public class MojangYggdrasilProvider implements YggdrasilProvider {
public static final MojangYggdrasilProvider INSTANCE = new MojangYggdrasilProvider();
@Override
public URL getAuthenticationURL() {
return NetworkUtils.toURL("https://authserver.mojang.com/authenticate");
}
@Override
public URL getRefreshmentURL() {
return NetworkUtils.toURL("https://authserver.mojang.com/refresh");
}
@Override
public URL getValidationURL() {
return NetworkUtils.toURL("https://authserver.mojang.com/validate");
}
@Override
public URL getInvalidationURL() {
return NetworkUtils.toURL("https://authserver.mojang.com/invalidate");
}
@Override
public URL getProfilePropertiesURL(UUID uuid) {
return NetworkUtils.toURL("https://sessionserver.mojang.com/session/minecraft/profile/" + UUIDTypeAdapter.fromUUID(uuid));
}
}

View File

@@ -1,46 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
public class ProfileResponse {
private final String id;
private final String name;
private final PropertyMap properties;
public ProfileResponse() {
this("", "", null);
}
public ProfileResponse(String id, String name, PropertyMap properties) {
this.id = id;
this.name = name;
this.properties = properties;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public PropertyMap getProperties() {
return properties;
}
}

View File

@@ -1,37 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
public class Property {
private final String name;
private final String value;
public Property(String name, String value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
}

View File

@@ -18,33 +18,20 @@
package org.jackhuang.hmcl.auth.yggdrasil;
import com.google.gson.*;
import org.jackhuang.hmcl.util.Lang;
import java.lang.reflect.Type;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
public final class PropertyMap extends HashMap<String, Property> {
public final class PropertyMap extends HashMap<String, String> {
public List<Map<String, String>> toList() {
List<Map<String, String>> properties = new ArrayList<>();
for (Property profileProperty : values()) {
Map<String, String> property = new HashMap<>();
property.put("name", profileProperty.getName());
property.put("value", profileProperty.getValue());
properties.add(property);
}
return properties;
}
public void fromList(List<?> list) {
for (Object propertyMap : list) {
if (!(propertyMap instanceof Map<?, ?>))
continue;
Optional<String> name = Lang.get((Map<?, ?>) propertyMap, "name", String.class);
Optional<String> value = Lang.get((Map<?, ?>) propertyMap, "value", String.class);
if (name.isPresent() && value.isPresent())
put(name.get(), new Property(name.get(), value.get()));
public static PropertyMap fromMap(Map<?, ?> map) {
PropertyMap propertyMap = new PropertyMap();
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getKey() instanceof String && entry.getValue() instanceof String)
propertyMap.put((String) entry.getKey(), (String) entry.getValue());
}
return propertyMap;
}
public static class Serializer implements JsonSerializer<PropertyMap>, JsonDeserializer<PropertyMap> {
@@ -57,19 +44,11 @@ public final class PropertyMap extends HashMap<String, Property> {
@Override
public PropertyMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
PropertyMap result = new PropertyMap();
if (json instanceof JsonObject) {
for (Map.Entry<String, JsonElement> entry : ((JsonObject) json).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();
result.put(name, new Property(name, value));
}
for (JsonElement element : json.getAsJsonArray())
if (element instanceof JsonObject) {
JsonObject object = (JsonObject) element;
result.put(object.get("name").getAsString(), object.get("value").getAsString());
}
return result;
}
@@ -77,29 +56,14 @@ public final class PropertyMap extends HashMap<String, Property> {
@Override
public JsonElement serialize(PropertyMap src, Type typeOfSrc, JsonSerializationContext context) {
JsonArray result = new JsonArray();
for (Property property : src.values()) {
for (Map.Entry<String, String> entry : src.entrySet()) {
JsonObject object = new JsonObject();
object.addProperty("name", property.getName());
object.addProperty("value", property.getValue());
object.addProperty("name", entry.getKey());
object.addProperty("value", entry.getValue());
result.add(object);
}
return result;
}
}
public static class LegacySerializer
implements JsonSerializer<PropertyMap> {
@Override
public JsonElement serialize(PropertyMap src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
for (PropertyMap.Entry<String, Property> entry : src.entrySet()) {
JsonArray values = new JsonArray();
values.add(new JsonPrimitive(entry.getValue().getValue()));
result.add(entry.getKey(), values);
}
return result;
}
}
}

View File

@@ -1,62 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
/**
*
* @author huang
*/
public final class RefreshRequest {
private final String accessToken;
private final String clientToken;
private final GameProfile selectedProfile;
private final boolean requestUser;
public RefreshRequest(String accessToken, String clientToken) {
this(accessToken, clientToken, null);
}
public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile) {
this(accessToken, clientToken, selectedProfile, true);
}
public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile, boolean requestUser) {
this.accessToken = accessToken;
this.clientToken = clientToken;
this.selectedProfile = selectedProfile;
this.requestUser = requestUser;
}
public String getAccessToken() {
return accessToken;
}
public String getClientToken() {
return clientToken;
}
public GameProfile getSelectedProfile() {
return selectedProfile;
}
public boolean isRequestUser() {
return requestUser;
}
}

View File

@@ -0,0 +1,40 @@
package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.auth.AuthenticationException;
public class RemoteAuthenticationException extends AuthenticationException {
private final String name;
private final String message;
private final String cause;
public RemoteAuthenticationException(String name, String message, String cause) {
super(buildMessage(name, message, cause));
this.name = name;
this.message = message;
this.cause = cause;
}
public String getRemoteName() {
return name;
}
public String getRemoteMessage() {
return message;
}
public String getRemoteCause() {
return cause;
}
private static String buildMessage(String name, String message, String cause) {
StringBuilder builder = new StringBuilder(name);
if (message != null)
builder.append(": ").append(message);
if (cause != null)
builder.append(": ").append(cause);
return builder.toString();
}
}

View File

@@ -22,16 +22,16 @@ import org.jackhuang.hmcl.util.Immutable;
import java.util.Map;
@Immutable
public final class ProfileTexture {
public final class Texture {
private final String url;
private final Map<String, String> metadata;
public ProfileTexture() {
public Texture() {
this(null, null);
}
public ProfileTexture(String url, Map<String, String> metadata) {
public Texture(String url, Map<String, String> metadata) {
this.url = url;
this.metadata = metadata;
}
@@ -46,8 +46,4 @@ public final class ProfileTexture {
else
return metadata.get(key);
}
public enum Type {
SKIN, CAPE
}
}

View File

@@ -27,13 +27,13 @@ import java.util.UUID;
public final class TextureResponse {
private final UUID profileId;
private final String profileName;
private final Map<ProfileTexture.Type, ProfileTexture> textures;
private final Map<TextureType, Texture> textures;
public TextureResponse() {
this(UUID.randomUUID(), "", Collections.emptyMap());
}
public TextureResponse(UUID profileId, String profileName, Map<ProfileTexture.Type, ProfileTexture> textures) {
public TextureResponse(UUID profileId, String profileName, Map<TextureType, Texture> textures) {
this.profileId = profileId;
this.profileName = profileName;
this.textures = textures;
@@ -47,7 +47,7 @@ public final class TextureResponse {
return profileName;
}
public Map<ProfileTexture.Type, ProfileTexture> getTextures() {
public Map<TextureType, Texture> getTextures() {
return textures == null ? null : Collections.unmodifiableMap(textures);
}
}

View File

@@ -0,0 +1,5 @@
package org.jackhuang.hmcl.auth.yggdrasil;
public enum TextureType {
SKIN, CAPE
}

View File

@@ -1,42 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.yggdrasil;
/**
*
* @author huangyuhui
*/
public final class ValidateRequest {
private final String accessToken;
private final String clientToken;
public ValidateRequest(String accessToken, String clientToken) {
this.accessToken = accessToken;
this.clientToken = clientToken;
}
public String getAccessToken() {
return accessToken;
}
public String getClientToken() {
return clientToken;
}
}

View File

@@ -17,46 +17,34 @@
*/
package org.jackhuang.hmcl.auth.yggdrasil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.util.Charsets;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.util.*;
/**
*
* @author huang
* @author huangyuhui
*/
public class YggdrasilAccount extends Account {
private final String username;
private String password;
private String userId;
private String accessToken = null;
private String clientToken = randomToken();
private final YggdrasilService service;
private boolean isOnline = false;
private PropertyMap userProperties = new PropertyMap();
private GameProfile selectedProfile = null;
private GameProfile[] profiles;
private UserType userType = UserType.LEGACY;
private YggdrasilSession session;
private final String clientToken;
private String character;
public YggdrasilAccount(String baseAuthServer, String baseSessionServer, String username) {
this.baseAuthServer = baseAuthServer;
this.baseSessionServer = baseSessionServer;
this.baseProfile = baseSessionServer + "session/minecraft/profile/";
protected YggdrasilAccount(YggdrasilService service, String username, String clientToken, String character, YggdrasilSession session) {
this.service = service;
this.username = username;
this.session = session;
this.clientToken = clientToken;
this.character = character;
this.routeAuthenticate = NetworkUtils.toURL(baseAuthServer + "authenticate");
this.routeRefresh = NetworkUtils.toURL(baseAuthServer + "refresh");
this.routeValidate = NetworkUtils.toURL(baseAuthServer + "validate");
if (session == null || session.getSelectedProfile() == null || StringUtils.isBlank(session.getAccessToken()))
this.session = null;
}
@Override
@@ -64,146 +52,68 @@ public class YggdrasilAccount extends Account {
return username;
}
void setPassword(String password) {
this.password = password;
}
public String getCurrentCharacterName() {
return userId;
}
void setUserId(String userId) {
this.userId = userId;
}
void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
String getClientToken() {
return clientToken;
}
void setClientToken(String clientToken) {
this.clientToken = clientToken;
}
PropertyMap getUserProperties() {
return userProperties;
}
public GameProfile getSelectedProfile() {
return selectedProfile;
}
void setSelectedProfile(GameProfile selectedProfile) {
this.selectedProfile = selectedProfile;
@Override
public String getCharacter() {
return session.getSelectedProfile().getName();
}
public boolean isLoggedIn() {
return StringUtils.isNotBlank(accessToken);
return session != null && StringUtils.isNotBlank(session.getAccessToken());
}
public boolean canPlayOnline() {
return isLoggedIn() && selectedProfile != null && isOnline;
}
public boolean canLogIn() {
return !canPlayOnline() && StringUtils.isNotBlank(username)
&& (StringUtils.isNotBlank(password) || StringUtils.isNotBlank(accessToken));
return isLoggedIn() && session.getSelectedProfile() != null && isOnline;
}
@Override
public AuthInfo logIn(MultiCharacterSelector selector, Proxy proxy) throws AuthenticationException {
if (canPlayOnline())
return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties));
else {
logIn0(proxy);
if (!isLoggedIn())
throw new AuthenticationException("Wrong password for account " + username);
if (selectedProfile == null) {
if (profiles == null || profiles.length <= 0)
throw new NoCharacterException(this);
selectedProfile = selector.select(this, Arrays.asList(profiles));
}
return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties));
public AuthInfo logIn() throws AuthenticationException {
if (!canPlayOnline()) {
logInWithToken();
selectProfile(new SpecificCharacterSelector(character));
}
return toAuthInfo();
}
@Override
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException {
logInWithPassword0(password, proxy);
if (!isLoggedIn())
throw new AuthenticationException("Wrong password for account " + username);
public final AuthInfo logInWithPassword(String password) throws AuthenticationException {
return logInWithPassword(password, new SpecificCharacterSelector(character));
}
if (selectedProfile == null) {
if (profiles == null || profiles.length <= 0)
protected AuthInfo logInWithPassword(String password, CharacterSelector selector) throws AuthenticationException {
session = service.authenticate(username, password, clientToken);
selectProfile(selector);
return toAuthInfo();
}
private void selectProfile(CharacterSelector selector) throws AuthenticationException {
if (session.getSelectedProfile() == null) {
if (session.getAvailableProfiles() == null || session.getAvailableProfiles().length <= 0)
throw new NoCharacterException(this);
selectedProfile = selector.select(this, Arrays.asList(profiles));
session.setSelectedProfile(selector.select(this, Arrays.asList(session.getAvailableProfiles())));
}
return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties));
character = session.getSelectedProfile().getName();
}
private void logIn0(Proxy proxy) throws AuthenticationException {
if (StringUtils.isNotBlank(accessToken)) {
logInWithToken(proxy);
} else if (StringUtils.isNotBlank(password))
logInWithPassword0(password, proxy);
else
throw new AuthenticationException("Password cannot be blank");
}
private void logInWithToken(Proxy proxy) throws AuthenticationException {
if (StringUtils.isBlank(userId))
if (StringUtils.isNotBlank(username))
userId = username;
else
throw new AuthenticationException("Invalid uuid and username");
if (checkTokenValidity(proxy)) {
private void logInWithToken() throws AuthenticationException {
if (service.validate(session.getAccessToken(), clientToken)) {
isOnline = true;
return;
}
logIn1(routeRefresh, new RefreshRequest(accessToken, clientToken, getSelectedProfile()), proxy);
session = service.refresh(session.getAccessToken(), clientToken);
}
public void logInWithPassword0(String password, Proxy proxy) throws AuthenticationException {
logIn1(routeAuthenticate, new AuthenticationRequest(username, password, clientToken), proxy);
}
private AuthInfo toAuthInfo() {
GameProfile profile = session.getSelectedProfile();
private void logIn1(URL url, Object input, Proxy proxy) throws AuthenticationException {
AuthenticationResponse response = makeRequest(url, input, proxy)
.orElseThrow(() -> new AuthenticationException("Server response empty"));
if (!clientToken.equals(response.getClientToken()))
throw new AuthenticationException("Client token changed");
if (response.getSelectedProfile() != null)
userType = UserType.fromLegacy(response.getSelectedProfile().isLegacy());
else if (response.getAvailableProfiles() != null && response.getAvailableProfiles().length > 0)
userType = UserType.fromLegacy(response.getAvailableProfiles()[0].isLegacy());
User user = response.getUser();
if (user == null || user.getId() == null)
userId = null;
else
userId = user.getId();
isOnline = true;
profiles = response.getAvailableProfiles();
selectedProfile = response.getSelectedProfile();
userProperties.clear();
accessToken = response.getAccessToken();
if (user != null && user.getProperties() != null)
userProperties.putAll(user.getProperties());
return new AuthInfo(profile.getName(), UUIDTypeAdapter.fromUUID(profile.getId()), session.getAccessToken(), profile.getUserType(),
YggdrasilService.GSON.toJson(Optional.ofNullable(session.getUser()).map(User::getProperties).orElseGet(PropertyMap::new)));
}
@Override
public boolean canPlayOffline() {
return isLoggedIn() && getSelectedProfile() != null && !canPlayOnline();
return isLoggedIn() && session.getSelectedProfile() != null && !canPlayOnline();
}
@Override
@@ -211,117 +121,51 @@ public class YggdrasilAccount extends Account {
if (!canPlayOffline())
throw new IllegalStateException("Current account " + this + " cannot play offline.");
return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties));
return toAuthInfo();
}
@Override
public void logOut() {
password = null;
userId = null;
accessToken = null;
isOnline = false;
userProperties.clear();
profiles = null;
selectedProfile = null;
session = null;
}
@Override
public Map<Object, Object> toStorageImpl() {
HashMap<Object, Object> result = new HashMap<>();
public Map<Object, Object> toStorage() {
HashMap<Object, Object> storage = new HashMap<>();
result.put(STORAGE_KEY_USER_NAME, getUsername());
result.put(STORAGE_KEY_CLIENT_TOKEN, getClientToken());
if (getCurrentCharacterName() != null)
result.put(STORAGE_KEY_USER_ID, getCurrentCharacterName());
if (!userProperties.isEmpty())
result.put(STORAGE_KEY_USER_PROPERTIES, userProperties.toList());
GameProfile profile = selectedProfile;
if (profile != null && profile.getName() != null && profile.getId() != null) {
result.put(STORAGE_KEY_PROFILE_NAME, profile.getName());
result.put(STORAGE_KEY_PROFILE_ID, profile.getId());
storage.put("username", getUsername());
storage.put("clientToken", clientToken);
storage.put("character", character);
if (session != null)
storage.putAll(session.toStorage());
if (!profile.getProperties().isEmpty())
result.put(STORAGE_KEY_PROFILE_PROPERTIES, profile.getProperties().toList());
}
if (StringUtils.isNotBlank(accessToken))
result.put(STORAGE_KEY_ACCESS_TOKEN, accessToken);
return result;
}
private Optional<AuthenticationResponse> makeRequest(URL url, Object input, Proxy proxy) throws AuthenticationException {
try {
String jsonResult = input == null ? NetworkUtils.doGet(url, proxy) : NetworkUtils.doPost(url, GSON.toJson(input), "application/json", proxy);
AuthenticationResponse response = GSON.fromJson(jsonResult, AuthenticationResponse.class);
if (response == null)
return Optional.empty();
if (!StringUtils.isBlank(response.getError())) {
if (response.getErrorMessage() != null)
if (response.getErrorMessage().contains("Invalid credentials"))
throw new InvalidCredentialsException(this);
else if (response.getErrorMessage().contains("Invalid token"))
throw new InvalidTokenException(this);
else if (response.getErrorMessage().contains("Invalid username or password"))
throw new InvalidPasswordException(this);
throw new AuthenticationException(response.getError() + ": " + response.getErrorMessage());
}
return Optional.of(response);
} catch (IOException e) {
throw new ServerDisconnectException(e);
} catch (JsonParseException e) {
throw new AuthenticationException("Unable to parse server response", e);
}
}
private boolean checkTokenValidity(Proxy proxy) {
if (accessToken == null)
return false;
try {
makeRequest(routeValidate, new ValidateRequest(accessToken, clientToken), proxy);
return true;
} catch (AuthenticationException e) {
return false;
}
return storage;
}
public UUID getUUID() {
if (getSelectedProfile() == null)
if (session == null)
return null;
else
return getSelectedProfile().getId();
return session.getSelectedProfile().getId();
}
public Optional<ProfileTexture> getSkin(GameProfile profile) throws IOException, JsonParseException {
if (StringUtils.isBlank(userId))
throw new IllegalStateException("Not logged in");
public Optional<Texture> getSkin() throws AuthenticationException {
return getSkin(session.getSelectedProfile());
}
Property textureProperty;
if (profile.getProperties().containsKey("textures"))
textureProperty = profile.getProperties().get("textures");
else {
ProfileResponse response = GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(baseProfile + UUIDTypeAdapter.fromUUID(profile.getId()))), ProfileResponse.class);
if (response.getProperties() == null) return Optional.empty();
textureProperty = response.getProperties().get("textures");
if (textureProperty == null) return Optional.empty();
profile.getProperties().putAll(response.getProperties());
public Optional<Texture> getSkin(GameProfile profile) throws AuthenticationException {
if (!service.getTextures(profile).isPresent()) {
session.setAvailableProfile(profile = service.getCompleteGameProfile(profile.getId()));
}
TextureResponse texture;
String json = new String(Base64.getDecoder().decode(textureProperty.getValue()), Charsets.UTF_8);
texture = GSON.fromJson(json, TextureResponse.class);
if (texture == null || texture.getTextures() == null)
return Optional.empty();
return Optional.ofNullable(texture.getTextures().get(ProfileTexture.Type.SKIN));
return service.getTextures(profile).map(map -> map.get(TextureType.SKIN));
}
@Override
public void clearCache() {
Optional.ofNullable(getSelectedProfile())
Optional.ofNullable(session)
.map(YggdrasilSession::getSelectedProfile)
.map(GameProfile::getProperties)
.ifPresent(it -> it.remove("texture"));
}
@@ -331,30 +175,4 @@ public class YggdrasilAccount extends Account {
return "YggdrasilAccount[username=" + getUsername() + "]";
}
private final String baseAuthServer;
private final String baseSessionServer;
private final String baseProfile;
private final URL routeAuthenticate;
private final URL routeRefresh;
private final URL routeValidate;
static final String STORAGE_KEY_ACCESS_TOKEN = "accessToken";
static final String STORAGE_KEY_PROFILE_NAME = "displayName";
static final String STORAGE_KEY_PROFILE_ID = "uuid";
static final String STORAGE_KEY_PROFILE_PROPERTIES = "profileProperties";
static final String STORAGE_KEY_USER_NAME = "username";
static final String STORAGE_KEY_USER_ID = "userid";
static final String STORAGE_KEY_USER_PROPERTIES = "userProperties";
static final String STORAGE_KEY_CLIENT_TOKEN = "clientToken";
public static String randomToken() {
return UUIDTypeAdapter.fromUUID(UUID.randomUUID());
}
private static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(GameProfile.class, GameProfile.Serializer.INSTANCE)
.registerTypeAdapter(PropertyMap.class, PropertyMap.Serializer.INSTANCE)
.registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)
.create();
}

View File

@@ -18,14 +18,15 @@
package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import java.util.List;
import java.net.Proxy;
import java.util.Map;
import java.util.Optional;
import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*;
import java.util.Objects;
import java.util.UUID;
/**
*
@@ -33,50 +34,40 @@ import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*;
*/
public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
private final String baseAuthServer;
private final String baseSessionServer;
private final YggdrasilProvider provider;
public YggdrasilAccountFactory() {
this(MOJANG_AUTH_SERVER, MOJANG_SESSION_SERVER);
}
public YggdrasilAccountFactory(String baseAuthServer, String baseSessionServer) {
this.baseAuthServer = baseAuthServer;
this.baseSessionServer = baseSessionServer;
public YggdrasilAccountFactory(YggdrasilProvider provider) {
this.provider = provider;
}
@Override
public YggdrasilAccount fromUsername(String username, String password, Object additionalData) {
YggdrasilAccount account = new YggdrasilAccount(MOJANG_AUTH_SERVER, MOJANG_SESSION_SERVER, username);
account.setPassword(password);
public YggdrasilAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException {
Objects.requireNonNull(selector);
Objects.requireNonNull(username);
Objects.requireNonNull(password);
Objects.requireNonNull(proxy);
YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider, proxy), username, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), null, null);
account.logInWithPassword(password, selector);
return account;
}
@Override
public YggdrasilAccount fromStorageImpl(Map<Object, Object> storage) {
String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME));
public YggdrasilAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
Objects.requireNonNull(storage);
Objects.requireNonNull(proxy);
YggdrasilAccount account = new YggdrasilAccount(baseAuthServer, baseSessionServer, username);
account.setUserId(Lang.get(storage, STORAGE_KEY_USER_ID, String.class).orElse(username));
account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class).orElse(null));
account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_CLIENT_TOKEN)));
String username = Lang.get(storage, "username", String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have username"));
String clientToken = Lang.get(storage, "clientToken", String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have client token."));
String character = Lang.get(storage, "clientToken", String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have selected character name."));
Lang.get(storage, STORAGE_KEY_USER_PROPERTIES, List.class)
.ifPresent(account.getUserProperties()::fromList);
Optional<String> profileId = Lang.get(storage, STORAGE_KEY_PROFILE_ID, String.class);
Optional<String> profileName = Lang.get(storage, STORAGE_KEY_PROFILE_NAME, String.class);
GameProfile profile = null;
if (profileId.isPresent() && profileName.isPresent()) {
profile = new GameProfile(UUIDTypeAdapter.fromString(profileId.get()), profileName.get());
Lang.get(storage, STORAGE_KEY_PROFILE_PROPERTIES, List.class)
.ifPresent(profile.getProperties()::fromList);
}
account.setSelectedProfile(profile);
return account;
return new YggdrasilAccount(new YggdrasilService(provider, proxy), username, clientToken, character, YggdrasilSession.fromStorage(storage));
}
private static final String MOJANG_AUTH_SERVER = "https://authserver.mojang.com/";
private static final String MOJANG_SESSION_SERVER = "https://sessionserver.mojang.com/";
public static String randomToken() {
return UUIDTypeAdapter.fromUUID(UUID.randomUUID());
}
}

View File

@@ -0,0 +1,21 @@
package org.jackhuang.hmcl.auth.yggdrasil;
import java.net.URL;
import java.util.UUID;
/**
* @see <a href="http://wiki.vg">http://wiki.vg</a>
*/
public interface YggdrasilProvider {
URL getAuthenticationURL();
URL getRefreshmentURL();
URL getValidationURL();
URL getInvalidationURL();
URL getProfilePropertiesURL(UUID uuid);
}

View File

@@ -0,0 +1,233 @@
package org.jackhuang.hmcl.auth.yggdrasil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.util.*;
import java.io.IOException;
import java.net.Proxy;
import java.net.URL;
import java.util.*;
public class YggdrasilService {
private final YggdrasilProvider provider;
private final Proxy proxy;
public YggdrasilService(YggdrasilProvider provider) {
this(provider, Proxy.NO_PROXY);
}
public YggdrasilService(YggdrasilProvider provider, Proxy proxy) {
this.provider = provider;
this.proxy = proxy;
}
public YggdrasilSession authenticate(String username, String password, String clientToken) throws AuthenticationException {
Objects.requireNonNull(username);
Objects.requireNonNull(password);
Objects.requireNonNull(clientToken);
Map<String, Object> request = new HashMap<>();
request.put("agent", Lang.mapOf(
new Pair<>("name", "Minecraft"),
new Pair<>("version", 1)
));
request.put("username", username);
request.put("password", password);
request.put("clientToken", clientToken);
request.put("requestUser", true);
return handle(request(provider.getAuthenticationURL(), request), clientToken);
}
public YggdrasilSession refresh(String accessToken, String clientToken) throws AuthenticationException {
Objects.requireNonNull(accessToken);
Objects.requireNonNull(clientToken);
return handle(request(provider.getRefreshmentURL(), new RefreshRequest(accessToken, clientToken)), clientToken);
}
public boolean validate(String accessToken) throws AuthenticationException {
return validate(accessToken, null);
}
public boolean validate(String accessToken, String clientToken) throws AuthenticationException {
Objects.requireNonNull(accessToken);
try {
requireEmpty(request(provider.getValidationURL(), new RefreshRequest(accessToken, clientToken)));
return true;
} catch (InvalidCredentialsException | InvalidTokenException e) {
return false;
}
}
public void invalidate(String accessToken) throws AuthenticationException {
invalidate(accessToken, null);
}
public void invalidate(String accessToken, String clientToken) throws AuthenticationException {
Objects.requireNonNull(accessToken);
requireEmpty(request(provider.getInvalidationURL(), GSON.toJson(new RefreshRequest(accessToken, clientToken))));
}
/**
* Get complete game profile.
*
* Game profile provided from authentication is not complete (no skin data in properties).
*
* @param userId the userId that the character corresponding to.
* @return the complete game profile(filled with more properties), null if character corresponding to {@code userId} does not exist.
* @throws AuthenticationException if an I/O error occurred or server response malformed.
*/
public GameProfile getCompleteGameProfile(UUID userId) throws AuthenticationException {
Objects.requireNonNull(userId);
ProfileResponse response = fromJson(request(provider.getProfilePropertiesURL(userId), null), ProfileResponse.class);
if (response == null)
return null;
return new GameProfile(response.getId(), response.getName(), response.getProperties());
}
public Optional<Map<TextureType, Texture>> getTextures(GameProfile profile) throws AuthenticationException {
Objects.requireNonNull(profile);
return Optional.ofNullable(profile.getProperties())
.map(properties -> properties.get("textures"))
.map(encodedTextures -> new String(Base64.getDecoder().decode(encodedTextures), Charsets.UTF_8))
.map(Lang.liftFunction(textures -> fromJson(textures, TextureResponse.class)))
.map(TextureResponse::getTextures);
}
private String request(URL url, Object input) throws AuthenticationException {
try {
if (input == null)
return NetworkUtils.doGet(url, proxy);
else
return NetworkUtils.doPost(url, input instanceof String ? (String) input : GSON.toJson(input), "application/json");
} catch (IOException e) {
throw new ServerDisconnectException(e);
}
}
private static YggdrasilSession handle(String responseText, String clientToken) throws AuthenticationException {
AuthenticationResponse response = fromJson(responseText, AuthenticationResponse.class);
handleErrorMessage(response);
if (!clientToken.equals(response.getClientToken()))
throw new AuthenticationException("Client token changed from " + response.getClientToken() + " to " + clientToken);
return new YggdrasilSession(response.getAccessToken(), response.getSelectedProfile(), response.getAvailableProfiles(), response.getUser());
}
private static void requireEmpty(String response) throws AuthenticationException {
if (StringUtils.isBlank(response))
return;
try {
handleErrorMessage(fromJson(response, ErrorResponse.class));
} catch (JsonParseException e) {
throw new ServerResponseMalformedException(e);
}
}
private static void handleErrorMessage(ErrorResponse response) throws AuthenticationException {
if (!StringUtils.isBlank(response.getError())) {
if (response.getErrorMessage() != null)
if (response.getErrorMessage().contains("Invalid credentials"))
throw new InvalidCredentialsException();
else if (response.getErrorMessage().contains("Invalid token"))
throw new InvalidTokenException();
else if (response.getErrorMessage().contains("Invalid username or password"))
throw new InvalidPasswordException();
throw new RemoteAuthenticationException(response.getError(), response.getErrorMessage(), response.getCause());
}
}
private static <T> T fromJson(String text, Class<T> typeOfT) throws ServerResponseMalformedException {
try {
return GSON.fromJson(text, typeOfT);
} catch (JsonParseException e) {
throw new ServerResponseMalformedException(e);
}
}
static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(GameProfile.class, GameProfile.Serializer.INSTANCE)
.registerTypeAdapter(PropertyMap.class, PropertyMap.Serializer.INSTANCE)
.registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)
.create();
private static final class RefreshRequest {
private final String accessToken;
private final String clientToken;
private final GameProfile selectedProfile;
private final boolean requestUser;
public RefreshRequest(String accessToken, String clientToken) {
this(accessToken, clientToken, null);
}
public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile) {
this(accessToken, clientToken, selectedProfile, true);
}
public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile, boolean requestUser) {
this.accessToken = accessToken;
this.clientToken = clientToken;
this.selectedProfile = selectedProfile;
this.requestUser = requestUser;
}
public String getAccessToken() {
return accessToken;
}
public String getClientToken() {
return clientToken;
}
public GameProfile getSelectedProfile() {
return selectedProfile;
}
public boolean isRequestUser() {
return requestUser;
}
}
private static final class ProfileResponse {
private final UUID id;
private final String name;
private final PropertyMap properties;
public ProfileResponse() {
this(UUID.randomUUID(), "", null);
}
public ProfileResponse(UUID id, String name, PropertyMap properties) {
this.id = id;
this.name = name;
this.properties = properties;
}
public UUID getId() {
return id;
}
public String getName() {
return name;
}
public PropertyMap getProperties() {
return properties;
}
}
}

View File

@@ -0,0 +1,89 @@
package org.jackhuang.hmcl.auth.yggdrasil;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class YggdrasilSession {
private final String accessToken;
private GameProfile selectedProfile;
private final GameProfile[] availableProfiles;
private final User user;
public YggdrasilSession(String accessToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user) {
this.accessToken = accessToken;
this.selectedProfile = selectedProfile;
this.availableProfiles = availableProfiles;
this.user = user;
}
public String getAccessToken() {
return accessToken;
}
public GameProfile getSelectedProfile() {
return selectedProfile;
}
public GameProfile[] getAvailableProfiles() {
return availableProfiles;
}
public User getUser() {
return user;
}
public void setAvailableProfile(GameProfile profile) {
if (availableProfiles != null)
for (int i = 0; i < availableProfiles.length; ++i)
if (availableProfiles[i].getId().equals(profile.getId()))
availableProfiles[i] = profile;
if (selectedProfile != null && profile.getId().equals(selectedProfile.getId()))
selectedProfile = profile;
}
public void setSelectedProfile(GameProfile selectedProfile) {
this.selectedProfile = selectedProfile;
setAvailableProfile(selectedProfile);
}
public static YggdrasilSession fromStorage(Map<?, ?> storage) {
Optional<String> profileId = Lang.get(storage, "uuid", String.class);
Optional<String> profileName = Lang.get(storage, "displayName", String.class);
GameProfile profile = null;
if (profileId.isPresent() && profileName.isPresent()) {
profile = new GameProfile(UUIDTypeAdapter.fromString(profileId.get()), profileName.get(),
Lang.get(storage, "profileProperties", Map.class).map(PropertyMap::fromMap).orElseGet(PropertyMap::new));
}
return new YggdrasilSession(
Lang.get(storage, "accessToken", String.class).orElse(null),
profile,
null,
Lang.get(storage, "userid", String.class)
.map(userId -> new User(userId, Lang.get(storage, "userProperties", Map.class).map(PropertyMap::fromMap).orElse(null)))
.orElse(null)
);
}
public Map<Object, Object> toStorage() {
Map<Object, Object> storage = new HashMap<>();
storage.put("accessToken", accessToken);
if (selectedProfile != null) {
storage.put("uuid", UUIDTypeAdapter.fromUUID(selectedProfile.getId()));
storage.put("displayName", selectedProfile.getName());
storage.put("profileProperties", selectedProfile.getProperties());
}
if (user != null) {
storage.put("userid", user.getId());
storage.put("userProperties", user.getProperties());
}
return storage;
}
}

View File

@@ -47,7 +47,7 @@ public class LaunchOptions implements Serializable {
private String proxyUser;
private String proxyPass;
private boolean noGeneratedJVMArgs;
private String precalledCommand;
private String preLaunchCommand;
/**
* The game directory
@@ -188,8 +188,8 @@ public class LaunchOptions implements Serializable {
/**
* Called command line before launching the game.
*/
public String getPrecalledCommand() {
return precalledCommand;
public String getPreLaunchCommand() {
return preLaunchCommand;
}
public static class Builder {
@@ -296,7 +296,7 @@ public class LaunchOptions implements Serializable {
}
public Builder setPrecalledCommand(String precalledCommand) {
options.precalledCommand = precalledCommand;
options.preLaunchCommand = precalledCommand;
return this;
}

View File

@@ -240,8 +240,8 @@ public class DefaultLauncher extends Launcher {
protected Map<String, String> getConfigurations() {
return Lang.mapOf(
new Pair<>("${auth_player_name}", authInfo.getUsername()),
new Pair<>("${auth_session}", authInfo.getAuthToken()),
new Pair<>("${auth_access_token}", authInfo.getAuthToken()),
new Pair<>("${auth_session}", authInfo.getAccessToken()),
new Pair<>("${auth_access_token}", authInfo.getAccessToken()),
new Pair<>("${auth_uuid}", authInfo.getUserId()),
new Pair<>("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())),
new Pair<>("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")),
@@ -262,8 +262,8 @@ public class DefaultLauncher extends Launcher {
decompressNatives(nativeFolder);
if (StringUtils.isNotBlank(options.getPrecalledCommand()))
Runtime.getRuntime().exec(options.getPrecalledCommand()).waitFor();
if (StringUtils.isNotBlank(options.getPreLaunchCommand()))
Runtime.getRuntime().exec(options.getPreLaunchCommand()).waitFor();
Process process;
try {
@@ -306,8 +306,8 @@ public class DefaultLauncher extends Launcher {
writer.write("cd /D %APPDATA%");
writer.newLine();
}
if (StringUtils.isNotBlank(options.getPrecalledCommand())) {
writer.write(options.getPrecalledCommand());
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
writer.write(options.getPreLaunchCommand());
writer.newLine();
}
writer.write(StringUtils.makeCommand(generateCommandLine(nativeFolder)));

View File

@@ -270,7 +270,7 @@ public abstract class Task {
}
else {
if (failure != null)
failure.accept(variables.get(TaskExecutor.LAST_EXCEPION_ID));
failure.accept(variables.get(TaskExecutor.LAST_EXCEPTION_ID));
}
});
}

View File

@@ -20,10 +20,7 @@ package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.*;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
@@ -109,7 +106,7 @@ public final class TaskExecutor {
return false;
Invoker invoker = new Invoker(task, latch, success);
try {
Future<?> future = task.getScheduler().schedule(invoker);
Future<?> future = scheduler.schedule(invoker);
if (future != null)
workerQueue.add(future);
} catch (RejectedExecutionException e) {
@@ -152,7 +149,12 @@ public final class TaskExecutor {
task.setDependentsSucceeded();
task.setVariables(variables);
task.execute();
try {
task.getScheduler().schedule(task::execute).get();
} catch (ExecutionException e) {
throw (Exception) e.getCause();
}
if (task instanceof TaskResult<?>) {
TaskResult<?> taskResult = (TaskResult<?>) task;
@@ -181,7 +183,7 @@ public final class TaskExecutor {
// do nothing
} catch (Exception e) {
lastException = e;
variables.set(LAST_EXCEPION_ID, e);
variables.set(LAST_EXCEPTION_ID, e);
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
task.onDone().fireEvent(new TaskEvent(this, task, true));
taskListeners.forEach(it -> it.onFailed(task, e));
@@ -226,5 +228,5 @@ public final class TaskExecutor {
}
public static final String LAST_EXCEPION_ID = "lastException";
public static final String LAST_EXCEPTION_ID = "lastException";
}

View File

@@ -58,8 +58,8 @@ public final class IOUtils {
return readFully(stream).toString();
}
public static String readFullyAsString(InputStream stream, Charset charset) {
return Lang.invoke(() -> readFully(stream).toString(charset.name()));
public static String readFullyAsString(InputStream stream, Charset charset) throws IOException {
return readFully(stream).toString(charset.name());
}
public static void copyTo(InputStream src, OutputStream dest) throws IOException {

View File

@@ -87,7 +87,7 @@ public final class Lang {
return r -> invoke(function, r);
}
public static <T, R, E extends Exception> Function<T, R> liftFunction(ExceptionalFunction<T, R, E> function) {
public static <T, R, E extends Exception> Function<T, R> liftFunction(ExceptionalFunction<T, R, E> function) throws E {
return hideFunction(function);
}
@@ -115,7 +115,7 @@ public final class Lang {
return () -> invoke(supplier);
}
public static <T, E extends Exception> Supplier<T> liftException(ExceptionalSupplier<T, E> supplier) {
public static <T, E extends Exception> Supplier<T> liftException(ExceptionalSupplier<T, E> supplier) throws E {
return hideException(supplier);
}
@@ -141,7 +141,7 @@ public final class Lang {
return it -> invokeConsumer(consumer, it);
}
public static <T, E extends Exception> Consumer<T> liftConsumer(ExceptionalConsumer<T, E> consumer) {
public static <T, E extends Exception> Consumer<T> liftConsumer(ExceptionalConsumer<T, E> consumer) throws E {
return hideConsumer(consumer);
}

View File

@@ -26,7 +26,6 @@ import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;

View File

@@ -85,10 +85,10 @@ public final class NetworkUtils {
return doPost(u, post, contentType, Proxy.NO_PROXY);
}
public static String doPost(URL u, String post, String contentType, Proxy proxy) throws IOException {
public static String doPost(URL url, String post, String contentType, Proxy proxy) throws IOException {
byte[] bytes = post.getBytes(Charsets.UTF_8);
HttpURLConnection con = createConnection(u, proxy);
HttpURLConnection con = createConnection(url, proxy);
con.setRequestMethod("POST");
con.setDoOutput(true);
con.setRequestProperty("Content-Type", contentType + "; charset=utf-8");