diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java index 180505259..b11f8e653 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java @@ -45,7 +45,7 @@ public final class AccountHelper { public static final AccountHelper INSTANCE = new AccountHelper(); private AccountHelper() {} - public static final File SKIN_DIR = new File(Main.APPDATA, "skins"); + public static final File SKIN_DIR = new File(Main.HMCL_DIRECTORY, "skins"); public static void loadSkins() { loadSkins(Proxy.NO_PROXY); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 2766141fa..dccc1d195 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hmcl.setting; +import com.google.gson.JsonParseException; import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.AccountFactory; @@ -24,12 +25,12 @@ import org.jackhuang.hmcl.auth.OfflineAccount; import org.jackhuang.hmcl.auth.OfflineAccountFactory; import org.jackhuang.hmcl.auth.yggdrasil.*; import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.util.FileUtils; -import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.*; import java.io.File; +import java.io.IOException; import java.net.URL; +import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -49,6 +50,8 @@ public final class Accounts { new Pair<>(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector)) ); + private static final Map AUTHLIB_INJECTOR_SERVER_NAMES = new HashMap<>(); + public static String getAccountType(Account account) { if (account instanceof OfflineAccount) return OFFLINE_ACCOUNT_KEY; else if (account instanceof AuthlibInjectorAccount) return AUTHLIB_INJECTOR_ACCOUNT_KEY; @@ -87,11 +90,29 @@ public final class Accounts { AuthlibInjectorBuildInfo buildInfo = AuthlibInjectorBuildInfo.requestBuildInfo(); File jar = new File(Main.HMCL_DIRECTORY, "authlib-injector.jar"); File local = new File(Main.HMCL_DIRECTORY, "authlib-injector.txt"); - int buildNumber = Integer.parseInt(FileUtils.readText(local)); + int buildNumber = 0; + try { + buildNumber = Integer.parseInt(FileUtils.readText(local)); + } catch (IOException | NumberFormatException ignore) { + } if (buildNumber < buildInfo.getBuildNumber()) { new FileDownloadTask(new URL(buildInfo.getUrl()), jar).run(); FileUtils.writeText(local, String.valueOf(buildInfo.getBuildNumber())); } return jar.getAbsolutePath(); } + + public static String getAuthlibInjectorServerName(String serverIp) { + if (AUTHLIB_INJECTOR_SERVER_NAMES.containsKey(serverIp)) + return AUTHLIB_INJECTOR_SERVER_NAMES.get(serverIp); + else { + try { + AuthlibInjectorServerResponse response = Constants.GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(serverIp)), AuthlibInjectorServerResponse.class); + AUTHLIB_INJECTOR_SERVER_NAMES.put(serverIp, response.getMeta().getServerName()); + return response.getMeta().getServerName(); + } catch (JsonParseException | IOException | NullPointerException e) { + return null; + } + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index cc5110ea2..800ba4408 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -19,15 +19,11 @@ package org.jackhuang.hmcl.setting; import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.Main; -import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorBuildInfo; import org.jackhuang.hmcl.util.JavaVersion; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; +import java.util.*; -public final class Config { +final class Config { @SerializedName("last") private String selectedProfile = ""; @@ -92,8 +88,8 @@ public final class Config { @SerializedName("logLines") private int logLines = 100; - @SerializedName("authlibInjectorServerURL") - private String authlibInjectorServerURL = AuthlibInjectorBuildInfo.UPDATE_URL; + @SerializedName("authlibInjectorServerURLs") + private Set authlibInjectorServerURLs = new HashSet<>(); public String getSelectedProfile() { return selectedProfile; @@ -283,17 +279,12 @@ public final class Config { this.logLines = logLines; } - public String getAuthlibInjectorServerURL() { - return authlibInjectorServerURL; + public Set getAuthlibInjectorServerURLs() { + return authlibInjectorServerURLs; } - /** - * Will not invoke Settings.INSTANCE.save() - * @param authlibInjectorServerURL new server url. - */ - public void setAuthlibInjectorServerURL(String authlibInjectorServerURL) { - this.authlibInjectorServerURL = authlibInjectorServerURL; - // Do not invoke Settings.INSTANCE.save() - // Because we want users set it theirself. + public void setAuthlibInjectorServerURLs(Set authlibInjectorServerURLs) { + this.authlibInjectorServerURLs = authlibInjectorServerURLs; + Settings.INSTANCE.save(); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java index a1f5a60d7..047d97f7a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java @@ -28,6 +28,7 @@ import javafx.scene.text.Font; import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.AccountFactory; +import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorBuildInfo; import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider; import org.jackhuang.hmcl.download.DownloadProvider; @@ -87,10 +88,12 @@ public class Settings { accounts.put(Accounts.getAccountId(account), account); } - save(); - + checkAuthlibInjectorServerURLs(); + checkAuthlibInjectorAccounts(); checkProfileMap(); + save(); + for (Map.Entry entry2 : getProfileMap().entrySet()) { entry2.getValue().setName(entry2.getKey()); entry2.getValue().nameProperty().setChangedListener(this::profileNameChanged); @@ -104,13 +107,6 @@ public class Settings { }); loadProxy(); - - try { - new URL(SETTINGS.getAuthlibInjectorServerURL()); - } catch (MalformedURLException ex) { - Logging.LOG.log(Level.SEVERE, "Authlib injector server URL is malformed, using official update url.", ex); - SETTINGS.setAuthlibInjectorServerURL(AuthlibInjectorBuildInfo.UPDATE_URL); - } } private Config initSettings() { @@ -280,8 +276,39 @@ public class Settings { SETTINGS.setLogLines(logLines); } - public String getAuthlibInjectorServerURL() { - return SETTINGS.getAuthlibInjectorServerURL(); + public Set getAuthlibInjectorServerURLs() { + return SETTINGS.getAuthlibInjectorServerURLs(); + } + + public void removeAuthlibInjectorServerURL(String serverURL) { + checkAuthlibInjectorServerURLs(); + + SETTINGS.getAuthlibInjectorServerURLs().remove(serverURL); + + checkAuthlibInjectorAccounts(); + save(); + } + + public void addAuthlibInjectorServerURL(String serverURL) { + checkAuthlibInjectorServerURLs(); + + SETTINGS.getAuthlibInjectorServerURLs().add(serverURL); + save(); + } + + private void checkAuthlibInjectorServerURLs() { + if (SETTINGS.getAuthlibInjectorServerURLs() == null) + SETTINGS.setAuthlibInjectorServerURLs(new HashSet<>()); + } + + private void checkAuthlibInjectorAccounts() { + for (Account account : getAccounts()) { + if (account instanceof AuthlibInjectorAccount) { + AuthlibInjectorAccount injectorAccount = (AuthlibInjectorAccount) account; + if (!SETTINGS.getAuthlibInjectorServerURLs().contains(injectorAccount.getServerBaseURL())) + deleteAccount(account); + } + } } public DownloadProvider getDownloadProvider() { @@ -490,6 +517,8 @@ public class Settings { getProfileMap().put(ver.getName(), ver); ver.nameProperty().setChangedListener(this::profileNameChanged); + + save(); } public void deleteProfile(Profile profile) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java index 93b5ee4e3..5508eef00 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java @@ -36,11 +36,13 @@ import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.OfflineAccount; +import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorAccount; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.game.AccountHelper; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; public final class AccountItem extends StackPane { @@ -56,6 +58,7 @@ public final class AccountItem extends StackPane { @FXML private Label lblUser; @FXML private Label lblType; @FXML private Label lblEmail; + @FXML private Label lblServer; @FXML private Label lblCurrentAccount; @FXML private JFXRadioButton chkSelected; @FXML private JFXProgressBar pgsSkin; @@ -73,12 +76,8 @@ public final class AccountItem extends StackPane { btnDelete.setGraphic(SVG.delete("black", 15, 15)); btnRefresh.setGraphic(SVG.refresh("black", 15, 15)); - // create content - String headerColor = getDefaultColor(i % 12); - header.setStyle("-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor); - // create image view - icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 32.0, header.boundsInParentProperty(), icon.heightProperty())); + icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty())); chkSelected.getProperties().put("account", account); setSelected(Settings.INSTANCE.getSelectedAccount() == account); @@ -87,6 +86,13 @@ public final class AccountItem extends StackPane { lblType.setText(AccountsPage.accountType(account)); lblEmail.setText(account.getUsername()); + if (account instanceof AuthlibInjectorAccount) { + Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(((AuthlibInjectorAccount) account).getServerBaseURL())) + .subscribe(Schedulers.javafx(), variables -> { + lblServer.setText(variables.get("serverName")); + }); + } + if (account instanceof YggdrasilAccount) { btnRefresh.setOnMouseClicked(e -> { pgsSkin.setVisible(true); @@ -113,25 +119,6 @@ public final class AccountItem extends StackPane { FXUtils.limitSize(portraitView, 32, 32); } - private String getDefaultColor(int i) { - switch (i) { - case 0: return "#8F3F7E"; - case 1: return "#B5305F"; - case 2: return "#CE584A"; - case 3: return "#DB8D5C"; - case 4: return "#DA854E"; - case 5: return "#E9AB44"; - case 6: return "#FEE435"; - case 7: return "#99C286"; - case 8: return "#01A05E"; - case 9: return "#4A8895"; - case 10: return "#16669B"; - case 11: return "#2F65A5"; - case 12: return "#4E6A9C"; - default: return "#FFFFFF"; - } - } - public Account getAccount() { return account; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountLoginPane.java similarity index 82% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountLoginPane.java index 927fdb04a..e0bd80f98 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountLoginPane.java @@ -23,7 +23,6 @@ import com.jfoenix.controls.JFXProgressBar; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; -import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.auth.*; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory; @@ -34,8 +33,8 @@ import org.jackhuang.hmcl.task.Task; import java.util.function.Consumer; -public class YggdrasilAccountLoginPane extends StackPane { - private final YggdrasilAccount oldAccount; +public class AccountLoginPane extends StackPane { + private final Account oldAccount; private final Consumer success; private final Runnable failed; @@ -46,12 +45,12 @@ public class YggdrasilAccountLoginPane extends StackPane { @FXML private JFXProgressBar progressBar; private JFXDialog dialog; - public YggdrasilAccountLoginPane(YggdrasilAccount oldAccount, Consumer success, Runnable failed) { + public AccountLoginPane(Account oldAccount, Consumer success, Runnable failed) { this.oldAccount = oldAccount; this.success = success; this.failed = failed; - FXUtils.loadFXML(this, "/assets/fxml/yggdrasil-account-login.fxml"); + FXUtils.loadFXML(this, "/assets/fxml/account-login.fxml"); lblUsername.setText(oldAccount.getUsername()); txtPassword.setOnAction(e -> onAccept()); @@ -59,14 +58,12 @@ public class YggdrasilAccountLoginPane extends StackPane { @FXML private void onAccept() { - String username = oldAccount.getUsername(); String password = txtPassword.getText(); progressBar.setVisible(true); lblCreationWarning.setText(""); Task.ofResult("login", () -> { try { - Account account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password); - return account.logIn(new SpecificCharacterSelector(Accounts.getCurrentCharacter(oldAccount)), Settings.INSTANCE.getProxy()); + return oldAccount.logInWithPassword(new SpecificCharacterSelector(Accounts.getCurrentCharacter(oldAccount)), password, Settings.INSTANCE.getProxy()); } catch (Exception e) { return e; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java index ca1b15b15..5d9c8ac1e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java @@ -25,20 +25,17 @@ import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import javafx.geometry.Pos; import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.ToggleGroup; +import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; +import javafx.util.Callback; import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.auth.*; -import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; +import org.jackhuang.hmcl.auth.yggdrasil.*; import org.jackhuang.hmcl.auth.InvalidCredentialsException; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory; import org.jackhuang.hmcl.game.AccountHelper; import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.setting.Settings; @@ -48,10 +45,14 @@ import org.jackhuang.hmcl.ui.construct.AdvancedListBox; import org.jackhuang.hmcl.ui.construct.IconedItem; import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.DecoratorPage; +import org.jackhuang.hmcl.util.Pair; +import org.jackhuang.hmcl.util.ReflectionHelper; +import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.stream.Collectors; public final class AccountsPage extends StackPane implements DecoratorPage { private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("account")); @@ -64,7 +65,10 @@ public final class AccountsPage extends StackPane implements DecoratorPage { @FXML private JFXPasswordField txtPassword; @FXML private Label lblCreationWarning; @FXML private JFXComboBox cboType; + @FXML private JFXComboBox cboServers; @FXML private JFXProgressBar progressBar; + @FXML private Label lblAddInjectorServer; + @FXML private Hyperlink linkAddInjectorServer; { FXUtils.loadFXML(this, "/assets/fxml/account.fxml"); @@ -74,12 +78,19 @@ public final class AccountsPage extends StackPane implements DecoratorPage { FXUtils.smoothScrolling(scrollPane); - cboType.getItems().setAll(Main.i18n("account.methods.offline"), Main.i18n("account.methods.yggdrasil")); + cboType.getItems().setAll(Main.i18n("account.methods.offline"), Main.i18n("account.methods.yggdrasil"), Main.i18n("account.methods.authlib_injector")); cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> { txtPassword.setVisible(newValue.intValue() != 0); + cboServers.setVisible(newValue.intValue() == 2); + linkAddInjectorServer.setVisible(newValue.intValue() == 2); + lblAddInjectorServer.setVisible(newValue.intValue() == 2); }); cboType.getSelectionModel().select(0); + // These two lines can eliminate black, don't know why. + cboServers.getItems().setAll(new TwoLineListItem("", "")); + cboServers.getSelectionModel().select(0); + txtPassword.setOnAction(e -> onCreationAccept()); txtUsername.setOnAction(e -> onCreationAccept()); txtUsername.getValidators().add(new Validator(Main.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@"))); @@ -91,6 +102,7 @@ public final class AccountsPage extends StackPane implements DecoratorPage { }); loadAccounts(); + loadServers(); if (Settings.INSTANCE.getAccounts().isEmpty()) addNewAccount(); @@ -114,6 +126,17 @@ public final class AccountsPage extends StackPane implements DecoratorPage { }); } + public void loadServers() { + Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream() + .map(serverURL -> new TwoLineListItem(Accounts.getAuthlibInjectorServerName(serverURL), serverURL)) + .collect(Collectors.toList())) + .subscribe(Task.of(Schedulers.javafx(), variables -> { + cboServers.getItems().setAll(variables.>get("list")); + if (!cboServers.getItems().isEmpty()) + cboServers.getSelectionModel().select(0); + })); + } + private Node buildNode(int i, Account account, ToggleGroup group) { AccountItem item = new AccountItem(i, account, group); item.setOnDeleteButtonMouseClicked(e -> { @@ -130,6 +153,11 @@ public final class AccountsPage extends StackPane implements DecoratorPage { dialog.show(); } + @FXML + private void onAddInjecterServer() { + Controllers.navigate(Controllers.getServersPage()); + } + @FXML private void onCreationAccept() { int type = cboType.getSelectionModel().getSelectedIndex(); @@ -141,8 +169,9 @@ public final class AccountsPage extends StackPane implements DecoratorPage { try { Account account; switch (type) { - case 0: account = OfflineAccountFactory.INSTANCE.fromUsername(username); break; - case 1: account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password); break; + case 0: account = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY).fromUsername(username); break; + case 1: account = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY).fromUsername(username, password); break; + case 2: account = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY).fromUsername(username, password, cboServers.getSelectionModel().getSelectedItem().getSubtitle()); break; default: throw new Error(); } @@ -203,6 +232,7 @@ public final class AccountsPage extends StackPane implements DecoratorPage { public static String accountType(Account account) { if (account instanceof OfflineAccount) return Main.i18n("account.methods.offline"); + else if (account instanceof AuthlibInjectorAccount) return Main.i18n("account.methods.authlib_injector"); else if (account instanceof YggdrasilAccount) return Main.i18n("account.methods.yggdrasil"); else throw new Error(Main.i18n("account.methods.no_method") + ": " + account); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServerItem.java new file mode 100644 index 000000000..436113b31 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServerItem.java @@ -0,0 +1,65 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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.ui; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.effects.JFXDepthManager; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorServerInfo; +import org.jackhuang.hmcl.mod.ModInfo; + +import java.util.function.Consumer; + +public final class AuthlibInjectorServerItem extends BorderPane { + private final AuthlibInjectorServerInfo info; + + private final Label lblServerName = new Label(); + private final Label lblServerIp = new Label(); + + public AuthlibInjectorServerItem(AuthlibInjectorServerInfo info, Consumer deleteCallback) { + this.info = info; + + lblServerName.setStyle("-fx-font-size: 15;"); + lblServerIp.setStyle("-fx-font-size: 10;"); + + VBox center = new VBox(); + BorderPane.setAlignment(center, Pos.CENTER); + center.getChildren().addAll(lblServerName, lblServerIp); + setCenter(center); + + JFXButton right = new JFXButton(); + right.setOnMouseClicked(e -> deleteCallback.accept(this)); + right.getStyleClass().add("toggle-icon4"); + BorderPane.setAlignment(right, Pos.CENTER); + right.setGraphic(SVG.close("black", 15, 15)); + setRight(right); + + setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;"); + JFXDepthManager.setDepth(this, 1); + lblServerName.setText(info.getServerName()); + lblServerIp.setText(info.getServerIp()); + } + + public AuthlibInjectorServerInfo getInfo() { + return info; + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java new file mode 100644 index 000000000..1f8192370 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AuthlibInjectorServersPage.java @@ -0,0 +1,150 @@ +package org.jackhuang.hmcl.ui; + +import com.jfoenix.controls.*; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorServerInfo; +import org.jackhuang.hmcl.setting.Accounts; +import org.jackhuang.hmcl.setting.Settings; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.animation.ContainerAnimations; +import org.jackhuang.hmcl.ui.animation.TransitionHandler; +import org.jackhuang.hmcl.ui.wizard.DecoratorPage; + +import java.util.Collection; +import java.util.Objects; +import java.util.stream.Collectors; + +public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage { + private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("account.injector.server")); + + @FXML private ScrollPane scrollPane; + @FXML private StackPane addServerContainer; + @FXML private Label lblServerIp; + @FXML private Label lblServerName; + @FXML private Label lblCreationWarning; + @FXML private VBox listPane; + @FXML private JFXTextField txtServerIp; + @FXML private JFXDialogLayout addServerPane; + @FXML private JFXDialogLayout confirmServerPane; + @FXML private JFXDialog dialog; + @FXML private StackPane contentPane; + @FXML private JFXSpinner spinner; + @FXML private JFXProgressBar progressBar; + @FXML private JFXButton btnAddNext; + + private TransitionHandler transitionHandler; + + { + FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml"); + FXUtils.smoothScrolling(scrollPane); + transitionHandler = new TransitionHandler(addServerContainer); + + getChildren().remove(dialog); + dialog.setDialogContainer(this); + + txtServerIp.textProperty().addListener((a, b, newValue) -> { + btnAddNext.setDisable(!txtServerIp.validate()); + }); + + loading(); + } + + private void removeServer(AuthlibInjectorServerItem item) { + Settings.INSTANCE.removeAuthlibInjectorServerURL(item.getInfo().getServerIp()); + loading(); + } + + private void loading() { + getChildren().remove(contentPane); + spinner.setVisible(true); + + Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream() + .map(serverURL -> new AuthlibInjectorServerItem(new AuthlibInjectorServerInfo(serverURL, Accounts.getAuthlibInjectorServerName(serverURL)), this::removeServer)) + .collect(Collectors.toList())) + .subscribe(Task.of(Schedulers.javafx(), variables -> { + listPane.getChildren().setAll(variables.>get("list")); + loadingCompleted(); + })); + } + + private void loadingCompleted() { + getChildren().add(contentPane); + spinner.setVisible(false); + + if (Settings.INSTANCE.getAuthlibInjectorServerURLs().isEmpty()) + onAdd(); + } + + @FXML + private void onAdd() { + transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer()); + txtServerIp.setText(""); + addServerPane.setDisable(false); + progressBar.setVisible(false); + dialog.show(); + } + + @FXML + private void onAddCancel() { + dialog.close(); + } + + @FXML + private void onAddNext() { + progressBar.setVisible(true); + addServerPane.setDisable(true); + + Task.ofResult("serverName", () -> Objects.requireNonNull(Accounts.getAuthlibInjectorServerName(txtServerIp.getText()))) + .finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> { + progressBar.setVisible(false); + addServerPane.setDisable(false); + + if (isDependentsSucceeded) { + lblServerName.setText(variables.get("serverName")); + lblServerIp.setText(txtServerIp.getText()); + + transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer()); + } else + lblCreationWarning.setText(variables.get("lastException").getLocalizedMessage()); + }).start(); + + + } + + @FXML + private void onAddPrev() { + transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer()); + } + + @FXML + private void onAddFinish() { + String ip = txtServerIp.getText(); + if (!ip.endsWith("/")) + ip += "/"; + Settings.INSTANCE.addAuthlibInjectorServerURL(ip); + loading(); + dialog.close(); + } + + public String getTitle() { + return title.get(); + } + + @Override + public StringProperty titleProperty() { + return title; + } + + public void setTitle(String title) { + this.title.set(title); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 9fa5500f6..6173a4818 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -42,6 +42,7 @@ public final class Controllers { private static MainPage mainPage = new MainPage(); private static SettingsPage settingsPage = null; private static VersionPage versionPage = null; + private static AuthlibInjectorServersPage serversPage = null; private static LeftPaneController leftPaneController; private static Decorator decorator; @@ -67,6 +68,13 @@ public final class Controllers { return versionPage; } + // FXThread + public static AuthlibInjectorServersPage getServersPage() { + if (serversPage == null) + serversPage = new AuthlibInjectorServersPage(); + return serversPage; + } + public static Decorator getDecorator() { return decorator; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java index 48aaaa863..3c78406ce 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/DialogController.java @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.concurrency.JFXUtilities; import org.jackhuang.hmcl.auth.Account; +import org.jackhuang.hmcl.auth.AccountFactory; import org.jackhuang.hmcl.auth.AuthInfo; import org.jackhuang.hmcl.auth.MultiCharacterSelector; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; @@ -35,7 +36,7 @@ public final class DialogController { CountDownLatch latch = new CountDownLatch(1); AtomicReference res = new AtomicReference<>(null); JFXUtilities.runInFX(() -> { - YggdrasilAccountLoginPane pane = new YggdrasilAccountLoginPane((YggdrasilAccount) account, it -> { + AccountLoginPane pane = new AccountLoginPane(account, it -> { res.set(it); latch.countDown(); Controllers.closeDialog(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java index 322266eb3..730a4e2e7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/ModItem.java @@ -29,7 +29,7 @@ import org.jackhuang.hmcl.mod.ModInfo; import java.util.function.Consumer; -public final class ModItem extends BorderPane { +public final class ModItem extends BorderPane { private final Label lblModFileName = new Label(); private final Label lblModAuthor = new Label(); private final JFXCheckBox chkEnabled = new JFXCheckBox(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/TwoLineListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TwoLineListItem.java new file mode 100644 index 000000000..576d5a837 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/TwoLineListItem.java @@ -0,0 +1,45 @@ +package org.jackhuang.hmcl.ui; + +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import org.jackhuang.hmcl.util.Pair; + +public class TwoLineListItem extends StackPane { + private final Label lblTitle = new Label(); + private final Label lblSubtitle = new Label(); + private final String title; + private final String subtitle; + + public TwoLineListItem(Pair pair) { + this(pair.getKey(), pair.getValue()); + } + + public TwoLineListItem(String title, String subtitle) { + lblTitle.setStyle("-fx-font-size: 15;"); + lblSubtitle.setStyle("-fx-font-size: 10; -fx-text-fill: gray;"); + + this.title = title; + this.subtitle = subtitle; + + lblTitle.setText(title); + lblSubtitle.setText(subtitle); + + VBox vbox = new VBox(); + vbox.getChildren().setAll(lblTitle, lblSubtitle); + getChildren().setAll(vbox); + } + + public String getTitle() { + return title; + } + + public String getSubtitle() { + return subtitle; + } + + @Override + public String toString() { + return getTitle(); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/URLValidator.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/URLValidator.java new file mode 100644 index 000000000..3cb641a08 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/URLValidator.java @@ -0,0 +1,42 @@ +package org.jackhuang.hmcl.ui.construct; + +import com.jfoenix.validation.base.ValidatorBase; +import javafx.beans.NamedArg; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.TextInputControl; + +import java.net.MalformedURLException; +import java.net.URL; + +public class URLValidator extends ValidatorBase { + + private final ObservableList protocols = FXCollections.observableArrayList(); + + public URLValidator() { + super(); + } + + public URLValidator(@NamedArg("message") String message) { + super(message); + } + + public ObservableList getProtocols() { + return protocols; + } + + @Override + protected void eval() { + if (srcControl.get() instanceof TextInputControl) { + try { + URL url = new URL(((TextInputControl) srcControl.get()).getText()); + if (protocols.isEmpty()) + hasErrors.set(false); + else + hasErrors.set(!protocols.contains(url.getProtocol())); + } catch (MalformedURLException e) { + hasErrors.set(true); + } + } + } +} diff --git a/HMCL/src/main/resources/assets/fxml/account-item.fxml b/HMCL/src/main/resources/assets/fxml/account-item.fxml index 6fd58e48d..1f4facc91 100644 --- a/HMCL/src/main/resources/assets/fxml/account-item.fxml +++ b/HMCL/src/main/resources/assets/fxml/account-item.fxml @@ -13,21 +13,16 @@ xmlns:fx="http://javafx.com/fxml" type="StackPane" FXUtils.limitWidth="160" FXUtils.limitHeight="156"> - - - - - - - -
- - -
-
+ + + + + + diff --git a/HMCL/src/main/resources/assets/fxml/yggdrasil-account-login.fxml b/HMCL/src/main/resources/assets/fxml/account-login.fxml similarity index 100% rename from HMCL/src/main/resources/assets/fxml/yggdrasil-account-login.fxml rename to HMCL/src/main/resources/assets/fxml/account-login.fxml diff --git a/HMCL/src/main/resources/assets/fxml/account.fxml b/HMCL/src/main/resources/assets/fxml/account.fxml index 6bc035aff..73ff37f18 100644 --- a/HMCL/src/main/resources/assets/fxml/account.fxml +++ b/HMCL/src/main/resources/assets/fxml/account.fxml @@ -7,6 +7,8 @@ + + @@ -31,20 +33,28 @@ - + + + diff --git a/HMCL/src/main/resources/assets/fxml/version-item.fxml b/HMCL/src/main/resources/assets/fxml/version-item.fxml index 1b7e8fd97..9d0b86063 100644 --- a/HMCL/src/main/resources/assets/fxml/version-item.fxml +++ b/HMCL/src/main/resources/assets/fxml/version-item.fxml @@ -12,19 +12,11 @@ type="StackPane" pickOnBounds="false" FXUtils.limitWidth="160" FXUtils.limitHeight="156"> - - - - - -
- - -
-
+ +
diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index a7558f0d1..55a34ed05 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -38,7 +38,13 @@ account.failed.invalid_credentials=Incorrect password. Or forbidden to log in te account.failed.invalid_password=Invalid password account.failed.invalid_token=Please log out and re-input your password to log in. account.failed.no_charactor=No character in this account. +account.injector.add=Add new authentication server +account.injector.http=Warning: This server is using HTTP, which will cause your password be transmitted in clear text. +account.injector.server=Auth Server +account.injector.server_ip=Server URL +account.injector.server_name=Server Name account.methods=Login Type +account.methods.authlib_injector=authlib-injector account.methods.no_method=No login method account.methods.offline=Offline account.methods.yggdrasil=Mojang @@ -127,6 +133,7 @@ folder.screenshots=Screenshots input.email=The username must be an e-mail. input.number=Must be a number. input.not_empty=Input Requrired! +input.url=Must be a valid URL. install=Install New Game install.failed=Failed to install @@ -406,11 +413,11 @@ version.manage.rename.message=Please enter the new name version.settings=Settings version.update=Update -wizard.<_prev=< Prev +wizard.prev=< Prev wizard.close=Close wizard.failed=Failed wizard.finish=Finish wizard.help=Help -wizard.next_>=Next > +wizard.next=Next > wizard.steps=Steps wizard.summary=Summary diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index b061f56fb..266ca838a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -38,7 +38,13 @@ account.failed.invalid_credentials=您的用户名或密码错误,或者登录 account.failed.invalid_password=无效的密码 account.failed.invalid_token=请尝试登出并重新输入密码登录 account.failed.no_charactor=该帐号没有角色 +account.injector.add=添加登录认证服务器 +account.injector.http=警告:此服务器使用不安全的http协议,您的密码在登录时会被明文传输。 +account.injector.server=认证服务器 +account.injector.server_ip=服务器地址 +account.injector.server_name=服务器名称 account.methods=登录方式 +account.methods.authlib_injector=authlib-injector 登录 account.methods.no_method=没有登入方式... account.methods.offline=离线模式 account.methods.yggdrasil=正版登录 @@ -127,6 +133,7 @@ folder.screenshots=截图文件夹 input.email=用户名必须是邮箱 input.number=必须是数字 input.not_empty=必填项 +input.url=必须是合法的链接 install=添加游戏 install.failed=安装失败 @@ -406,11 +413,11 @@ version.manage.rename.message=请输入要改成的名字 version.settings=游戏设置 version.update=更新 -wizard.<_prev=< 上一步 +wizard.prev=< 上一步 wizard.close=关闭 wizard.failed=失败 wizard.finish=完成 wizard.help=帮助 -wizard.next_>=下一步 > +wizard.next=下一步 > wizard.steps=步骤 wizard.summary=概要 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java index c9555d997..03d64eec6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java @@ -57,6 +57,12 @@ public abstract class Account { */ 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(); /** diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.java index a28660373..8090c9401 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.java @@ -64,6 +64,11 @@ public class OfflineAccount extends Account { return new AuthInfo(username, uuid, uuid); } + @Override + public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException { + return logIn(selector, proxy); + } + @Override public void logOut() { // Offline account need not log out. diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccountFactory.java index 35c2d2455..9b1da88a6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccountFactory.java @@ -32,7 +32,7 @@ public class OfflineAccountFactory extends AccountFactory { } @Override - public OfflineAccount fromUsername(String username, String password) { + public OfflineAccount fromUsername(String username, String password, Object additionalData) { return new OfflineAccount(username, getUUIDFromUserName(username)); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorAccount.java index 33a3a0bc2..ea899c3c6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorAccount.java @@ -29,13 +29,7 @@ public class AuthlibInjectorAccount extends YggdrasilAccount { // Authlib Injector recommends launchers to pre-fetch the server basic information before launched the game to save time. GetTask getTask = new GetTask(NetworkUtils.toURL(serverBaseURL)); AtomicBoolean flag = new AtomicBoolean(true); - Thread thread = Lang.thread(() -> { - try { - getTask.run(); - } catch (Exception ignore) { - flag.set(false); - } - }); + Thread thread = Lang.thread(() -> flag.set(getTask.test())); AuthInfo info = super.logIn(selector, proxy); try { @@ -53,6 +47,29 @@ public class AuthlibInjectorAccount extends YggdrasilAccount { } } + @Override + public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException { + // Authlib Injector recommends launchers to pre-fetch the server basic information before launched the game to save time. + GetTask getTask = new GetTask(NetworkUtils.toURL(serverBaseURL)); + AtomicBoolean flag = new AtomicBoolean(true); + Thread thread = Lang.thread(() -> flag.set(getTask.test())); + + AuthInfo info = super.logInWithPassword(selector, password, proxy); + try { + thread.join(); + + String arg = "-javaagent:" + injectorJarPath.get() + "=" + serverBaseURL; + Arguments arguments = Arguments.addJVMArguments(null, arg); + + //if (flag.get()) + // arguments = Arguments.addJVMArguments(arguments, "-Dorg.to2mbn.authlibinjector.config.prefetched=" + getTask.getResult()); + + return info.setArguments(arguments); + } catch (Exception e) { + throw new AuthenticationException("Unable to get authlib injector jar path", e); + } + } + @Override public Map toStorageImpl() { Map map = super.toStorageImpl(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorServerInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorServerInfo.java new file mode 100644 index 000000000..0c5e9c7c5 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorServerInfo.java @@ -0,0 +1,19 @@ +package org.jackhuang.hmcl.auth.yggdrasil; + +public class AuthlibInjectorServerInfo { + private final String serverIp; + private final String serverName; + + public AuthlibInjectorServerInfo(String serverIp, String serverName) { + this.serverIp = serverIp; + this.serverName = serverName; + } + + public String getServerIp() { + return serverIp; + } + + public String getServerName() { + return serverName; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorServerResponse.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorServerResponse.java new file mode 100644 index 000000000..3600adf2f --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorServerResponse.java @@ -0,0 +1,34 @@ +package org.jackhuang.hmcl.auth.yggdrasil; + +public class AuthlibInjectorServerResponse { + + private final Meta meta; + + public AuthlibInjectorServerResponse() { + this(new Meta()); + } + + public AuthlibInjectorServerResponse(Meta meta) { + this.meta = meta; + } + + public Meta getMeta() { + return meta; + } + + public static class Meta { + private final String serverName; + + public Meta() { + this(""); + } + + public Meta(String serverName) { + this.serverName = serverName; + } + + public String getServerName() { + return serverName; + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java index f633ade4f..f1fbc2de1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java @@ -132,24 +132,47 @@ public class YggdrasilAccount extends Account { } } + @Override + public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException { + logInWithPassword0(password, proxy); + if (!isLoggedIn()) + throw new AuthenticationException("Wrong password for account " + username); + + 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)); + } + private void logIn0(Proxy proxy) throws AuthenticationException { if (StringUtils.isNotBlank(accessToken)) { - if (StringUtils.isBlank(userId)) - if (StringUtils.isNotBlank(username)) - userId = username; - else - throw new AuthenticationException("Invalid uuid and username"); - if (checkTokenValidity(proxy)) { - isOnline = true; - return; - } - logIn1(routeRefresh, new RefreshRequest(accessToken, clientToken), proxy); + logInWithToken(proxy); } else if (StringUtils.isNotBlank(password)) - logIn1(routeAuthenticate, new AuthenticationRequest(username, password, clientToken), proxy); + 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; + return; + } + logIn1(routeRefresh, new RefreshRequest(accessToken, clientToken), proxy); + } + + public void logInWithPassword0(String password, Proxy proxy) throws AuthenticationException { + logIn1(routeAuthenticate, new AuthenticationRequest(username, password, clientToken), proxy); + } + private void logIn1(URL url, Object input, Proxy proxy) throws AuthenticationException { AuthenticationResponse response = makeRequest(url, input, proxy) .orElseThrow(() -> new AuthenticationException("Server response empty")); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index ebbc0a9a1..bdf3ae488 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -138,10 +138,16 @@ public class DefaultLauncher extends Launcher { configuration.put("${assets_root}", gameAssets.getAbsolutePath()); res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getJvm).orElseGet(this::getDefaultJVMArguments), configuration)); + if (authInfo.getArguments() != null && authInfo.getArguments().getJvm() != null && !authInfo.getArguments().getJvm().isEmpty()) + res.addAll(Arguments.parseArguments(authInfo.getArguments().getJvm(), configuration)); + res.add(version.getMainClass()); Map features = getFeatures(); res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getGame).orElseGet(this::getDefaultGameArguments), configuration, features)); + if (authInfo.getArguments() != null && authInfo.getArguments().getGame() != null && !authInfo.getArguments().getGame().isEmpty()) + res.addAll(Arguments.parseArguments(authInfo.getArguments().getGame(), configuration, features)); + res.addAll(Arguments.parseStringArguments(version.getMinecraftArguments().map(StringUtils::tokenize).orElseGet(LinkedList::new), configuration)); // Optional Minecraft arguments diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java index 5fca59c70..bef84a607 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/TaskExecutor.java @@ -192,6 +192,7 @@ public final class TaskExecutor { // do nothing } catch (Exception e) { lastException = e; + variables.set("lastException", e); Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e); task.onDone().fireEvent(new TaskEvent(this, task, true)); taskListeners.forEach(it -> it.onFailed(task, e)); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/AutoTypingMap.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/AutoTypingMap.java index 691a5c18a..1e3dfb602 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/AutoTypingMap.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/AutoTypingMap.java @@ -35,15 +35,15 @@ public final class AutoTypingMap { this.impl = impl; } - public V get(K key) { + public synchronized V get(K key) { return (V) impl.get(key); } - public Optional getOptional(K key) { + public synchronized Optional getOptional(K key) { return Optional.ofNullable(get(key)); } - public void set(K key, Object value) { + public synchronized void set(K key, Object value) { if (value != null) impl.put(key, value); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java index 2b1519498..6bdc7f104 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java @@ -93,7 +93,7 @@ public final class NetworkUtils { public static HttpURLConnection createConnection(URL url, Proxy proxy) throws IOException { initHttps(); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy); connection.setDoInput(true); connection.setUseCaches(false); connection.setConnectTimeout(15000);