authentication reconstruction
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
@@ -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) {
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
public enum TextureType {
|
||||
SKIN, CAPE
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user