authentication reconstruction
This commit is contained in:
@@ -22,7 +22,7 @@ import javafx.scene.image.Image;
|
|||||||
import org.jackhuang.hmcl.Main;
|
import org.jackhuang.hmcl.Main;
|
||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.ProfileTexture;
|
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||||
import org.jackhuang.hmcl.setting.Settings;
|
import org.jackhuang.hmcl.setting.Settings;
|
||||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
@@ -82,12 +82,9 @@ public final class AccountHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Image getSkin(YggdrasilAccount account, double scaleRatio) {
|
public static Image getSkin(YggdrasilAccount account, double scaleRatio) {
|
||||||
if (account.getSelectedProfile() == null)
|
if (account.getCharacter() == null)
|
||||||
return getDefaultSkin(account, scaleRatio);
|
return getDefaultSkin(account, scaleRatio);
|
||||||
String name = account.getSelectedProfile().getName();
|
File file = getSkinFile(account.getCharacter());
|
||||||
if (name == null)
|
|
||||||
return getDefaultSkin(account, scaleRatio);
|
|
||||||
File file = getSkinFile(name);
|
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
Image original = new Image("file:" + file.getAbsolutePath());
|
Image original = new Image("file:" + file.getAbsolutePath());
|
||||||
return new Image("file:" + file.getAbsolutePath(),
|
return new Image("file:" + file.getAbsolutePath(),
|
||||||
@@ -142,23 +139,33 @@ public final class AccountHelper {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute() throws Exception {
|
public void execute() throws Exception {
|
||||||
if (account.canLogIn() && (account.getSelectedProfile() == null || refresh))
|
if (!account.isLoggedIn() && (account.getCharacter() == null || refresh))
|
||||||
DialogController.logIn(account);
|
DialogController.logIn(account);
|
||||||
|
|
||||||
downloadSkin(account, account.getSelectedProfile(), refresh, proxy);
|
downloadSkin(account, refresh, proxy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh, Proxy proxy) throws Exception {
|
private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh, Proxy proxy) throws Exception {
|
||||||
account.clearCache();
|
account.clearCache();
|
||||||
|
|
||||||
if (profile == null) return;
|
Optional<Texture> texture = account.getSkin(profile);
|
||||||
String name = profile.getName();
|
|
||||||
if (name == null) return;
|
|
||||||
Optional<ProfileTexture> texture = account.getSkin(profile);
|
|
||||||
if (!texture.isPresent()) return;
|
if (!texture.isPresent()) return;
|
||||||
String url = texture.get().getUrl();
|
String url = texture.get().getUrl();
|
||||||
File file = getSkinFile(name);
|
File file = getSkinFile(profile.getName());
|
||||||
|
if (!refresh && file.exists())
|
||||||
|
return;
|
||||||
|
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void downloadSkin(YggdrasilAccount account, boolean refresh, Proxy proxy) throws Exception {
|
||||||
|
account.clearCache();
|
||||||
|
|
||||||
|
if (account.getCharacter() == null) return;
|
||||||
|
Optional<Texture> texture = account.getSkin();
|
||||||
|
if (!texture.isPresent()) return;
|
||||||
|
String url = texture.get().getUrl();
|
||||||
|
File file = getSkinFile(account.getCharacter());
|
||||||
if (!refresh && file.exists())
|
if (!refresh && file.exists())
|
||||||
return;
|
return;
|
||||||
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
||||||
|
|||||||
@@ -20,11 +20,17 @@ package org.jackhuang.hmcl.game;
|
|||||||
import com.jfoenix.concurrency.JFXUtilities;
|
import com.jfoenix.concurrency.JFXUtilities;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import org.jackhuang.hmcl.Main;
|
import org.jackhuang.hmcl.Main;
|
||||||
import org.jackhuang.hmcl.auth.*;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
|
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||||
|
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||||
|
import org.jackhuang.hmcl.auth.ServerDisconnectException;
|
||||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||||
import org.jackhuang.hmcl.launch.*;
|
import org.jackhuang.hmcl.launch.*;
|
||||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||||
import org.jackhuang.hmcl.setting.*;
|
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||||
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
|
import org.jackhuang.hmcl.setting.Settings;
|
||||||
|
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||||
import org.jackhuang.hmcl.task.*;
|
import org.jackhuang.hmcl.task.*;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
import org.jackhuang.hmcl.ui.DialogController;
|
import org.jackhuang.hmcl.ui.DialogController;
|
||||||
@@ -89,7 +95,7 @@ public final class LauncherHelper {
|
|||||||
.then(Task.of(Main.i18n("account.methods"), variables -> {
|
.then(Task.of(Main.i18n("account.methods"), variables -> {
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
variables.set("account", account.logIn(new SpecificCharacterSelector(Accounts.getCurrentCharacter(account)), Settings.INSTANCE.getProxy()));
|
variables.set("account", account.logIn());
|
||||||
} catch (ServerDisconnectException e) {
|
} catch (ServerDisconnectException e) {
|
||||||
if (account.canPlayOffline())
|
if (account.canPlayOffline())
|
||||||
variables.set("account", account.playOffline());
|
variables.set("account", account.playOffline());
|
||||||
@@ -302,7 +308,7 @@ public final class LauncherHelper {
|
|||||||
forbiddenTokens = Collections.emptyMap();
|
forbiddenTokens = Collections.emptyMap();
|
||||||
else
|
else
|
||||||
forbiddenTokens = Lang.mapOf(
|
forbiddenTokens = Lang.mapOf(
|
||||||
new Pair<>(authInfo.getAuthToken(), "<access token>"),
|
new Pair<>(authInfo.getAccessToken(), "<access token>"),
|
||||||
new Pair<>(authInfo.getUserId(), "<uuid>"),
|
new Pair<>(authInfo.getUserId(), "<uuid>"),
|
||||||
new Pair<>(authInfo.getUsername(), "<player>")
|
new Pair<>(authInfo.getUsername(), "<player>")
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,9 +21,15 @@ import com.google.gson.JsonParseException;
|
|||||||
import org.jackhuang.hmcl.Main;
|
import org.jackhuang.hmcl.Main;
|
||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||||
import org.jackhuang.hmcl.auth.OfflineAccount;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||||
import org.jackhuang.hmcl.auth.OfflineAccountFactory;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.*;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorBuildInfo;
|
||||||
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerResponse;
|
||||||
|
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||||
|
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||||
|
import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
|
||||||
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||||
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
import org.jackhuang.hmcl.util.*;
|
import org.jackhuang.hmcl.util.*;
|
||||||
|
|
||||||
@@ -32,7 +38,6 @@ import java.io.IOException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
@@ -46,7 +51,7 @@ public final class Accounts {
|
|||||||
|
|
||||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = Lang.mapOf(
|
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = Lang.mapOf(
|
||||||
new Pair<>(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
new Pair<>(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
||||||
new Pair<>(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory()),
|
new Pair<>(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
||||||
new Pair<>(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
|
new Pair<>(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -59,26 +64,8 @@ public final class Accounts {
|
|||||||
else return YGGDRASIL_ACCOUNT_KEY;
|
else return YGGDRASIL_ACCOUNT_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setCurrentCharacter(Account account, String character) {
|
|
||||||
account.getProperties().put("character", character);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean hasCurrentCharacter(Account account) {
|
|
||||||
return Lang.get(account.getProperties(), "character", String.class).isPresent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getCurrentCharacter(Account account) {
|
|
||||||
return Lang.get(account.getProperties(), "character", String.class)
|
|
||||||
.orElseThrow(() -> new IllegalArgumentException("Account " + account + " has not set current character."));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<String> getCurrentCharacter(Map<Object, Object> storage) {
|
|
||||||
return Lang.get(storage, "properties", Map.class)
|
|
||||||
.flatMap(properties -> Lang.get(properties, "character", String.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
static String getAccountId(Account account) {
|
static String getAccountId(Account account) {
|
||||||
return getAccountId(account.getUsername(), getCurrentCharacter(account));
|
return getAccountId(account.getUsername(), account.getCharacter());
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getAccountId(String username, String character) {
|
static String getAccountId(String username, String character) {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import javafx.scene.text.Font;
|
|||||||
import org.jackhuang.hmcl.Main;
|
import org.jackhuang.hmcl.Main;
|
||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorAccount;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||||
import org.jackhuang.hmcl.download.MojangDownloadProvider;
|
import org.jackhuang.hmcl.download.MojangDownloadProvider;
|
||||||
@@ -67,21 +67,23 @@ public class Settings {
|
|||||||
private Map<String, Account> accounts = new HashMap<>();
|
private Map<String, Account> accounts = new HashMap<>();
|
||||||
|
|
||||||
{
|
{
|
||||||
for (Map<Object, Object> settings : SETTINGS.getAccounts()) {
|
loadProxy();
|
||||||
Optional<String> characterName = Accounts.getCurrentCharacter(settings);
|
|
||||||
|
for (Iterator<Map<Object, Object>> iterator = SETTINGS.getAccounts().iterator(); iterator.hasNext(); ) {
|
||||||
|
Map<Object, Object> settings = iterator.next();
|
||||||
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(Lang.get(settings, "type", String.class).orElse(""));
|
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(Lang.get(settings, "type", String.class).orElse(""));
|
||||||
if (factory == null || !characterName.isPresent()) {
|
if (factory == null) {
|
||||||
// unrecognized account type, so remove it.
|
// unrecognized account type, so remove it.
|
||||||
SETTINGS.getAccounts().remove(settings);
|
iterator.remove();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Account account;
|
Account account;
|
||||||
try {
|
try {
|
||||||
account = factory.fromStorage(settings);
|
account = factory.fromStorage(settings, getProxy());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
SETTINGS.getAccounts().remove(settings);
|
|
||||||
// storage is malformed, delete.
|
// storage is malformed, delete.
|
||||||
|
iterator.remove();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,8 +103,6 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
|
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
|
||||||
|
|
||||||
loadProxy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Config initSettings() {
|
private Config initSettings() {
|
||||||
@@ -468,8 +468,6 @@ public class Settings {
|
|||||||
* PROFILES *
|
* PROFILES *
|
||||||
****************************************/
|
****************************************/
|
||||||
|
|
||||||
private Profile selectedProfile;
|
|
||||||
|
|
||||||
public Profile getSelectedProfile() {
|
public Profile getSelectedProfile() {
|
||||||
checkProfileMap();
|
checkProfileMap();
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ import javafx.scene.layout.StackPane;
|
|||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.auth.OfflineAccount;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorAccount;
|
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||||
import org.jackhuang.hmcl.game.AccountHelper;
|
import org.jackhuang.hmcl.game.AccountHelper;
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
import org.jackhuang.hmcl.setting.Accounts;
|
||||||
@@ -82,7 +82,7 @@ public final class AccountItem extends StackPane {
|
|||||||
chkSelected.getProperties().put("account", account);
|
chkSelected.getProperties().put("account", account);
|
||||||
setSelected(Settings.INSTANCE.getSelectedAccount() == account);
|
setSelected(Settings.INSTANCE.getSelectedAccount() == account);
|
||||||
|
|
||||||
lblUser.setText(Accounts.getCurrentCharacter(account));
|
lblUser.setText(account.getCharacter());
|
||||||
lblType.setText(AccountsPage.accountType(account));
|
lblType.setText(AccountsPage.accountType(account));
|
||||||
lblEmail.setText(account.getUsername());
|
lblEmail.setText(account.getUsername());
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ import javafx.scene.layout.StackPane;
|
|||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||||
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
||||||
import org.jackhuang.hmcl.auth.SpecificCharacterSelector;
|
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
|
||||||
import org.jackhuang.hmcl.setting.Settings;
|
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
|
|
||||||
@@ -64,7 +61,7 @@ public class AccountLoginPane extends StackPane {
|
|||||||
lblCreationWarning.setText("");
|
lblCreationWarning.setText("");
|
||||||
Task.ofResult("login", () -> {
|
Task.ofResult("login", () -> {
|
||||||
try {
|
try {
|
||||||
return oldAccount.logInWithPassword(new SpecificCharacterSelector(Accounts.getCurrentCharacter(oldAccount)), password, Settings.INSTANCE.getProxy());
|
return oldAccount.logInWithPassword(password);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ import javafx.scene.layout.HBox;
|
|||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.jackhuang.hmcl.Main;
|
import org.jackhuang.hmcl.Main;
|
||||||
import org.jackhuang.hmcl.auth.*;
|
import org.jackhuang.hmcl.auth.*;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorAccount;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||||
|
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||||
import org.jackhuang.hmcl.game.AccountHelper;
|
import org.jackhuang.hmcl.game.AccountHelper;
|
||||||
@@ -52,6 +53,7 @@ import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -153,6 +155,7 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
|||||||
private void addNewAccount() {
|
private void addNewAccount() {
|
||||||
txtUsername.setText("");
|
txtUsername.setText("");
|
||||||
txtPassword.setText("");
|
txtPassword.setText("");
|
||||||
|
lblCreationWarning.setText("");
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,20 +169,19 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
|||||||
int type = cboType.getSelectionModel().getSelectedIndex();
|
int type = cboType.getSelectionModel().getSelectedIndex();
|
||||||
String username = txtUsername.getText();
|
String username = txtUsername.getText();
|
||||||
String password = txtPassword.getText();
|
String password = txtPassword.getText();
|
||||||
|
String apiRoot = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem()).map(TwoLineListItem::getSubtitle).orElse(null);
|
||||||
progressBar.setVisible(true);
|
progressBar.setVisible(true);
|
||||||
lblCreationWarning.setText("");
|
lblCreationWarning.setText("");
|
||||||
Task.ofResult("create_account", () -> {
|
Task.ofResult("create_account", () -> {
|
||||||
Account account;
|
AccountFactory<?> factory;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 0: account = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY).fromUsername(username); break;
|
case 0: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY); break;
|
||||||
case 1: account = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY).fromUsername(username, password); break;
|
case 1: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY); break;
|
||||||
case 2: account = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY).fromUsername(username, password, cboServers.getSelectionModel().getSelectedItem().getSubtitle()); break;
|
case 2: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY); break;
|
||||||
default: throw new Error();
|
default: throw new Error();
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthInfo info = account.logIn(new CharacterSelector(), Settings.INSTANCE.getProxy());
|
return factory.create(new Selector(), username, password, apiRoot, Settings.INSTANCE.getProxy());
|
||||||
Accounts.setCurrentCharacter(account, info.getUsername());
|
|
||||||
return account;
|
|
||||||
}).finalized(Schedulers.javafx(), variables -> {
|
}).finalized(Schedulers.javafx(), variables -> {
|
||||||
Settings.INSTANCE.addAccount(variables.get("create_account"));
|
Settings.INSTANCE.addAccount(variables.get("create_account"));
|
||||||
dialog.close();
|
dialog.close();
|
||||||
@@ -236,7 +238,7 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
|||||||
else throw new Error(Main.i18n("account.methods.no_method") + ": " + account);
|
else throw new Error(Main.i18n("account.methods.no_method") + ": " + account);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CharacterSelector extends BorderPane implements MultiCharacterSelector {
|
private static class Selector extends BorderPane implements CharacterSelector {
|
||||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||||
private final JFXButton cancel = new JFXButton();
|
private final JFXButton cancel = new JFXButton();
|
||||||
|
|
||||||
@@ -263,7 +265,7 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
|||||||
@Override
|
@Override
|
||||||
public GameProfile select(Account account, List<GameProfile> names) throws NoSelectedCharacterException {
|
public GameProfile select(Account account, List<GameProfile> names) throws NoSelectedCharacterException {
|
||||||
if (!(account instanceof YggdrasilAccount))
|
if (!(account instanceof YggdrasilAccount))
|
||||||
return MultiCharacterSelector.DEFAULT.select(account, names);
|
return CharacterSelector.DEFAULT.select(account, names);
|
||||||
YggdrasilAccount yggdrasilAccount = (YggdrasilAccount) account;
|
YggdrasilAccount yggdrasilAccount = (YggdrasilAccount) account;
|
||||||
|
|
||||||
for (GameProfile profile : names) {
|
for (GameProfile profile : names) {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import javafx.geometry.Pos;
|
|||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorServerInfo;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import javafx.scene.control.ScrollPane;
|
|||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import org.jackhuang.hmcl.Main;
|
import org.jackhuang.hmcl.Main;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorServerInfo;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo;
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
import org.jackhuang.hmcl.setting.Accounts;
|
||||||
import org.jackhuang.hmcl.setting.Settings;
|
import org.jackhuang.hmcl.setting.Settings;
|
||||||
import org.jackhuang.hmcl.task.Schedulers;
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.ui;
|
|||||||
import com.jfoenix.concurrency.JFXUtilities;
|
import com.jfoenix.concurrency.JFXUtilities;
|
||||||
import org.jackhuang.hmcl.auth.Account;
|
import org.jackhuang.hmcl.auth.Account;
|
||||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||||
import org.jackhuang.hmcl.auth.MultiCharacterSelector;
|
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||||
import org.jackhuang.hmcl.task.SilentException;
|
import org.jackhuang.hmcl.task.SilentException;
|
||||||
|
|
||||||
@@ -48,6 +47,6 @@ public final class DialogController {
|
|||||||
latch.await();
|
latch.await();
|
||||||
return Optional.ofNullable(res.get()).orElseThrow(SilentException::new);
|
return Optional.ofNullable(res.get()).orElseThrow(SilentException::new);
|
||||||
}
|
}
|
||||||
return account.logIn(MultiCharacterSelector.DEFAULT);
|
return account.logIn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import org.jackhuang.hmcl.game.HMCLGameRepository;
|
|||||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||||
import org.jackhuang.hmcl.mod.Modpack;
|
import org.jackhuang.hmcl.mod.Modpack;
|
||||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||||
import org.jackhuang.hmcl.setting.Accounts;
|
|
||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Profiles;
|
import org.jackhuang.hmcl.setting.Profiles;
|
||||||
import org.jackhuang.hmcl.setting.Settings;
|
import org.jackhuang.hmcl.setting.Settings;
|
||||||
@@ -90,7 +89,7 @@ public final class LeftPaneController {
|
|||||||
accountItem.setVersionName(Main.i18n("account.missing"));
|
accountItem.setVersionName(Main.i18n("account.missing"));
|
||||||
accountItem.setGameVersion(Main.i18n("message.unknown"));
|
accountItem.setGameVersion(Main.i18n("message.unknown"));
|
||||||
} else {
|
} else {
|
||||||
accountItem.setVersionName(Accounts.getCurrentCharacter(it));
|
accountItem.setVersionName(it.getCharacter());
|
||||||
accountItem.setGameVersion(AccountsPage.accountType(it));
|
accountItem.setGameVersion(AccountsPage.accountType(it));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.construct;
|
|||||||
import com.jfoenix.validation.base.ValidatorBase;
|
import com.jfoenix.validation.base.ValidatorBase;
|
||||||
import javafx.beans.NamedArg;
|
import javafx.beans.NamedArg;
|
||||||
import javafx.scene.control.TextInputControl;
|
import javafx.scene.control.TextInputControl;
|
||||||
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
|
|
||||||
public class NumberValidator extends ValidatorBase {
|
public class NumberValidator extends ValidatorBase {
|
||||||
@@ -51,11 +52,6 @@ public class NumberValidator extends ValidatorBase {
|
|||||||
if (StringUtils.isBlank(textField.getText()))
|
if (StringUtils.isBlank(textField.getText()))
|
||||||
hasErrors.set(!nullable);
|
hasErrors.set(!nullable);
|
||||||
else
|
else
|
||||||
try {
|
hasErrors.set(Lang.toIntOrNull(textField.getText()) == null);
|
||||||
Integer.parseInt(textField.getText());
|
|
||||||
hasErrors.set(false);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
hasErrors.set(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.auth;
|
package org.jackhuang.hmcl.auth;
|
||||||
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@@ -33,6 +31,12 @@ public abstract class Account {
|
|||||||
*/
|
*/
|
||||||
public abstract String getUsername();
|
public abstract String getUsername();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return the character name
|
||||||
|
*/
|
||||||
|
public abstract String getCharacter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the UUID
|
* @return the UUID
|
||||||
*/
|
*/
|
||||||
@@ -40,28 +44,12 @@ public abstract class Account {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* log in.
|
* log in.
|
||||||
* @param selector selects a character
|
|
||||||
* @return the specific player's info.
|
* @return the specific player's info.
|
||||||
* @throws AuthenticationException if server error occurred.
|
* @throws AuthenticationException if server error occurred.
|
||||||
*/
|
*/
|
||||||
public AuthInfo logIn(MultiCharacterSelector selector) throws AuthenticationException {
|
public abstract AuthInfo logIn() throws AuthenticationException;
|
||||||
return logIn(selector, Proxy.NO_PROXY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public abstract AuthInfo logInWithPassword(String password) 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 boolean canPlayOffline();
|
public abstract boolean canPlayOffline();
|
||||||
|
|
||||||
@@ -73,24 +61,7 @@ public abstract class Account {
|
|||||||
|
|
||||||
public abstract void logOut();
|
public abstract void logOut();
|
||||||
|
|
||||||
protected abstract Map<Object, Object> toStorageImpl();
|
public abstract Map<Object, Object> toStorage();
|
||||||
|
|
||||||
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 void clearCache();
|
public abstract void clearCache();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.auth;
|
package org.jackhuang.hmcl.auth;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import java.net.Proxy;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,23 +26,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public abstract class AccountFactory<T extends Account> {
|
public abstract class AccountFactory<T extends Account> {
|
||||||
|
|
||||||
public final T fromUsername(String username) {
|
public abstract T create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException;
|
||||||
return fromUsername(username, "", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final T fromUsername(String username, String password) {
|
public abstract T fromStorage(Map<Object, Object> storage, Proxy proxy);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.auth;
|
package org.jackhuang.hmcl.auth;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
|
||||||
import org.jackhuang.hmcl.game.Arguments;
|
import org.jackhuang.hmcl.game.Arguments;
|
||||||
import org.jackhuang.hmcl.util.Immutable;
|
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 username;
|
||||||
private final String userId;
|
private final String userId;
|
||||||
private final String authToken;
|
private final String accessToken;
|
||||||
private final UserType userType;
|
private final UserType userType;
|
||||||
private final String userProperties;
|
private final String userProperties;
|
||||||
private final String userPropertyMap;
|
|
||||||
private final Arguments arguments;
|
private final Arguments arguments;
|
||||||
|
|
||||||
public AuthInfo(String username, String userId, String authToken) {
|
public AuthInfo(String username, String userId, String accessToken) {
|
||||||
this(username, userId, authToken, UserType.LEGACY);
|
this(username, userId, accessToken, UserType.LEGACY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthInfo(String username, String userId, String authToken, UserType userType) {
|
public AuthInfo(String username, String userId, String accessToken, UserType userType) {
|
||||||
this(username, userId, authToken, userType, "{}");
|
this(username, userId, accessToken, userType, "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties) {
|
public AuthInfo(String username, String userId, String accessToken, UserType userType, String userProperties) {
|
||||||
this(username, userId, authToken, userType, userProperties, "{}");
|
this(username, userId, accessToken, userType, userProperties, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties, String userPropertyMap) {
|
public AuthInfo(String username, String userId, String accessToken, UserType userType, String userProperties, Arguments arguments) {
|
||||||
this(username, userId, authToken, userType, userProperties, userPropertyMap, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties, String userPropertyMap, Arguments arguments) {
|
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.authToken = authToken;
|
this.accessToken = accessToken;
|
||||||
this.userType = userType;
|
this.userType = userType;
|
||||||
this.userProperties = userProperties;
|
this.userProperties = userProperties;
|
||||||
this.userPropertyMap = userPropertyMap;
|
|
||||||
this.arguments = arguments;
|
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() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
@@ -75,8 +63,8 @@ public final class AuthInfo {
|
|||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAuthToken() {
|
public String getAccessToken() {
|
||||||
return authToken;
|
return accessToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserType getUserType() {
|
public UserType getUserType() {
|
||||||
@@ -93,21 +81,11 @@ public final class AuthInfo {
|
|||||||
return userProperties;
|
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() {
|
public Arguments getArguments() {
|
||||||
return arguments;
|
return arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthInfo setArguments(Arguments 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
|
* 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..
|
* 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.
|
* Select one of {@code names} GameProfiles to login.
|
||||||
@@ -35,5 +35,5 @@ public interface MultiCharacterSelector {
|
|||||||
*/
|
*/
|
||||||
GameProfile select(Account account, List<GameProfile> names) throws NoSelectedCharacterException;
|
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
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public final class InvalidCredentialsException extends AuthenticationException {
|
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.
|
* throws if wrong password.
|
||||||
*/
|
*/
|
||||||
public class InvalidPasswordException extends AuthenticationException {
|
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;
|
package org.jackhuang.hmcl.auth;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public class InvalidTokenException extends AuthenticationException {
|
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;
|
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.
|
* valid character.
|
||||||
*
|
*
|
||||||
* @see org.jackhuang.hmcl.auth.MultiCharacterSelector
|
* @see CharacterSelector
|
||||||
* @author huangyuhui
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public final class NoSelectedCharacterException extends AuthenticationException {
|
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.
|
* Select character by name.
|
||||||
*/
|
*/
|
||||||
public class SpecificCharacterSelector implements MultiCharacterSelector {
|
public class SpecificCharacterSelector implements CharacterSelector {
|
||||||
private final String id;
|
private final String id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,18 +15,20 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
* 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.AuthInfo;
|
||||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
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.game.Arguments;
|
||||||
import org.jackhuang.hmcl.task.GetTask;
|
import org.jackhuang.hmcl.task.GetTask;
|
||||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||||
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
@@ -35,21 +37,30 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
|||||||
private final String serverBaseURL;
|
private final String serverBaseURL;
|
||||||
private final ExceptionalSupplier<String, ?> injectorJarPath;
|
private final ExceptionalSupplier<String, ?> injectorJarPath;
|
||||||
|
|
||||||
public AuthlibInjectorAccount(ExceptionalSupplier<String, ?> injectorJarPath, String serverBaseURL, String username) {
|
protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier<String, ?> injectorJarPath, String username, String clientToken, String character, YggdrasilSession session) {
|
||||||
super(serverBaseURL + "authserver/", serverBaseURL + "sessionserver/", username);
|
super(service, username, clientToken, character, session);
|
||||||
|
|
||||||
this.injectorJarPath = injectorJarPath;
|
this.injectorJarPath = injectorJarPath;
|
||||||
this.serverBaseURL = serverBaseURL;
|
this.serverBaseURL = serverBaseURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.
|
// 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));
|
GetTask getTask = new GetTask(NetworkUtils.toURL(serverBaseURL));
|
||||||
AtomicBoolean flag = new AtomicBoolean(true);
|
AtomicBoolean flag = new AtomicBoolean(true);
|
||||||
Thread thread = Lang.thread(() -> flag.set(getTask.test()));
|
Thread thread = Lang.thread(() -> flag.set(getTask.test()));
|
||||||
|
|
||||||
AuthInfo info = super.logIn(selector, proxy);
|
AuthInfo info = supplier.get();
|
||||||
try {
|
try {
|
||||||
thread.join();
|
thread.join();
|
||||||
|
|
||||||
@@ -66,32 +77,9 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException {
|
public Map<Object, Object> toStorage() {
|
||||||
// Authlib Injector recommends launchers to pre-fetch the server basic information before launched the game to save time.
|
Map<Object, Object> map = super.toStorage();
|
||||||
GetTask getTask = new GetTask(NetworkUtils.toURL(serverBaseURL));
|
map.put("serverBaseURL", 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);
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,5 +87,4 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
|||||||
return serverBaseURL;
|
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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
* 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 com.google.gson.JsonParseException;
|
||||||
import org.jackhuang.hmcl.util.Immutable;
|
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
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
* 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 {
|
public class AuthlibInjectorServerInfo {
|
||||||
private final String serverIp;
|
private final String serverIp;
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
* 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 {
|
public class AuthlibInjectorServerResponse {
|
||||||
|
|
||||||
@@ -15,14 +15,16 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
* 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.Lang;
|
||||||
import org.jackhuang.hmcl.util.Pair;
|
import org.jackhuang.hmcl.util.Pair;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||||
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -58,7 +60,12 @@ public class OfflineAccount extends Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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))
|
if (StringUtils.isBlank(username) || StringUtils.isBlank(uuid))
|
||||||
throw new AuthenticationException("Username cannot be empty");
|
throw new AuthenticationException("Username cannot be empty");
|
||||||
|
|
||||||
@@ -66,8 +73,8 @@ public class OfflineAccount extends Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException {
|
public AuthInfo logInWithPassword(String password) throws AuthenticationException {
|
||||||
return logIn(selector, proxy);
|
return logIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -86,7 +93,7 @@ public class OfflineAccount extends Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Object, Object> toStorageImpl() {
|
public Map<Object, Object> toStorage() {
|
||||||
return Lang.mapOf(
|
return Lang.mapOf(
|
||||||
new Pair<>("uuid", uuid),
|
new Pair<>("uuid", uuid),
|
||||||
new Pair<>("username", username)
|
new Pair<>("username", username)
|
||||||
@@ -15,10 +15,14 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
* 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.DigestUtils;
|
||||||
|
import org.jackhuang.hmcl.util.Lang;
|
||||||
|
|
||||||
|
import java.net.Proxy;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,21 +36,18 @@ public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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));
|
return new OfflineAccount(username, getUUIDFromUserName(username));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OfflineAccount fromStorageImpl(Map<Object, Object> storage) {
|
public OfflineAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
|
||||||
Object username = storage.get("username");
|
String username = Lang.get(storage, "username", String.class)
|
||||||
if (username == null || !(username instanceof String))
|
.orElseThrow(() -> new IllegalStateException("Offline account configuration malformed."));
|
||||||
throw new IllegalStateException("Offline account configuration malformed.");
|
String uuid = Lang.get(storage, "uuid", String.class)
|
||||||
|
.orElse(getUUIDFromUserName(username));
|
||||||
|
|
||||||
Object uuid = storage.get("uuid");
|
return new OfflineAccount(username, uuid);
|
||||||
if (uuid == null || !(uuid instanceof String))
|
|
||||||
uuid = getUUIDFromUserName((String) username);
|
|
||||||
|
|
||||||
return new OfflineAccount((String) username, (String) uuid);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getUUIDFromUserName(String username) {
|
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
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public final class AuthenticationResponse {
|
final class AuthenticationResponse extends ErrorResponse {
|
||||||
|
|
||||||
private final String accessToken;
|
private final String accessToken;
|
||||||
private final String clientToken;
|
private final String clientToken;
|
||||||
private final GameProfile selectedProfile;
|
private final GameProfile selectedProfile;
|
||||||
private final GameProfile[] availableProfiles;
|
private final GameProfile[] availableProfiles;
|
||||||
private final User user;
|
private final User user;
|
||||||
private final String error;
|
|
||||||
private final String errorMessage;
|
|
||||||
private final String cause;
|
|
||||||
|
|
||||||
public AuthenticationResponse() {
|
public AuthenticationResponse() {
|
||||||
this(null, null, null, null, null, null, null, null);
|
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) {
|
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.accessToken = accessToken;
|
||||||
this.clientToken = clientToken;
|
this.clientToken = clientToken;
|
||||||
this.selectedProfile = selectedProfile;
|
this.selectedProfile = selectedProfile;
|
||||||
this.availableProfiles = availableProfiles;
|
this.availableProfiles = availableProfiles;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.error = error;
|
|
||||||
this.errorMessage = errorMessage;
|
|
||||||
this.cause = cause;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAccessToken() {
|
public String getAccessToken() {
|
||||||
@@ -67,16 +63,4 @@ public final class AuthenticationResponse {
|
|||||||
return user;
|
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;
|
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
|
import org.jackhuang.hmcl.auth.UserType;
|
||||||
import org.jackhuang.hmcl.util.Immutable;
|
import org.jackhuang.hmcl.util.Immutable;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
@@ -40,7 +41,11 @@ public final class GameProfile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public GameProfile(UUID id, String name) {
|
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) {
|
public GameProfile(UUID id, String name, PropertyMap properties, boolean legacy) {
|
||||||
@@ -66,6 +71,10 @@ public final class GameProfile {
|
|||||||
return legacy;
|
return legacy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UserType getUserType() {
|
||||||
|
return UserType.fromLegacy(isLegacy());
|
||||||
|
}
|
||||||
|
|
||||||
public static class Serializer implements JsonSerializer<GameProfile>, JsonDeserializer<GameProfile> {
|
public static class Serializer implements JsonSerializer<GameProfile>, JsonDeserializer<GameProfile> {
|
||||||
|
|
||||||
public static final Serializer INSTANCE = new Serializer();
|
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;
|
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import org.jackhuang.hmcl.util.Lang;
|
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
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() {
|
public static PropertyMap fromMap(Map<?, ?> map) {
|
||||||
List<Map<String, String>> properties = new ArrayList<>();
|
PropertyMap propertyMap = new PropertyMap();
|
||||||
for (Property profileProperty : values()) {
|
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||||
Map<String, String> property = new HashMap<>();
|
if (entry.getKey() instanceof String && entry.getValue() instanceof String)
|
||||||
property.put("name", profileProperty.getName());
|
propertyMap.put((String) entry.getKey(), (String) entry.getValue());
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
|
return propertyMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Serializer implements JsonSerializer<PropertyMap>, JsonDeserializer<PropertyMap> {
|
public static class Serializer implements JsonSerializer<PropertyMap>, JsonDeserializer<PropertyMap> {
|
||||||
@@ -57,19 +44,11 @@ public final class PropertyMap extends HashMap<String, Property> {
|
|||||||
@Override
|
@Override
|
||||||
public PropertyMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
public PropertyMap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
PropertyMap result = new PropertyMap();
|
PropertyMap result = new PropertyMap();
|
||||||
if (json instanceof JsonObject) {
|
for (JsonElement element : json.getAsJsonArray())
|
||||||
for (Map.Entry<String, JsonElement> entry : ((JsonObject) json).entrySet())
|
if (element instanceof JsonObject) {
|
||||||
if (entry.getValue() instanceof JsonArray)
|
JsonObject object = (JsonObject) element;
|
||||||
for (JsonElement element : (JsonArray) entry.getValue())
|
result.put(object.get("name").getAsString(), object.get("value").getAsString());
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -77,29 +56,14 @@ public final class PropertyMap extends HashMap<String, Property> {
|
|||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(PropertyMap src, Type typeOfSrc, JsonSerializationContext context) {
|
public JsonElement serialize(PropertyMap src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
JsonArray result = new JsonArray();
|
JsonArray result = new JsonArray();
|
||||||
for (Property property : src.values()) {
|
for (Map.Entry<String, String> entry : src.entrySet()) {
|
||||||
JsonObject object = new JsonObject();
|
JsonObject object = new JsonObject();
|
||||||
object.addProperty("name", property.getName());
|
object.addProperty("name", entry.getKey());
|
||||||
object.addProperty("value", property.getValue());
|
object.addProperty("value", entry.getValue());
|
||||||
result.add(object);
|
result.add(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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;
|
import java.util.Map;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
public final class ProfileTexture {
|
public final class Texture {
|
||||||
|
|
||||||
private final String url;
|
private final String url;
|
||||||
private final Map<String, String> metadata;
|
private final Map<String, String> metadata;
|
||||||
|
|
||||||
public ProfileTexture() {
|
public Texture() {
|
||||||
this(null, null);
|
this(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProfileTexture(String url, Map<String, String> metadata) {
|
public Texture(String url, Map<String, String> metadata) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.metadata = metadata;
|
this.metadata = metadata;
|
||||||
}
|
}
|
||||||
@@ -46,8 +46,4 @@ public final class ProfileTexture {
|
|||||||
else
|
else
|
||||||
return metadata.get(key);
|
return metadata.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Type {
|
|
||||||
SKIN, CAPE
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -27,13 +27,13 @@ import java.util.UUID;
|
|||||||
public final class TextureResponse {
|
public final class TextureResponse {
|
||||||
private final UUID profileId;
|
private final UUID profileId;
|
||||||
private final String profileName;
|
private final String profileName;
|
||||||
private final Map<ProfileTexture.Type, ProfileTexture> textures;
|
private final Map<TextureType, Texture> textures;
|
||||||
|
|
||||||
public TextureResponse() {
|
public TextureResponse() {
|
||||||
this(UUID.randomUUID(), "", Collections.emptyMap());
|
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.profileId = profileId;
|
||||||
this.profileName = profileName;
|
this.profileName = profileName;
|
||||||
this.textures = textures;
|
this.textures = textures;
|
||||||
@@ -47,7 +47,7 @@ public final class TextureResponse {
|
|||||||
return profileName;
|
return profileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<ProfileTexture.Type, ProfileTexture> getTextures() {
|
public Map<TextureType, Texture> getTextures() {
|
||||||
return textures == null ? null : Collections.unmodifiableMap(textures);
|
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;
|
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.auth.*;
|
||||||
import org.jackhuang.hmcl.util.Charsets;
|
|
||||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Proxy;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author huang
|
* @author huangyuhui
|
||||||
*/
|
*/
|
||||||
public class YggdrasilAccount extends Account {
|
public class YggdrasilAccount extends Account {
|
||||||
|
|
||||||
private final String username;
|
private final String username;
|
||||||
private String password;
|
private final YggdrasilService service;
|
||||||
private String userId;
|
|
||||||
private String accessToken = null;
|
|
||||||
private String clientToken = randomToken();
|
|
||||||
private boolean isOnline = false;
|
private boolean isOnline = false;
|
||||||
private PropertyMap userProperties = new PropertyMap();
|
private YggdrasilSession session;
|
||||||
private GameProfile selectedProfile = null;
|
private final String clientToken;
|
||||||
private GameProfile[] profiles;
|
private String character;
|
||||||
private UserType userType = UserType.LEGACY;
|
|
||||||
|
|
||||||
public YggdrasilAccount(String baseAuthServer, String baseSessionServer, String username) {
|
protected YggdrasilAccount(YggdrasilService service, String username, String clientToken, String character, YggdrasilSession session) {
|
||||||
this.baseAuthServer = baseAuthServer;
|
this.service = service;
|
||||||
this.baseSessionServer = baseSessionServer;
|
|
||||||
this.baseProfile = baseSessionServer + "session/minecraft/profile/";
|
|
||||||
this.username = username;
|
this.username = username;
|
||||||
|
this.session = session;
|
||||||
|
this.clientToken = clientToken;
|
||||||
|
this.character = character;
|
||||||
|
|
||||||
this.routeAuthenticate = NetworkUtils.toURL(baseAuthServer + "authenticate");
|
if (session == null || session.getSelectedProfile() == null || StringUtils.isBlank(session.getAccessToken()))
|
||||||
this.routeRefresh = NetworkUtils.toURL(baseAuthServer + "refresh");
|
this.session = null;
|
||||||
this.routeValidate = NetworkUtils.toURL(baseAuthServer + "validate");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -64,146 +52,68 @@ public class YggdrasilAccount extends Account {
|
|||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPassword(String password) {
|
@Override
|
||||||
this.password = password;
|
public String getCharacter() {
|
||||||
}
|
return session.getSelectedProfile().getName();
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLoggedIn() {
|
public boolean isLoggedIn() {
|
||||||
return StringUtils.isNotBlank(accessToken);
|
return session != null && StringUtils.isNotBlank(session.getAccessToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canPlayOnline() {
|
public boolean canPlayOnline() {
|
||||||
return isLoggedIn() && selectedProfile != null && isOnline;
|
return isLoggedIn() && session.getSelectedProfile() != null && isOnline;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean canLogIn() {
|
|
||||||
return !canPlayOnline() && StringUtils.isNotBlank(username)
|
|
||||||
&& (StringUtils.isNotBlank(password) || StringUtils.isNotBlank(accessToken));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthInfo logIn(MultiCharacterSelector selector, Proxy proxy) throws AuthenticationException {
|
public AuthInfo logIn() throws AuthenticationException {
|
||||||
if (canPlayOnline())
|
if (!canPlayOnline()) {
|
||||||
return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties));
|
logInWithToken();
|
||||||
else {
|
selectProfile(new SpecificCharacterSelector(character));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
return toAuthInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException {
|
public final AuthInfo logInWithPassword(String password) throws AuthenticationException {
|
||||||
logInWithPassword0(password, proxy);
|
return logInWithPassword(password, new SpecificCharacterSelector(character));
|
||||||
if (!isLoggedIn())
|
}
|
||||||
throw new AuthenticationException("Wrong password for account " + username);
|
|
||||||
|
|
||||||
if (selectedProfile == null) {
|
protected AuthInfo logInWithPassword(String password, CharacterSelector selector) throws AuthenticationException {
|
||||||
if (profiles == null || profiles.length <= 0)
|
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);
|
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 {
|
private void logInWithToken() throws AuthenticationException {
|
||||||
if (StringUtils.isNotBlank(accessToken)) {
|
if (service.validate(session.getAccessToken(), clientToken)) {
|
||||||
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)) {
|
|
||||||
isOnline = true;
|
isOnline = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logIn1(routeRefresh, new RefreshRequest(accessToken, clientToken, getSelectedProfile()), proxy);
|
session = service.refresh(session.getAccessToken(), clientToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void logInWithPassword0(String password, Proxy proxy) throws AuthenticationException {
|
private AuthInfo toAuthInfo() {
|
||||||
logIn1(routeAuthenticate, new AuthenticationRequest(username, password, clientToken), proxy);
|
GameProfile profile = session.getSelectedProfile();
|
||||||
}
|
|
||||||
|
|
||||||
private void logIn1(URL url, Object input, Proxy proxy) throws AuthenticationException {
|
return new AuthInfo(profile.getName(), UUIDTypeAdapter.fromUUID(profile.getId()), session.getAccessToken(), profile.getUserType(),
|
||||||
AuthenticationResponse response = makeRequest(url, input, proxy)
|
YggdrasilService.GSON.toJson(Optional.ofNullable(session.getUser()).map(User::getProperties).orElseGet(PropertyMap::new)));
|
||||||
.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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canPlayOffline() {
|
public boolean canPlayOffline() {
|
||||||
return isLoggedIn() && getSelectedProfile() != null && !canPlayOnline();
|
return isLoggedIn() && session.getSelectedProfile() != null && !canPlayOnline();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -211,117 +121,51 @@ public class YggdrasilAccount extends Account {
|
|||||||
if (!canPlayOffline())
|
if (!canPlayOffline())
|
||||||
throw new IllegalStateException("Current account " + this + " cannot play offline.");
|
throw new IllegalStateException("Current account " + this + " cannot play offline.");
|
||||||
|
|
||||||
return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties));
|
return toAuthInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void logOut() {
|
public void logOut() {
|
||||||
password = null;
|
|
||||||
userId = null;
|
|
||||||
accessToken = null;
|
|
||||||
isOnline = false;
|
isOnline = false;
|
||||||
userProperties.clear();
|
session = null;
|
||||||
profiles = null;
|
|
||||||
selectedProfile = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<Object, Object> toStorageImpl() {
|
public Map<Object, Object> toStorage() {
|
||||||
HashMap<Object, Object> result = new HashMap<>();
|
HashMap<Object, Object> storage = new HashMap<>();
|
||||||
|
|
||||||
result.put(STORAGE_KEY_USER_NAME, getUsername());
|
storage.put("username", getUsername());
|
||||||
result.put(STORAGE_KEY_CLIENT_TOKEN, getClientToken());
|
storage.put("clientToken", clientToken);
|
||||||
if (getCurrentCharacterName() != null)
|
storage.put("character", character);
|
||||||
result.put(STORAGE_KEY_USER_ID, getCurrentCharacterName());
|
if (session != null)
|
||||||
if (!userProperties.isEmpty())
|
storage.putAll(session.toStorage());
|
||||||
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());
|
|
||||||
|
|
||||||
if (!profile.getProperties().isEmpty())
|
return storage;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getUUID() {
|
public UUID getUUID() {
|
||||||
if (getSelectedProfile() == null)
|
if (session == null)
|
||||||
return null;
|
return null;
|
||||||
else
|
else
|
||||||
return getSelectedProfile().getId();
|
return session.getSelectedProfile().getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ProfileTexture> getSkin(GameProfile profile) throws IOException, JsonParseException {
|
public Optional<Texture> getSkin() throws AuthenticationException {
|
||||||
if (StringUtils.isBlank(userId))
|
return getSkin(session.getSelectedProfile());
|
||||||
throw new IllegalStateException("Not logged in");
|
}
|
||||||
|
|
||||||
Property textureProperty;
|
public Optional<Texture> getSkin(GameProfile profile) throws AuthenticationException {
|
||||||
|
if (!service.getTextures(profile).isPresent()) {
|
||||||
if (profile.getProperties().containsKey("textures"))
|
session.setAvailableProfile(profile = service.getCompleteGameProfile(profile.getId()));
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureResponse texture;
|
return service.getTextures(profile).map(map -> map.get(TextureType.SKIN));
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearCache() {
|
public void clearCache() {
|
||||||
Optional.ofNullable(getSelectedProfile())
|
Optional.ofNullable(session)
|
||||||
|
.map(YggdrasilSession::getSelectedProfile)
|
||||||
.map(GameProfile::getProperties)
|
.map(GameProfile::getProperties)
|
||||||
.ifPresent(it -> it.remove("texture"));
|
.ifPresent(it -> it.remove("texture"));
|
||||||
}
|
}
|
||||||
@@ -331,30 +175,4 @@ public class YggdrasilAccount extends Account {
|
|||||||
return "YggdrasilAccount[username=" + getUsername() + "]";
|
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;
|
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
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.Lang;
|
||||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.net.Proxy;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -33,50 +34,40 @@ import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*;
|
|||||||
*/
|
*/
|
||||||
public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||||
|
|
||||||
private final String baseAuthServer;
|
private final YggdrasilProvider provider;
|
||||||
private final String baseSessionServer;
|
|
||||||
|
|
||||||
public YggdrasilAccountFactory() {
|
public YggdrasilAccountFactory(YggdrasilProvider provider) {
|
||||||
this(MOJANG_AUTH_SERVER, MOJANG_SESSION_SERVER);
|
this.provider = provider;
|
||||||
}
|
|
||||||
|
|
||||||
public YggdrasilAccountFactory(String baseAuthServer, String baseSessionServer) {
|
|
||||||
this.baseAuthServer = baseAuthServer;
|
|
||||||
this.baseSessionServer = baseSessionServer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public YggdrasilAccount fromUsername(String username, String password, Object additionalData) {
|
public YggdrasilAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException {
|
||||||
YggdrasilAccount account = new YggdrasilAccount(MOJANG_AUTH_SERVER, MOJANG_SESSION_SERVER, username);
|
Objects.requireNonNull(selector);
|
||||||
account.setPassword(password);
|
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;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public YggdrasilAccount fromStorageImpl(Map<Object, Object> storage) {
|
public YggdrasilAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
|
||||||
String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class)
|
Objects.requireNonNull(storage);
|
||||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME));
|
Objects.requireNonNull(proxy);
|
||||||
|
|
||||||
YggdrasilAccount account = new YggdrasilAccount(baseAuthServer, baseSessionServer, username);
|
String username = Lang.get(storage, "username", String.class)
|
||||||
account.setUserId(Lang.get(storage, STORAGE_KEY_USER_ID, String.class).orElse(username));
|
.orElseThrow(() -> new IllegalArgumentException("storage does not have username"));
|
||||||
account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class).orElse(null));
|
String clientToken = Lang.get(storage, "clientToken", String.class)
|
||||||
account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class)
|
.orElseThrow(() -> new IllegalArgumentException("storage does not have client token."));
|
||||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_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)
|
return new YggdrasilAccount(new YggdrasilService(provider, proxy), username, clientToken, character, YggdrasilSession.fromStorage(storage));
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String MOJANG_AUTH_SERVER = "https://authserver.mojang.com/";
|
public static String randomToken() {
|
||||||
private static final String MOJANG_SESSION_SERVER = "https://sessionserver.mojang.com/";
|
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 proxyUser;
|
||||||
private String proxyPass;
|
private String proxyPass;
|
||||||
private boolean noGeneratedJVMArgs;
|
private boolean noGeneratedJVMArgs;
|
||||||
private String precalledCommand;
|
private String preLaunchCommand;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The game directory
|
* The game directory
|
||||||
@@ -188,8 +188,8 @@ public class LaunchOptions implements Serializable {
|
|||||||
/**
|
/**
|
||||||
* Called command line before launching the game.
|
* Called command line before launching the game.
|
||||||
*/
|
*/
|
||||||
public String getPrecalledCommand() {
|
public String getPreLaunchCommand() {
|
||||||
return precalledCommand;
|
return preLaunchCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
@@ -296,7 +296,7 @@ public class LaunchOptions implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Builder setPrecalledCommand(String precalledCommand) {
|
public Builder setPrecalledCommand(String precalledCommand) {
|
||||||
options.precalledCommand = precalledCommand;
|
options.preLaunchCommand = precalledCommand;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -240,8 +240,8 @@ public class DefaultLauncher extends Launcher {
|
|||||||
protected Map<String, String> getConfigurations() {
|
protected Map<String, String> getConfigurations() {
|
||||||
return Lang.mapOf(
|
return Lang.mapOf(
|
||||||
new Pair<>("${auth_player_name}", authInfo.getUsername()),
|
new Pair<>("${auth_player_name}", authInfo.getUsername()),
|
||||||
new Pair<>("${auth_session}", authInfo.getAuthToken()),
|
new Pair<>("${auth_session}", authInfo.getAccessToken()),
|
||||||
new Pair<>("${auth_access_token}", authInfo.getAuthToken()),
|
new Pair<>("${auth_access_token}", authInfo.getAccessToken()),
|
||||||
new Pair<>("${auth_uuid}", authInfo.getUserId()),
|
new Pair<>("${auth_uuid}", authInfo.getUserId()),
|
||||||
new Pair<>("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())),
|
new Pair<>("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())),
|
||||||
new Pair<>("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")),
|
new Pair<>("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")),
|
||||||
@@ -262,8 +262,8 @@ public class DefaultLauncher extends Launcher {
|
|||||||
|
|
||||||
decompressNatives(nativeFolder);
|
decompressNatives(nativeFolder);
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(options.getPrecalledCommand()))
|
if (StringUtils.isNotBlank(options.getPreLaunchCommand()))
|
||||||
Runtime.getRuntime().exec(options.getPrecalledCommand()).waitFor();
|
Runtime.getRuntime().exec(options.getPreLaunchCommand()).waitFor();
|
||||||
|
|
||||||
Process process;
|
Process process;
|
||||||
try {
|
try {
|
||||||
@@ -306,8 +306,8 @@ public class DefaultLauncher extends Launcher {
|
|||||||
writer.write("cd /D %APPDATA%");
|
writer.write("cd /D %APPDATA%");
|
||||||
writer.newLine();
|
writer.newLine();
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotBlank(options.getPrecalledCommand())) {
|
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
|
||||||
writer.write(options.getPrecalledCommand());
|
writer.write(options.getPreLaunchCommand());
|
||||||
writer.newLine();
|
writer.newLine();
|
||||||
}
|
}
|
||||||
writer.write(StringUtils.makeCommand(generateCommandLine(nativeFolder)));
|
writer.write(StringUtils.makeCommand(generateCommandLine(nativeFolder)));
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ public abstract class Task {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (failure != null)
|
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 org.jackhuang.hmcl.util.*;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@@ -109,7 +106,7 @@ public final class TaskExecutor {
|
|||||||
return false;
|
return false;
|
||||||
Invoker invoker = new Invoker(task, latch, success);
|
Invoker invoker = new Invoker(task, latch, success);
|
||||||
try {
|
try {
|
||||||
Future<?> future = task.getScheduler().schedule(invoker);
|
Future<?> future = scheduler.schedule(invoker);
|
||||||
if (future != null)
|
if (future != null)
|
||||||
workerQueue.add(future);
|
workerQueue.add(future);
|
||||||
} catch (RejectedExecutionException e) {
|
} catch (RejectedExecutionException e) {
|
||||||
@@ -152,7 +149,12 @@ public final class TaskExecutor {
|
|||||||
task.setDependentsSucceeded();
|
task.setDependentsSucceeded();
|
||||||
|
|
||||||
task.setVariables(variables);
|
task.setVariables(variables);
|
||||||
task.execute();
|
|
||||||
|
try {
|
||||||
|
task.getScheduler().schedule(task::execute).get();
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
throw (Exception) e.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
if (task instanceof TaskResult<?>) {
|
if (task instanceof TaskResult<?>) {
|
||||||
TaskResult<?> taskResult = (TaskResult<?>) task;
|
TaskResult<?> taskResult = (TaskResult<?>) task;
|
||||||
@@ -181,7 +183,7 @@ public final class TaskExecutor {
|
|||||||
// do nothing
|
// do nothing
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
lastException = 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);
|
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
|
||||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
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();
|
return readFully(stream).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readFullyAsString(InputStream stream, Charset charset) {
|
public static String readFullyAsString(InputStream stream, Charset charset) throws IOException {
|
||||||
return Lang.invoke(() -> readFully(stream).toString(charset.name()));
|
return readFully(stream).toString(charset.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void copyTo(InputStream src, OutputStream dest) throws IOException {
|
public static void copyTo(InputStream src, OutputStream dest) throws IOException {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ public final class Lang {
|
|||||||
return r -> invoke(function, r);
|
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);
|
return hideFunction(function);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ public final class Lang {
|
|||||||
return () -> invoke(supplier);
|
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);
|
return hideException(supplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ public final class Lang {
|
|||||||
return it -> invokeConsumer(consumer, it);
|
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);
|
return hideConsumer(consumer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import com.google.gson.stream.JsonToken;
|
|||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
|||||||
@@ -85,10 +85,10 @@ public final class NetworkUtils {
|
|||||||
return doPost(u, post, contentType, Proxy.NO_PROXY);
|
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);
|
byte[] bytes = post.getBytes(Charsets.UTF_8);
|
||||||
|
|
||||||
HttpURLConnection con = createConnection(u, proxy);
|
HttpURLConnection con = createConnection(url, proxy);
|
||||||
con.setRequestMethod("POST");
|
con.setRequestMethod("POST");
|
||||||
con.setDoOutput(true);
|
con.setDoOutput(true);
|
||||||
con.setRequestProperty("Content-Type", contentType + "; charset=utf-8");
|
con.setRequestProperty("Content-Type", contentType + "; charset=utf-8");
|
||||||
|
|||||||
Reference in New Issue
Block a user