mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-03-07 17:36:52 +08:00
Authlib Injector support
This commit is contained in:
parent
83bc6748a3
commit
24ec0adacf
@ -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);
|
||||
|
@ -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<String, String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<String> 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<String> 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<String> authlibInjectorServerURLs) {
|
||||
this.authlibInjectorServerURLs = authlibInjectorServerURLs;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
}
|
||||
|
@ -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<String, Profile> 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<String> 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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<AuthInfo> 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<AuthInfo> success, Runnable failed) {
|
||||
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> 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;
|
||||
}
|
@ -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<String> cboType;
|
||||
@FXML private JFXComboBox<TwoLineListItem> 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.<Collection<TwoLineListItem>>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);
|
||||
}
|
||||
|
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 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.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<AuthlibInjectorServerItem> 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;
|
||||
}
|
||||
}
|
@ -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.<Collection<? extends Node>>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.<Exception>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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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<AuthInfo> 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();
|
||||
|
@ -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();
|
||||
|
@ -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<String, String> 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();
|
||||
}
|
||||
}
|
@ -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<String> protocols = FXCollections.observableArrayList();
|
||||
|
||||
public URLValidator() {
|
||||
super();
|
||||
}
|
||||
|
||||
public URLValidator(@NamedArg("message") String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ObservableList<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,21 +13,16 @@
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane" FXUtils.limitWidth="160" FXUtils.limitHeight="156">
|
||||
<VBox fx:id="content">
|
||||
<StackPane fx:id="header" VBox.vgrow="ALWAYS">
|
||||
<BorderPane>
|
||||
<top>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
<JFXProgressBar fx:id="pgsSkin" visible="false" />
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<VBox style="-fx-padding: 20 20 0 20;">
|
||||
<Label fx:id="lblUser" style="-fx-font-size: 20;" wrapText="true" textAlignment="JUSTIFY" />
|
||||
<Label fx:id="lblEmail" style="-fx-font-size: 12;" />
|
||||
<Label fx:id="lblType" style="-fx-font-size: 12;" />
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
||||
<StackPane fx:id="header" VBox.vgrow="ALWAYS" style="-fx-background-radius: 2 2 0 0; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;">
|
||||
<HBox StackPane.alignment="TOP_CENTER" alignment="CENTER_RIGHT">
|
||||
<JFXProgressBar fx:id="pgsSkin" visible="false" />
|
||||
</HBox>
|
||||
<VBox style="-fx-padding: 8 8 0 8;">
|
||||
<Label fx:id="lblUser" style="-fx-font-size: 15;" wrapText="true" textAlignment="JUSTIFY" />
|
||||
<Label fx:id="lblEmail" style="-fx-font-size: 11; -fx-text-fill: gray;" />
|
||||
<Label fx:id="lblServer" style="-fx-font-size: 11; -fx-text-fill: gray;" />
|
||||
<Label fx:id="lblType" style="-fx-font-size: 11; -fx-text-fill: gray;" />
|
||||
</VBox>
|
||||
</StackPane>
|
||||
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;" minHeight="40">
|
||||
<BorderPane>
|
||||
|
@ -7,6 +7,8 @@
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<?import javafx.scene.control.Hyperlink?>
|
||||
<?import java.lang.String?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
@ -31,20 +33,28 @@
|
||||
<body>
|
||||
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints maxWidth="100" />
|
||||
<ColumnConstraints maxWidth="70" minWidth="70"/>
|
||||
<ColumnConstraints />
|
||||
<ColumnConstraints minWidth="140" />
|
||||
</columnConstraints>
|
||||
|
||||
<Label text="%account.methods" GridPane.halignment="RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
|
||||
<JFXComboBox fx:id="cboType" GridPane.columnIndex="1" GridPane.rowIndex="0" />
|
||||
<JFXComboBox fx:id="cboType" GridPane.columnIndex="1" GridPane.rowIndex="0" GridPane.columnSpan="2" />
|
||||
|
||||
<JFXTextField fx:id="txtUsername" promptText="%account.username" labelFloat="true" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.columnSpan="2" FXUtils.validateWhileTextChanged="true">
|
||||
<Label fx:id="lblAddInjectorServer" text="%account.injector.server" GridPane.halignment="RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="1" />
|
||||
|
||||
<JFXComboBox fx:id="cboServers" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||
|
||||
<Hyperlink fx:id="linkAddInjectorServer" text="%account.injector.add" onMouseClicked="#onAddInjecterServer" GridPane.columnIndex="2" GridPane.rowIndex="1" />
|
||||
|
||||
<JFXTextField fx:id="txtUsername" promptText="%account.username" labelFloat="true" GridPane.columnIndex="0" GridPane.rowIndex="2" GridPane.columnSpan="3" FXUtils.validateWhileTextChanged="true">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="%input.not_empty">
|
||||
</RequiredFieldValidator>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
<JFXPasswordField fx:id="txtPassword" promptText="%account.password" labelFloat="true" GridPane.columnIndex="0" GridPane.rowIndex="2" GridPane.columnSpan="2" FXUtils.validateWhileTextChanged="true">
|
||||
<JFXPasswordField fx:id="txtPassword" promptText="%account.password" labelFloat="true" GridPane.columnIndex="0" GridPane.rowIndex="3" GridPane.columnSpan="3" FXUtils.validateWhileTextChanged="true">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="%input.not_empty">
|
||||
</RequiredFieldValidator>
|
||||
|
@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import com.jfoenix.controls.JFXSpinner?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import com.jfoenix.controls.JFXDialog?>
|
||||
<?import com.jfoenix.controls.JFXDialogLayout?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
|
||||
<?import java.lang.String?>
|
||||
<?import com.jfoenix.controls.JFXProgressBar?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<JFXSpinner fx:id="spinner" style="-fx-radius:16" styleClass="materialDesign-purple, first-spinner" />
|
||||
<StackPane fx:id="contentPane">
|
||||
<ScrollPane fx:id="scrollPane" fitToWidth="true" fitToHeight="true">
|
||||
<VBox fx:id="listPane" spacing="10" style="-fx-padding: 20 20 70 20;">
|
||||
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
<VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT">
|
||||
<JFXButton prefWidth="40" prefHeight="40" buttonType="RAISED" onMouseClicked="#onAdd" styleClass="jfx-button-raised-round">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/plus.fxml"/>
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
|
||||
<JFXDialog fx:id="dialog">
|
||||
<StackPane>
|
||||
<StackPane fx:id="addServerContainer">
|
||||
<JFXDialogLayout fx:id="addServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<JFXTextField fx:id="txtServerIp" promptText="%account.injector.server_ip" labelFloat="true">
|
||||
<validators>
|
||||
<URLValidator message="%input.url">
|
||||
<protocols>
|
||||
<String fx:value="http" />
|
||||
<String fx:value="https" />
|
||||
</protocols>
|
||||
</URLValidator>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.ok" styleClass="dialog-cancel" />
|
||||
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
|
||||
<JFXDialogLayout fx:id="confirmServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints maxWidth="100" />
|
||||
<ColumnConstraints />
|
||||
</columnConstraints>
|
||||
<Label text="%account.injector.server_ip" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
<Label text="%account.injector.server_name" GridPane.columnIndex="0" GridPane.rowIndex="1" />
|
||||
|
||||
<Label fx:id="lblServerIp" GridPane.columnIndex="1" GridPane.rowIndex="0" />
|
||||
<Label fx:id="lblServerName" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||
|
||||
<Label text="%account.injector.http" style="-fx-text-fill: red;" GridPane.rowIndex="2" GridPane.columnSpan="2" GridPane.columnIndex="0" />
|
||||
</GridPane>
|
||||
</body>
|
||||
<actions>
|
||||
<JFXButton onMouseClicked="#onAddPrev" text="%wizard.prev" styleClass="dialog-cancel" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
|
||||
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
</StackPane>
|
||||
<JFXProgressBar fx:id="progressBar" visible="false" StackPane.alignment="TOP_CENTER" />
|
||||
</StackPane>
|
||||
</JFXDialog>
|
||||
</fx:root>
|
@ -61,7 +61,7 @@
|
||||
<bottom>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
<JFXButton fx:id="btnNext" onMouseClicked="#onNext" prefWidth="100" prefHeight="40" buttonType="RAISED"
|
||||
text="%wizard.next_>" styleClass="jfx-button-raised"/>
|
||||
text="%wizard.next" styleClass="jfx-button-raised"/>
|
||||
</HBox>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
|
@ -12,19 +12,11 @@
|
||||
type="StackPane" pickOnBounds="false" FXUtils.limitWidth="160" FXUtils.limitHeight="156">
|
||||
<VBox fx:id="content" pickOnBounds="false">
|
||||
<StackPane fx:id="header" VBox.vgrow="ALWAYS" pickOnBounds="false" style="-fx-background-radius: 2 2 0 0; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;">
|
||||
<BorderPane>
|
||||
<top>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<VBox style="-fx-padding: 8 8 0 8;">
|
||||
<Label fx:id="lblVersionName" style="-fx-font-size: 15;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblGameVersion" style="-fx-font-size: 10;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblLibraries" style="-fx-font-size: 10; -fx-text-fill: gray;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
||||
<VBox style="-fx-padding: 8 8 0 8;">
|
||||
<Label fx:id="lblVersionName" style="-fx-font-size: 15;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblGameVersion" style="-fx-font-size: 11;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblLibraries" style="-fx-font-size: 11; -fx-text-fill: gray;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
</VBox>
|
||||
</StackPane>
|
||||
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;" minHeight="40" pickOnBounds="false">
|
||||
<BorderPane>
|
||||
|
@ -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
|
||||
|
@ -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=概要
|
||||
|
@ -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();
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
|
@ -32,7 +32,7 @@ public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineAccount fromUsername(String username, String password) {
|
||||
public OfflineAccount fromUsername(String username, String password, Object additionalData) {
|
||||
return new OfflineAccount(username, getUUIDFromUserName(username));
|
||||
}
|
||||
|
||||
|
@ -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<Object, Object> toStorageImpl() {
|
||||
Map<Object, Object> map = super.toStorageImpl();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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"));
|
||||
|
@ -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<String, Boolean> 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
|
||||
|
@ -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));
|
||||
|
@ -35,15 +35,15 @@ public final class AutoTypingMap<K> {
|
||||
this.impl = impl;
|
||||
}
|
||||
|
||||
public <V> V get(K key) {
|
||||
public synchronized <V> V get(K key) {
|
||||
return (V) impl.get(key);
|
||||
}
|
||||
|
||||
public <V> Optional<V> getOptional(K key) {
|
||||
public synchronized <V> Optional<V> 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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user