mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-02-17 17:09:55 +08:00
Merge pull request #342 from yushijinhun/refactor-skin
Yggdrasil 部分重构及 bug 修复
This commit is contained in:
commit
72871e6d9a
@ -17,9 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
@ -38,7 +39,6 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.LogWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
@ -47,7 +47,6 @@ import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class LauncherHelper {
|
||||
public static final LauncherHelper INSTANCE = new LauncherHelper();
|
||||
@ -301,10 +300,10 @@ public final class LauncherHelper {
|
||||
if (authInfo == null)
|
||||
forbiddenTokens = Collections.emptyMap();
|
||||
else
|
||||
forbiddenTokens = Lang.mapOf(
|
||||
new Pair<>(authInfo.getAccessToken(), "<access token>"),
|
||||
new Pair<>(authInfo.getUserId(), "<uuid>"),
|
||||
new Pair<>(authInfo.getUsername(), "<player>")
|
||||
forbiddenTokens = mapOf(
|
||||
pair(authInfo.getAccessToken(), "<access token>"),
|
||||
pair(UUIDTypeAdapter.fromUUID(authInfo.getUUID()), "<uuid>"),
|
||||
pair(authInfo.getUsername(), "<player>")
|
||||
);
|
||||
|
||||
visibility = setting.getLauncherVisibility();
|
||||
@ -334,7 +333,7 @@ public final class LauncherHelper {
|
||||
else
|
||||
System.out.print(log);
|
||||
|
||||
logs.add(new Pair<>(log, level));
|
||||
logs.add(pair(log, level));
|
||||
if (logs.size() > Settings.INSTANCE.getLogLines())
|
||||
logs.removeFirst();
|
||||
|
||||
|
@ -17,6 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
@ -50,10 +53,10 @@ public final class Accounts {
|
||||
public static final String YGGDRASIL_ACCOUNT_KEY = "yggdrasil";
|
||||
public static final String AUTHLIB_INJECTOR_ACCOUNT_KEY = "authlibInjector";
|
||||
|
||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = Lang.mapOf(
|
||||
new Pair<>(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
||||
new Pair<>(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
||||
new Pair<>(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
|
||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = mapOf(
|
||||
pair(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
||||
pair(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
||||
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
|
||||
);
|
||||
|
||||
private static final Map<String, String> AUTHLIB_INJECTOR_SERVER_NAMES = new HashMap<>();
|
||||
|
@ -17,6 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.export;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import com.jfoenix.controls.JFXTreeView;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
@ -33,8 +36,6 @@ import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
@ -154,19 +155,19 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
}
|
||||
|
||||
public static final String MODPACK_FILE_SELECTION = "modpack.accepted";
|
||||
private static final Map<String, String> TRANSLATION = Lang.mapOf(
|
||||
new Pair<>("minecraft/servers.dat", Launcher.i18n("modpack.files.servers_dat")),
|
||||
new Pair<>("minecraft/saves", Launcher.i18n("modpack.files.saves")),
|
||||
new Pair<>("minecraft/mods", Launcher.i18n("modpack.files.mods")),
|
||||
new Pair<>("minecraft/config", Launcher.i18n("modpack.files.config")),
|
||||
new Pair<>("minecraft/liteconfig", Launcher.i18n("modpack.files.liteconfig")),
|
||||
new Pair<>("minecraft/resourcepacks", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
new Pair<>("minecraft/resources", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
new Pair<>("minecraft/options.txt", Launcher.i18n("modpack.files.options_txt")),
|
||||
new Pair<>("minecraft/optionsshaders.txt", Launcher.i18n("modpack.files.optionsshaders_txt")),
|
||||
new Pair<>("minecraft/mods/VoxelMods", Launcher.i18n("modpack.files.mods.voxelmods")),
|
||||
new Pair<>("minecraft/dumps", Launcher.i18n("modpack.files.dumps")),
|
||||
new Pair<>("minecraft/blueprints", Launcher.i18n("modpack.files.blueprints")),
|
||||
new Pair<>("minecraft/scripts", Launcher.i18n("modpack.files.scripts"))
|
||||
private static final Map<String, String> TRANSLATION = mapOf(
|
||||
pair("minecraft/servers.dat", Launcher.i18n("modpack.files.servers_dat")),
|
||||
pair("minecraft/saves", Launcher.i18n("modpack.files.saves")),
|
||||
pair("minecraft/mods", Launcher.i18n("modpack.files.mods")),
|
||||
pair("minecraft/config", Launcher.i18n("modpack.files.config")),
|
||||
pair("minecraft/liteconfig", Launcher.i18n("modpack.files.liteconfig")),
|
||||
pair("minecraft/resourcepacks", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
pair("minecraft/resources", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
pair("minecraft/options.txt", Launcher.i18n("modpack.files.options_txt")),
|
||||
pair("minecraft/optionsshaders.txt", Launcher.i18n("modpack.files.optionsshaders_txt")),
|
||||
pair("minecraft/mods/VoxelMods", Launcher.i18n("modpack.files.mods.voxelmods")),
|
||||
pair("minecraft/dumps", Launcher.i18n("modpack.files.dumps")),
|
||||
pair("minecraft/blueprints", Launcher.i18n("modpack.files.blueprints")),
|
||||
pair("minecraft/scripts", Launcher.i18n("modpack.files.scripts"))
|
||||
);
|
||||
}
|
||||
|
@ -61,8 +61,6 @@ public abstract class Account {
|
||||
*/
|
||||
public abstract AuthInfo playOffline();
|
||||
|
||||
public abstract void logOut();
|
||||
|
||||
public abstract Map<Object, Object> toStorage();
|
||||
|
||||
public abstract void clearCache();
|
||||
|
@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
@ -28,29 +30,19 @@ import org.jackhuang.hmcl.util.Immutable;
|
||||
public final class AuthInfo {
|
||||
|
||||
private final String username;
|
||||
private final String userId;
|
||||
private final UUID uuid;
|
||||
private final String accessToken;
|
||||
private final UserType userType;
|
||||
private final String userProperties;
|
||||
private final Arguments arguments;
|
||||
|
||||
public AuthInfo(String username, String userId, String accessToken) {
|
||||
this(username, userId, accessToken, UserType.LEGACY);
|
||||
public AuthInfo(String username, UUID uuid, String accessToken, String userProperties) {
|
||||
this(username, uuid, accessToken, userProperties, null);
|
||||
}
|
||||
|
||||
public AuthInfo(String username, String userId, String accessToken, UserType userType) {
|
||||
this(username, userId, accessToken, userType, "{}");
|
||||
}
|
||||
|
||||
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 accessToken, UserType userType, String userProperties, Arguments arguments) {
|
||||
public AuthInfo(String username, UUID uuid, String accessToken, String userProperties, Arguments arguments) {
|
||||
this.username = username;
|
||||
this.userId = userId;
|
||||
this.uuid = uuid;
|
||||
this.accessToken = accessToken;
|
||||
this.userType = userType;
|
||||
this.userProperties = userProperties;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
@ -59,18 +51,14 @@ public final class AuthInfo {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public UserType getUserType() {
|
||||
return userType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties of this user.
|
||||
* Don't know the difference between user properties and user property map.
|
||||
@ -81,11 +69,14 @@ public final class AuthInfo {
|
||||
return userProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if no argument is specified
|
||||
*/
|
||||
public Arguments getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public AuthInfo setArguments(Arguments arguments) {
|
||||
return new AuthInfo(username, userId, accessToken, userType, userProperties, arguments);
|
||||
public AuthInfo withArguments(Arguments arguments) {
|
||||
return new AuthInfo(username, uuid, accessToken, userProperties, arguments);
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public enum UserType {
|
||||
LEGACY,
|
||||
MOJANG;
|
||||
|
||||
public static UserType fromName(String name) {
|
||||
return BY_NAME.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
public static UserType fromLegacy(boolean isLegacy) {
|
||||
return isLegacy ? LEGACY : MOJANG;
|
||||
}
|
||||
|
||||
static {
|
||||
HashMap<String, UserType> byName = new HashMap<>();
|
||||
for (UserType type : values())
|
||||
byName.put(type.name().toLowerCase(), type);
|
||||
BY_NAME = Collections.unmodifiableMap(byName);
|
||||
}
|
||||
|
||||
public static final Map<String, UserType> BY_NAME;
|
||||
|
||||
}
|
@ -39,8 +39,8 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
private final String serverBaseURL;
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPath;
|
||||
|
||||
protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier<String, ?> injectorJarPath, String username, String clientToken, String character, YggdrasilSession session) {
|
||||
super(service, username, clientToken, character, session);
|
||||
protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier<String, ?> injectorJarPath, String username, String character, YggdrasilSession session) {
|
||||
super(service, username, character, session);
|
||||
|
||||
this.injectorJarPath = injectorJarPath;
|
||||
this.serverBaseURL = serverBaseURL;
|
||||
@ -72,7 +72,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
if (flag.get())
|
||||
arguments = Arguments.addJVMArguments(arguments, "-Dorg.to2mbn.authlibinjector.config.prefetched=" + new String(Base64.getEncoder().encode(getTask.getResult().getBytes()), UTF_8));
|
||||
|
||||
return info.setArguments(arguments);
|
||||
return info.withArguments(arguments);
|
||||
} catch (Exception e) {
|
||||
throw new AuthenticationException("Unable to get authlib injector jar path", e);
|
||||
}
|
||||
|
@ -7,12 +7,10 @@ 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.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
@ -34,7 +32,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
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);
|
||||
(String) serverBaseURL, injectorJarPathSupplier, username, null, null);
|
||||
account.logInWithPassword(password, selector);
|
||||
return account;
|
||||
}
|
||||
@ -46,14 +44,12 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
|
||||
String username = tryCast(storage.get("username"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have username"));
|
||||
String clientToken = tryCast(storage.get("clientToken"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have client token."));
|
||||
String character = tryCast(storage.get("clientToken"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have selected character name."));
|
||||
String apiRoot = tryCast(storage.get("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));
|
||||
apiRoot, injectorJarPathSupplier, username, character, YggdrasilSession.fromStorage(storage));
|
||||
}
|
||||
}
|
||||
|
@ -17,15 +17,20 @@
|
||||
*/
|
||||
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.*;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
@ -33,9 +38,9 @@ import java.util.UUID;
|
||||
public class OfflineAccount extends Account {
|
||||
|
||||
private final String username;
|
||||
private final String uuid;
|
||||
private final UUID uuid;
|
||||
|
||||
OfflineAccount(String username, String uuid) {
|
||||
OfflineAccount(String username, UUID uuid) {
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(uuid);
|
||||
|
||||
@ -48,7 +53,7 @@ public class OfflineAccount extends Account {
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return UUIDTypeAdapter.fromString(uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -66,7 +71,7 @@ public class OfflineAccount extends Account {
|
||||
if (StringUtils.isBlank(username))
|
||||
throw new AuthenticationException("Username cannot be empty");
|
||||
|
||||
return new AuthInfo(username, uuid, uuid);
|
||||
return new AuthInfo(username, uuid, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), "{}");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -74,11 +79,6 @@ public class OfflineAccount extends Account {
|
||||
return logIn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logOut() {
|
||||
// Offline account need not log out.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlayOffline() {
|
||||
return false;
|
||||
@ -91,9 +91,9 @@ public class OfflineAccount extends Account {
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> toStorage() {
|
||||
return Lang.mapOf(
|
||||
new Pair<>("uuid", uuid),
|
||||
new Pair<>("username", username)
|
||||
return mapOf(
|
||||
pair("uuid", UUIDTypeAdapter.fromUUID(uuid)),
|
||||
pair("username", username)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -23,9 +23,9 @@ import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
import static org.jackhuang.hmcl.util.Hex.encodeHex;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
/**
|
||||
@ -47,17 +47,15 @@ public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
|
||||
public OfflineAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
|
||||
String username = tryCast(storage.get("username"), String.class)
|
||||
.orElseThrow(() -> new IllegalStateException("Offline account configuration malformed."));
|
||||
String uuid = tryCast(storage.get("uuid"), String.class)
|
||||
UUID uuid = tryCast(storage.get("uuid"), String.class)
|
||||
.map(UUIDTypeAdapter::fromString)
|
||||
.orElse(getUUIDFromUserName(username));
|
||||
|
||||
// Check if the uuid is vaild
|
||||
UUIDTypeAdapter.fromString(uuid);
|
||||
|
||||
return new OfflineAccount(username, uuid);
|
||||
}
|
||||
|
||||
private static String getUUIDFromUserName(String username) {
|
||||
return encodeHex(digest("MD5", username));
|
||||
private static UUID getUUIDFromUserName(String username) {
|
||||
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(UTF_8));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,66 +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
|
||||
*/
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String getClientToken() {
|
||||
return clientToken;
|
||||
}
|
||||
|
||||
public GameProfile getSelectedProfile() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public GameProfile[] getAvailableProfiles() {
|
||||
return availableProfiles;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -17,11 +17,8 @@
|
||||
*/
|
||||
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;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@ -34,7 +31,6 @@ public final class GameProfile {
|
||||
private final UUID id;
|
||||
private final String name;
|
||||
private final PropertyMap properties;
|
||||
private final boolean legacy;
|
||||
|
||||
public GameProfile() {
|
||||
this(null, null);
|
||||
@ -45,14 +41,9 @@ public final class GameProfile {
|
||||
}
|
||||
|
||||
public GameProfile(UUID id, String name, PropertyMap properties) {
|
||||
this(id, name, properties, false);
|
||||
}
|
||||
|
||||
public GameProfile(UUID id, String name, PropertyMap properties, boolean legacy) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.properties = properties;
|
||||
this.legacy = legacy;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
@ -63,46 +54,11 @@ public final class GameProfile {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return nullable
|
||||
*/
|
||||
public PropertyMap getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public boolean isLegacy() {
|
||||
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();
|
||||
|
||||
private Serializer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(GameProfile src, Type type, JsonSerializationContext context) {
|
||||
JsonObject result = new JsonObject();
|
||||
if (src.getId() != null)
|
||||
result.add("id", context.serialize(src.getId()));
|
||||
if (src.getName() != null)
|
||||
result.addProperty("name", src.getName());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameProfile deserialize(JsonElement je, Type type, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (!(je instanceof JsonObject))
|
||||
throw new JsonParseException("The json element is not a JsonObject.");
|
||||
|
||||
JsonObject json = (JsonObject) je;
|
||||
|
||||
UUID id = json.has("id") ? context.deserialize(json.get("id"), UUID.class) : null;
|
||||
String name = json.has("name") ? json.getAsJsonPrimitive("name").getAsString() : null;
|
||||
return new GameProfile(id, name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,53 +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.Immutable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Immutable
|
||||
public final class TextureResponse {
|
||||
private final UUID profileId;
|
||||
private final String profileName;
|
||||
private final Map<TextureType, Texture> textures;
|
||||
|
||||
public TextureResponse() {
|
||||
this(UUID.randomUUID(), "", Collections.emptyMap());
|
||||
}
|
||||
|
||||
public TextureResponse(UUID profileId, String profileName, Map<TextureType, Texture> textures) {
|
||||
this.profileId = profileId;
|
||||
this.profileName = profileName;
|
||||
this.textures = textures;
|
||||
}
|
||||
|
||||
public UUID getProfileId() {
|
||||
return profileId;
|
||||
}
|
||||
|
||||
public String getProfileName() {
|
||||
return profileName;
|
||||
}
|
||||
|
||||
public Map<TextureType, Texture> getTextures() {
|
||||
return textures == null ? null : Collections.unmodifiableMap(textures);
|
||||
}
|
||||
}
|
@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
@ -34,14 +33,12 @@ public class YggdrasilAccount extends Account {
|
||||
private final YggdrasilService service;
|
||||
private boolean isOnline = false;
|
||||
private YggdrasilSession session;
|
||||
private final String clientToken;
|
||||
private String character;
|
||||
|
||||
protected YggdrasilAccount(YggdrasilService service, String username, String clientToken, String character, YggdrasilSession session) {
|
||||
protected YggdrasilAccount(YggdrasilService service, String username, String character, YggdrasilSession session) {
|
||||
this.service = service;
|
||||
this.username = username;
|
||||
this.session = session;
|
||||
this.clientToken = clientToken;
|
||||
this.character = character;
|
||||
|
||||
if (session == null || session.getSelectedProfile() == null || StringUtils.isBlank(session.getAccessToken()))
|
||||
@ -72,7 +69,7 @@ public class YggdrasilAccount extends Account {
|
||||
logInWithToken();
|
||||
selectProfile(new SpecificCharacterSelector(character));
|
||||
}
|
||||
return toAuthInfo();
|
||||
return session.toAuthInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -81,9 +78,9 @@ public class YggdrasilAccount extends Account {
|
||||
}
|
||||
|
||||
protected AuthInfo logInWithPassword(String password, CharacterSelector selector) throws AuthenticationException {
|
||||
session = service.authenticate(username, password, clientToken);
|
||||
session = service.authenticate(username, password, UUIDTypeAdapter.fromUUID(UUID.randomUUID()));
|
||||
selectProfile(selector);
|
||||
return toAuthInfo();
|
||||
return session.toAuthInfo();
|
||||
}
|
||||
|
||||
private void selectProfile(CharacterSelector selector) throws AuthenticationException {
|
||||
@ -91,25 +88,18 @@ public class YggdrasilAccount extends Account {
|
||||
if (session.getAvailableProfiles() == null || session.getAvailableProfiles().length <= 0)
|
||||
throw new NoCharacterException(this);
|
||||
|
||||
session.setSelectedProfile(selector.select(this, Arrays.asList(session.getAvailableProfiles())));
|
||||
session = service.refresh(session.getAccessToken(), session.getClientToken(), selector.select(this, Arrays.asList(session.getAvailableProfiles())));
|
||||
}
|
||||
|
||||
character = session.getSelectedProfile().getName();
|
||||
}
|
||||
|
||||
private void logInWithToken() throws AuthenticationException {
|
||||
if (service.validate(session.getAccessToken(), clientToken)) {
|
||||
if (service.validate(session.getAccessToken(), session.getClientToken())) {
|
||||
isOnline = true;
|
||||
return;
|
||||
}
|
||||
session = service.refresh(session.getAccessToken(), clientToken);
|
||||
}
|
||||
|
||||
private AuthInfo toAuthInfo() {
|
||||
GameProfile profile = session.getSelectedProfile();
|
||||
|
||||
return new AuthInfo(profile.getName(), UUIDTypeAdapter.fromUUID(profile.getId()), session.getAccessToken(), profile.getUserType(),
|
||||
new GsonBuilder().registerTypeAdapter(PropertyMap.class, PropertyMap.LegacySerializer.INSTANCE).create().toJson(Optional.ofNullable(session.getUser()).map(User::getProperties).orElseGet(PropertyMap::new)));
|
||||
session = service.refresh(session.getAccessToken(), session.getClientToken(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -122,13 +112,7 @@ public class YggdrasilAccount extends Account {
|
||||
if (!canPlayOffline())
|
||||
throw new IllegalStateException("Current account " + this + " cannot play offline.");
|
||||
|
||||
return toAuthInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logOut() {
|
||||
isOnline = false;
|
||||
session = null;
|
||||
return session.toAuthInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -136,7 +120,6 @@ public class YggdrasilAccount extends Account {
|
||||
HashMap<Object, Object> storage = new HashMap<>();
|
||||
|
||||
storage.put("username", getUsername());
|
||||
storage.put("clientToken", clientToken);
|
||||
storage.put("character", character);
|
||||
if (session != null)
|
||||
storage.putAll(session.toStorage());
|
||||
@ -144,6 +127,7 @@ public class YggdrasilAccount extends Account {
|
||||
return storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
if (session == null || session.getSelectedProfile() == null)
|
||||
return null;
|
||||
@ -157,7 +141,7 @@ public class YggdrasilAccount extends Account {
|
||||
|
||||
public Optional<Texture> getSkin(GameProfile profile) throws AuthenticationException {
|
||||
if (!service.getTextures(profile).isPresent()) {
|
||||
session.setAvailableProfile(profile = service.getCompleteGameProfile(profile.getId()));
|
||||
profile = service.getCompleteGameProfile(profile.getId()).orElse(profile);
|
||||
}
|
||||
|
||||
return service.getTextures(profile).map(map -> map.get(TextureType.SKIN));
|
||||
|
@ -48,7 +48,7 @@ public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
Objects.requireNonNull(password);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider, proxy), username, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), null, null);
|
||||
YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider, proxy), username, null, null);
|
||||
account.logInWithPassword(password, selector);
|
||||
return account;
|
||||
}
|
||||
@ -60,12 +60,10 @@ public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
|
||||
String username = tryCast(storage.get("username"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have username"));
|
||||
String clientToken = tryCast(storage.get("clientToken"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have client token."));
|
||||
String character = tryCast(storage.get("clientToken"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have selected character name."));
|
||||
|
||||
return new YggdrasilAccount(new YggdrasilService(provider, proxy), username, clientToken, character, YggdrasilSession.fromStorage(storage));
|
||||
return new YggdrasilAccount(new YggdrasilService(provider, proxy), username, character, YggdrasilSession.fromStorage(storage));
|
||||
}
|
||||
|
||||
public static String randomToken() {
|
||||
|
@ -1,17 +1,33 @@
|
||||
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 static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Lang.liftFunction;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.InvalidCredentialsException;
|
||||
import org.jackhuang.hmcl.auth.InvalidPasswordException;
|
||||
import org.jackhuang.hmcl.auth.InvalidTokenException;
|
||||
import org.jackhuang.hmcl.auth.ServerDisconnectException;
|
||||
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
public class YggdrasilService {
|
||||
|
||||
@ -33,23 +49,39 @@ public class YggdrasilService {
|
||||
Objects.requireNonNull(clientToken);
|
||||
|
||||
Map<String, Object> request = new HashMap<>();
|
||||
request.put("agent", Lang.mapOf(
|
||||
new Pair<>("name", "Minecraft"),
|
||||
new Pair<>("version", 1)
|
||||
request.put("agent", mapOf(
|
||||
pair("name", "Minecraft"),
|
||||
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);
|
||||
return handleAuthenticationResponse(request(provider.getAuthenticationURL(), request), clientToken);
|
||||
}
|
||||
|
||||
public YggdrasilSession refresh(String accessToken, String clientToken) throws AuthenticationException {
|
||||
private Map<String, Object> createRequestWithCredentials(String accessToken, String clientToken) {
|
||||
Map<String, Object> request = new HashMap<>();
|
||||
request.put("accessToken", accessToken);
|
||||
request.put("clientToken", clientToken);
|
||||
return request;
|
||||
}
|
||||
|
||||
public YggdrasilSession refresh(String accessToken, String clientToken, GameProfile characterToSelect) throws AuthenticationException {
|
||||
Objects.requireNonNull(accessToken);
|
||||
Objects.requireNonNull(clientToken);
|
||||
|
||||
return handle(request(provider.getRefreshmentURL(), new RefreshRequest(accessToken, clientToken)), clientToken);
|
||||
Map<String, Object> request = createRequestWithCredentials(accessToken, clientToken);
|
||||
request.put("requestUser", true);
|
||||
|
||||
if (characterToSelect != null) {
|
||||
request.put("selectedProfile", mapOf(
|
||||
pair("id", characterToSelect.getId()),
|
||||
pair("name", characterToSelect.getName())));
|
||||
}
|
||||
|
||||
return handleAuthenticationResponse(request(provider.getRefreshmentURL(), request), clientToken);
|
||||
}
|
||||
|
||||
public boolean validate(String accessToken) throws AuthenticationException {
|
||||
@ -60,7 +92,7 @@ public class YggdrasilService {
|
||||
Objects.requireNonNull(accessToken);
|
||||
|
||||
try {
|
||||
requireEmpty(request(provider.getValidationURL(), new RefreshRequest(accessToken, clientToken)));
|
||||
requireEmpty(request(provider.getValidationURL(), createRequestWithCredentials(accessToken, clientToken)));
|
||||
return true;
|
||||
} catch (InvalidCredentialsException | InvalidTokenException e) {
|
||||
return false;
|
||||
@ -74,7 +106,7 @@ public class YggdrasilService {
|
||||
public void invalidate(String accessToken, String clientToken) throws AuthenticationException {
|
||||
Objects.requireNonNull(accessToken);
|
||||
|
||||
requireEmpty(request(provider.getInvalidationURL(), GSON.toJson(new RefreshRequest(accessToken, clientToken))));
|
||||
requireEmpty(request(provider.getInvalidationURL(), createRequestWithCredentials(accessToken, clientToken)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,49 +114,33 @@ public class YggdrasilService {
|
||||
*
|
||||
* 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.
|
||||
* @param uuid the uuid that the character corresponding to.
|
||||
* @return the complete game profile(filled with more properties)
|
||||
*/
|
||||
public GameProfile getCompleteGameProfile(UUID userId) throws AuthenticationException {
|
||||
Objects.requireNonNull(userId);
|
||||
public Optional<GameProfile> getCompleteGameProfile(UUID uuid) throws AuthenticationException {
|
||||
Objects.requireNonNull(uuid);
|
||||
|
||||
ProfileResponse response = fromJson(request(provider.getProfilePropertiesURL(userId), null), ProfileResponse.class);
|
||||
if (response == null)
|
||||
return null;
|
||||
|
||||
return new GameProfile(response.getId(), response.getName(), response.getProperties());
|
||||
return Optional.ofNullable(fromJson(request(provider.getProfilePropertiesURL(uuid), null), GameProfile.class));
|
||||
}
|
||||
|
||||
public Optional<Map<TextureType, Texture>> getTextures(GameProfile profile) throws AuthenticationException {
|
||||
Objects.requireNonNull(profile);
|
||||
|
||||
return Optional.ofNullable(profile.getProperties())
|
||||
.map(properties -> properties.get("textures"))
|
||||
.flatMap(properties -> Optional.ofNullable(properties.get("textures")))
|
||||
.map(encodedTextures -> new String(Base64.getDecoder().decode(encodedTextures), UTF_8))
|
||||
.map(Lang.liftFunction(textures -> fromJson(textures, TextureResponse.class)))
|
||||
.map(TextureResponse::getTextures);
|
||||
.map(liftFunction(textures -> fromJson(textures, TextureResponse.class)))
|
||||
.flatMap(response -> Optional.ofNullable(response.textures));
|
||||
}
|
||||
|
||||
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 {
|
||||
private static YggdrasilSession handleAuthenticationResponse(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);
|
||||
if (!clientToken.equals(response.clientToken))
|
||||
throw new AuthenticationException("Client token changed from " + clientToken + " to " + response.clientToken);
|
||||
|
||||
return new YggdrasilSession(response.getAccessToken(), response.getSelectedProfile(), response.getAvailableProfiles(), response.getUser());
|
||||
return new YggdrasilSession(response.clientToken, response.accessToken, response.selectedProfile, response.availableProfiles, response.user);
|
||||
}
|
||||
|
||||
private static void requireEmpty(String response) throws AuthenticationException {
|
||||
@ -139,15 +155,30 @@ public class YggdrasilService {
|
||||
}
|
||||
|
||||
private static void handleErrorMessage(ErrorResponse response) throws AuthenticationException {
|
||||
if (!StringUtils.isBlank(response.getError())) {
|
||||
if (response.getErrorMessage() != null)
|
||||
if (response.getErrorMessage().contains("Invalid credentials"))
|
||||
if (!StringUtils.isBlank(response.error)) {
|
||||
if (response.errorMessage != null && "ForbiddenOperationException".equals(response.error)) {
|
||||
if (response.errorMessage.contains("Invalid credentials"))
|
||||
throw new InvalidCredentialsException();
|
||||
else if (response.getErrorMessage().contains("Invalid token"))
|
||||
|
||||
else if (response.errorMessage.contains("Invalid token"))
|
||||
throw new InvalidTokenException();
|
||||
else if (response.getErrorMessage().contains("Invalid username or password"))
|
||||
|
||||
else if (response.errorMessage.contains("Invalid username or password"))
|
||||
throw new InvalidPasswordException();
|
||||
throw new RemoteAuthenticationException(response.getError(), response.getErrorMessage(), response.getCause());
|
||||
}
|
||||
|
||||
throw new RemoteAuthenticationException(response.error, response.errorMessage, response.cause);
|
||||
}
|
||||
}
|
||||
|
||||
private String request(URL url, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
return NetworkUtils.doGet(url, proxy);
|
||||
else
|
||||
return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
} catch (IOException e) {
|
||||
throw new ServerDisconnectException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,77 +190,27 @@ public class YggdrasilService {
|
||||
}
|
||||
}
|
||||
|
||||
static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(GameProfile.class, GameProfile.Serializer.INSTANCE)
|
||||
private class TextureResponse {
|
||||
public Map<TextureType, Texture> textures;
|
||||
}
|
||||
|
||||
private class AuthenticationResponse extends ErrorResponse {
|
||||
public String accessToken;
|
||||
public String clientToken;
|
||||
public GameProfile selectedProfile;
|
||||
public GameProfile[] availableProfiles;
|
||||
public User user;
|
||||
}
|
||||
|
||||
private class ErrorResponse {
|
||||
public String error;
|
||||
public String errorMessage;
|
||||
public String cause;
|
||||
}
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,35 +1,53 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class YggdrasilSession {
|
||||
|
||||
private final String accessToken;
|
||||
private String clientToken;
|
||||
private String accessToken;
|
||||
private GameProfile selectedProfile;
|
||||
private final GameProfile[] availableProfiles;
|
||||
private final User user;
|
||||
private GameProfile[] availableProfiles;
|
||||
private User user;
|
||||
|
||||
public YggdrasilSession(String accessToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user) {
|
||||
public YggdrasilSession(String clientToken, String accessToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user) {
|
||||
this.clientToken = clientToken;
|
||||
this.accessToken = accessToken;
|
||||
this.selectedProfile = selectedProfile;
|
||||
this.availableProfiles = availableProfiles;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getClientToken() {
|
||||
return clientToken;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return nullable (null if no character is selected)
|
||||
*/
|
||||
public GameProfile getSelectedProfile() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return nullable (null if the YggdrasilSession is loaded from storage)
|
||||
*/
|
||||
public GameProfile[] getAvailableProfiles() {
|
||||
return availableProfiles;
|
||||
}
|
||||
@ -38,53 +56,40 @@ public class YggdrasilSession {
|
||||
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 = tryCast(storage.get("uuid"), String.class);
|
||||
Optional<String> profileName = tryCast(storage.get("displayName"), String.class);
|
||||
GameProfile profile = null;
|
||||
if (profileId.isPresent() && profileName.isPresent()) {
|
||||
profile = new GameProfile(UUIDTypeAdapter.fromString(profileId.get()), profileName.get(),
|
||||
tryCast(storage.get("profileProperties"), Map.class).map(PropertyMap::fromMap).orElseGet(PropertyMap::new));
|
||||
}
|
||||
|
||||
return new YggdrasilSession(
|
||||
tryCast(storage.get("accessToken"), String.class).orElse(null),
|
||||
profile,
|
||||
null,
|
||||
tryCast(storage.get("userid"), String.class)
|
||||
.map(userId -> new User(userId, tryCast(storage.get("userProperties"), Map.class).map(PropertyMap::fromMap).orElse(null)))
|
||||
.orElse(null)
|
||||
);
|
||||
UUID uuid = tryCast(storage.get("uuid"), String.class).map(UUIDTypeAdapter::fromString).orElseThrow(() -> new IllegalArgumentException("uuid is missing"));
|
||||
String name = tryCast(storage.get("displayName"), String.class).orElseThrow(() -> new IllegalArgumentException("displayName is missing"));
|
||||
String clientToken = tryCast(storage.get("clientToken"), String.class).orElseThrow(() -> new IllegalArgumentException("clientToken is missing"));
|
||||
String accessToken = tryCast(storage.get("accessToken"), String.class).orElseThrow(() -> new IllegalArgumentException("accessToken is missing"));
|
||||
String userId = tryCast(storage.get("userid"), String.class).orElseThrow(() -> new IllegalArgumentException("userid is missing"));
|
||||
PropertyMap userProperties = tryCast(storage.get("userProperties"), Map.class).map(PropertyMap::fromMap).orElse(null);
|
||||
return new YggdrasilSession(clientToken, accessToken, new GameProfile(uuid, name), null, new User(userId, userProperties));
|
||||
}
|
||||
|
||||
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;
|
||||
if (selectedProfile == null)
|
||||
throw new IllegalStateException("No character is selected");
|
||||
if (user == null)
|
||||
throw new IllegalStateException("No user is specified");
|
||||
|
||||
return mapOf(
|
||||
pair("clientToken", clientToken),
|
||||
pair("accessToken", accessToken),
|
||||
pair("uuid", UUIDTypeAdapter.fromUUID(selectedProfile.getId())),
|
||||
pair("displayName", selectedProfile.getName()),
|
||||
pair("userid", user.getId()),
|
||||
pair("userProperties", user.getProperties()));
|
||||
}
|
||||
|
||||
public AuthInfo toAuthInfo() {
|
||||
if (selectedProfile == null)
|
||||
throw new IllegalStateException("No character is selected");
|
||||
if (user == null)
|
||||
throw new IllegalStateException("No user is specified");
|
||||
|
||||
return new AuthInfo(selectedProfile.getName(), selectedProfile.getId(), accessToken,
|
||||
Optional.ofNullable(user.getProperties()).map(GSON_PROPERTIES::toJson).orElse("{}"));
|
||||
}
|
||||
|
||||
private static final Gson GSON_PROPERTIES = new GsonBuilder().registerTypeAdapter(PropertyMap.class, PropertyMap.LegacySerializer.INSTANCE).create();
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
||||
import org.jackhuang.hmcl.game.AssetIndex;
|
||||
import org.jackhuang.hmcl.game.AssetIndexInfo;
|
||||
@ -82,7 +84,7 @@ public final class GameAssetRefreshTask extends TaskResult<Collection<Pair<File,
|
||||
if (Thread.interrupted())
|
||||
throw new InterruptedException();
|
||||
|
||||
res.add(new Pair<>(dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject), assetObject));
|
||||
res.add(pair(dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject), assetObject));
|
||||
updateProgress(++progress, index.getObjects().size());
|
||||
}
|
||||
setResult(res);
|
||||
|
@ -17,6 +17,9 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.launch;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
@ -191,8 +194,8 @@ public class DefaultLauncher extends Launcher {
|
||||
);
|
||||
}
|
||||
|
||||
private final Map<String, Supplier<Boolean>> forbiddens = Lang.mapOf(
|
||||
new Pair<String, Supplier<Boolean>>("-Xincgc", () -> options.getJava().getParsedVersion() >= JavaVersion.JAVA_9)
|
||||
private final Map<String, Supplier<Boolean>> forbiddens = mapOf(
|
||||
pair("-Xincgc", () -> options.getJava().getParsedVersion() >= JavaVersion.JAVA_9)
|
||||
);
|
||||
|
||||
protected Map<String, Supplier<Boolean>> getForbiddens() {
|
||||
@ -232,18 +235,18 @@ 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.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")),
|
||||
new Pair<>("${version_type}", version.getType().getId()),
|
||||
new Pair<>("${game_directory}", repository.getRunDirectory(version.getId()).getAbsolutePath()),
|
||||
new Pair<>("${user_type}", authInfo.getUserType().toString().toLowerCase()),
|
||||
new Pair<>("${assets_index_name}", version.getAssetIndex().getId()),
|
||||
new Pair<>("${user_properties}", authInfo.getUserProperties())
|
||||
return mapOf(
|
||||
pair("${auth_player_name}", authInfo.getUsername()),
|
||||
pair("${auth_session}", authInfo.getAccessToken()),
|
||||
pair("${auth_access_token}", authInfo.getAccessToken()),
|
||||
pair("${auth_uuid}", UUIDTypeAdapter.fromUUID(authInfo.getUUID())),
|
||||
pair("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())),
|
||||
pair("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")),
|
||||
pair("${version_type}", version.getType().getId()),
|
||||
pair("${game_directory}", repository.getRunDirectory(version.getId()).getAbsolutePath()),
|
||||
pair("${user_type}", "mojang"),
|
||||
pair("${assets_index_name}", version.getAssetIndex().getId()),
|
||||
pair("${user_properties}", authInfo.getUserProperties())
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,14 @@ import java.util.Objects;
|
||||
*/
|
||||
public class Pair<K, V> implements Map.Entry<K, V> {
|
||||
|
||||
public static <K, V> Pair<K, V> pair(K key, V value) {
|
||||
return new Pair<>(key, value);
|
||||
}
|
||||
|
||||
private K key;
|
||||
private V value;
|
||||
|
||||
@Deprecated
|
||||
public Pair(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
|
Loading…
Reference in New Issue
Block a user