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 ac45079d0..90ddd2b68 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/AccountHelper.java @@ -22,6 +22,7 @@ import javafx.scene.image.Image; import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; +import org.jackhuang.hmcl.auth.yggdrasil.ProfileTexture; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.task.FileDownloadTask; @@ -33,6 +34,7 @@ import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.util.NetworkUtils; import java.io.File; +import java.io.IOException; import java.net.Proxy; import java.util.Collection; import java.util.LinkedList; @@ -49,12 +51,13 @@ public final class AccountHelper { } public static void loadSkins(Proxy proxy) { - for (Account account : Settings.INSTANCE.getAccounts().values()) { + for (Account account : Settings.INSTANCE.getAccounts()) { if (account instanceof YggdrasilAccount) { new SkinLoadTask((YggdrasilAccount) account, proxy, false).start(); } } } + public static Task loadSkinAsync(YggdrasilAccount account) { return loadSkinAsync(account, Settings.INSTANCE.getProxy()); } @@ -94,6 +97,20 @@ public final class AccountHelper { return FXUtils.DEFAULT_ICON; } + public static Image getSkinImmediately(YggdrasilAccount account, GameProfile profile, double scaleRatio, Proxy proxy) throws Exception { + String name = profile.getName(); + File file = getSkinFile(name); + downloadSkin(account, profile, true, proxy); + if (!file.exists()) + return FXUtils.DEFAULT_ICON; + + Image original = new Image("file:" + file.getAbsolutePath()); + return new Image("file:" + file.getAbsolutePath(), + original.getWidth() * scaleRatio, + original.getHeight() * scaleRatio, + false, false); + } + public static Rectangle2D getViewport(double scaleRatio) { double size = 8.0 * scaleRatio; return new Rectangle2D(size, size, size, size); @@ -130,15 +147,20 @@ public final class AccountHelper { if (account.canLogIn() && (account.getSelectedProfile() == null || refresh)) DialogController.logIn(account); - GameProfile profile = account.getSelectedProfile(); - if (profile == null) return; - String name = profile.getName(); - if (name == null) return; - String url = "http://skins.minecraft.net/MinecraftSkins/" + name + ".png"; - File file = getSkinFile(name); - if (!refresh && file.exists()) - return; - dependencies.add(new FileDownloadTask(NetworkUtils.toURL(url), file, proxy)); + downloadSkin(account, account.getSelectedProfile(), refresh, proxy); } } + + private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh, Proxy proxy) throws Exception { + if (profile == null) return; + String name = profile.getName(); + if (name == null) return; + ProfileTexture texture = account.getSkin(profile); + if (texture == null) return; + String url = texture.getUrl(); + File file = getSkinFile(name); + if (!refresh && file.exists()) + return; + new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run(); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index a6b317301..49fc2d050 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -23,13 +23,11 @@ import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.AuthInfo; import org.jackhuang.hmcl.auth.AuthenticationException; +import org.jackhuang.hmcl.auth.SpecificCharacterSelector; import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.launch.*; import org.jackhuang.hmcl.mod.CurseCompletionTask; -import org.jackhuang.hmcl.setting.LauncherVisibility; -import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.Settings; -import org.jackhuang.hmcl.setting.VersionSetting; +import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.task.*; import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.DialogController; @@ -86,7 +84,7 @@ public final class LauncherHelper { .then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN))) .then(Task.of(Main.i18n("account.methods"), variables -> { try { - variables.set("account", account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.getProxy())); + variables.set("account", account.logIn(new SpecificCharacterSelector(Accounts.getCurrentCharacter(account)), Settings.INSTANCE.getProxy())); } catch (AuthenticationException e) { variables.set("account", DialogController.logIn(account)); JFXUtilities.runInFX(() -> Controllers.dialog(launchingStepsPane)); 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 1b68820bc..35c249172 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -48,4 +48,25 @@ public final class Accounts { else return YGGDRASIL_ACCOUNT_KEY; } + public static void setCurrentCharacter(Account account, String character) { + account.getProperties().put("character", character); + } + + public static String getCurrentCharacter(Account account) { + return Lang.get(account.getProperties(), "character", String.class, null); + } + + public static String getCurrentCharacter(Map storage) { + Map properties = Lang.get(storage, "properties", Map.class, null); + if (properties == null) return null; + return Lang.get(properties, "character", String.class, null); + } + + static String getAccountId(Account account) { + return getAccountId(account.getUsername(), getCurrentCharacter(account)); + } + + static String getAccountId(String username, String character) { + return username + ":" + character; + } } 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 d38cf6623..b6adc4200 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -21,6 +21,7 @@ import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.util.JavaVersion; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -67,7 +68,7 @@ public final class Config { private Map configurations = new TreeMap<>(); @SerializedName("accounts") - private Map> accounts = new TreeMap<>(); + private List> accounts = new LinkedList<>(); @SerializedName("selectedAccount") private String selectedAccount = ""; @@ -198,11 +199,11 @@ public final class Config { Settings.INSTANCE.save(); } - public Map> getAccounts() { + public List> getAccounts() { return accounts; } - public void setAccounts(Map> accounts) { + public void setAccounts(List> accounts) { this.accounts = accounts; 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 d91c3fc36..efd25bfa8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hmcl.setting; - import com.google.gson.Gson; import com.google.gson.GsonBuilder; import javafx.beans.property.ObjectProperty; @@ -66,12 +65,12 @@ public class Settings { private Map accounts = new HashMap<>(); { - for (Map.Entry> entry : SETTINGS.getAccounts().entrySet()) { - String name = entry.getKey(); - Map settings = entry.getValue(); + for (Map settings : SETTINGS.getAccounts()) { + String characterName = Accounts.getCurrentCharacter(settings); AccountFactory factory = Accounts.ACCOUNT_FACTORY.get(Lang.get(settings, "type", String.class, "")); - if (factory == null) { - SETTINGS.getAccounts().remove(name); + if (factory == null || characterName == null) { + // unrecognized account type, so remove it. + SETTINGS.getAccounts().remove(settings); continue; } @@ -79,17 +78,12 @@ public class Settings { try { account = factory.fromStorage(settings); } catch (Exception e) { - SETTINGS.getAccounts().remove(name); + SETTINGS.getAccounts().remove(settings); // storage is malformed, delete. continue; } - if (!Objects.equals(account.getUsername(), name)) { - SETTINGS.getAccounts().remove(name); - continue; - } - - accounts.put(name, account); + accounts.put(Accounts.getAccountId(account), account); } save(); @@ -138,12 +132,10 @@ public class Settings { public void save() { try { SETTINGS.getAccounts().clear(); - for (Map.Entry entry : accounts.entrySet()) { - String name = entry.getKey(); - Account account = entry.getValue(); + for (Account account : accounts.values()) { Map storage = account.toStorage(); storage.put("type", Accounts.getAccountType(account)); - SETTINGS.getAccounts().put(name, storage); + SETTINGS.getAccounts().add(storage); } FileUtils.writeText(SETTINGS_FILE, GSON.toJson(SETTINGS)); @@ -304,11 +296,11 @@ public class Settings { * ACCOUNTS * ****************************************/ - private final ImmediateObjectProperty selectedAccount = new ImmediateObjectProperty(this, "selectedAccount", getAccount(SETTINGS.getSelectedAccount())) { + private final ImmediateObjectProperty selectedAccount = new ImmediateObjectProperty(this, "selectedAccount", accounts.get(SETTINGS.getSelectedAccount())) { @Override public Account get() { Account a = super.get(); - if (a == null || !accounts.containsKey(a.getUsername())) { + if (a == null || !accounts.containsKey(Accounts.getAccountId(a))) { Account acc = accounts.values().stream().findAny().orElse(null); set(acc); return acc; @@ -317,7 +309,7 @@ public class Settings { @Override public void set(Account newValue) { - if (newValue == null || accounts.containsKey(newValue.getUsername())) { + if (newValue == null || accounts.containsKey(Accounts.getAccountId(newValue))) { super.set(newValue); } } @@ -326,7 +318,7 @@ public class Settings { public void invalidated() { super.invalidated(); - SETTINGS.setSelectedAccount(getValue() == null ? "" : getValue().getUsername()); + SETTINGS.setSelectedAccount(getValue() == null ? "" : Accounts.getAccountId(getValue())); } }; @@ -343,19 +335,25 @@ public class Settings { } public void addAccount(Account account) { - accounts.put(account.getUsername(), account); + accounts.put(Accounts.getAccountId(account), account); } - public Account getAccount(String name) { - return accounts.get(name); + public Account getAccount(String name, String character) { + return accounts.get(Accounts.getAccountId(name, character)); } - public Map getAccounts() { - return Collections.unmodifiableMap(accounts); + public Collection getAccounts() { + return Collections.unmodifiableCollection(accounts.values()); } - public void deleteAccount(String name) { - accounts.remove(name); + public void deleteAccount(String name, String character) { + accounts.remove(Accounts.getAccountId(name, character)); + + selectedAccount.get(); + } + + public void deleteAccount(Account account) { + accounts.remove(Accounts.getAccountId(account)); selectedAccount.get(); } 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 a4613f497..c9a69add0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountItem.java @@ -38,6 +38,7 @@ import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.OfflineAccount; 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; @@ -53,8 +54,9 @@ public final class AccountItem extends StackPane { @FXML private JFXButton btnDelete; @FXML private JFXButton btnRefresh; @FXML private Label lblUser; - @FXML private JFXRadioButton chkSelected; @FXML private Label lblType; + @FXML private Label lblEmail; + @FXML private JFXRadioButton chkSelected; @FXML private JFXProgressBar pgsSkin; @FXML private ImageView portraitView; @FXML private HBox buttonPane; @@ -82,8 +84,9 @@ public final class AccountItem extends StackPane { chkSelected.getProperties().put("account", account); chkSelected.setSelected(Settings.INSTANCE.getSelectedAccount() == account); - lblUser.setText(account.getUsername()); + lblUser.setText(Accounts.getCurrentCharacter(account)); lblType.setText(AccountsPage.accountType(account)); + lblEmail.setText(account.getUsername()); if (account instanceof YggdrasilAccount) { btnRefresh.setOnMouseClicked(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 2c0b7004c..b37767ced 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/AccountsPage.java @@ -17,33 +17,40 @@ */ package org.jackhuang.hmcl.ui; +import com.jfoenix.concurrency.JFXUtilities; import com.jfoenix.controls.*; import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; 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.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.Main; -import org.jackhuang.hmcl.auth.Account; -import org.jackhuang.hmcl.auth.OfflineAccount; -import org.jackhuang.hmcl.auth.OfflineAccountFactory; -import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException; +import org.jackhuang.hmcl.auth.*; +import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; +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.HMCLMultiCharacterSelector; +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; +import org.jackhuang.hmcl.ui.construct.IconedItem; import org.jackhuang.hmcl.ui.construct.Validator; import org.jackhuang.hmcl.ui.wizard.DecoratorPage; import java.util.LinkedList; import java.util.List; -import java.util.Map; +import java.util.concurrent.CountDownLatch; public final class AccountsPage extends StackPane implements DecoratorPage { private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("account")); @@ -94,8 +101,8 @@ public final class AccountsPage extends StackPane implements DecoratorPage { List children = new LinkedList<>(); int i = 0; ToggleGroup group = new ToggleGroup(); - for (Map.Entry entry : Settings.INSTANCE.getAccounts().entrySet()) { - children.add(buildNode(++i, entry.getValue(), group)); + for (Account account : Settings.INSTANCE.getAccounts()) { + children.add(buildNode(++i, account, group)); } group.selectedToggleProperty().addListener((a, b, newValue) -> { if (newValue != null) @@ -111,7 +118,7 @@ public final class AccountsPage extends StackPane implements DecoratorPage { private Node buildNode(int i, Account account, ToggleGroup group) { AccountItem item = new AccountItem(i, account, group); item.setOnDeleteButtonMouseClicked(e -> { - Settings.INSTANCE.deleteAccount(account.getUsername()); + Settings.INSTANCE.deleteAccount(account); Platform.runLater(this::loadAccounts); }); return item; @@ -140,7 +147,8 @@ public final class AccountsPage extends StackPane implements DecoratorPage { default: throw new Error(); } - account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.getProxy()); + AuthInfo info = account.logIn(new CharacterSelector(), Settings.INSTANCE.getProxy()); + Accounts.setCurrentCharacter(account, info.getUsername()); return account; } catch (Exception e) { return e; @@ -153,8 +161,18 @@ public final class AccountsPage extends StackPane implements DecoratorPage { loadAccounts(); } else if (account instanceof InvalidCredentialsException) { lblCreationWarning.setText(Main.i18n("account.failed.wrong_password")); + } else if (account instanceof NoCharacterException) { + lblCreationWarning.setText(Main.i18n("account.failed.no_charactor")); + } else if (account instanceof ServerDisconnectException) { + lblCreationWarning.setText(Main.i18n("account.failed.connect_authentication_server")); + } else if (account instanceof InvalidTokenException) { + lblCreationWarning.setText(Main.i18n("account.failed.invalid_token")); + } else if (account instanceof InvalidPasswordException) { + lblCreationWarning.setText(Main.i18n("account.failed.invalid_password")); + } else if (account instanceof NoSelectedCharacterException) { + dialog.close(); } else if (account instanceof Exception) { - lblCreationWarning.setText(((Exception) account).getLocalizedMessage()); + lblCreationWarning.setText(account.getClass() + ": " + ((Exception) account).getLocalizedMessage()); } progressBar.setVisible(false); }); @@ -183,4 +201,76 @@ public final class AccountsPage extends StackPane implements DecoratorPage { else if (account instanceof YggdrasilAccount) return Main.i18n("account.methods.yggdrasil"); else throw new Error(Main.i18n("account.methods.no_method") + ": " + account); } + + class CharacterSelector extends BorderPane implements MultiCharacterSelector { + private AdvancedListBox listBox = new AdvancedListBox(); + private JFXButton cancel = new JFXButton(); + + private CountDownLatch latch = new CountDownLatch(1); + private GameProfile selectedProfile = null; + + { + setStyle("-fx-padding: 8px;"); + + cancel.setText(Main.i18n("button.cancel")); + StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT); + cancel.setOnMouseClicked(e -> latch.countDown()); + + listBox.startCategory(Main.i18n("account.choose")); + + setCenter(listBox); + + HBox hbox = new HBox(); + hbox.setAlignment(Pos.CENTER_RIGHT); + hbox.getChildren().add(cancel); + setBottom(hbox); + } + + @Override + public GameProfile select(Account account, List names) throws NoSelectedCharacterException { + if (!(account instanceof YggdrasilAccount)) + return MultiCharacterSelector.DEFAULT.select(account, names); + YggdrasilAccount yggdrasilAccount = (YggdrasilAccount) account; + + for (GameProfile profile : names) { + Image image; + try { + image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4, Settings.INSTANCE.getProxy()); + } catch (Exception e) { + image = FXUtils.DEFAULT_ICON; + } + ImageView portraitView = new ImageView(); + portraitView.setSmooth(false); + if (image == FXUtils.DEFAULT_ICON) + portraitView.setImage(FXUtils.DEFAULT_ICON); + else { + portraitView.setImage(image); + portraitView.setViewport(AccountHelper.getViewport(4)); + } + FXUtils.limitSize(portraitView, 32, 32); + + IconedItem accountItem = new IconedItem(portraitView, profile.getName()); + accountItem.setOnMouseClicked(e -> { + selectedProfile = profile; + latch.countDown(); + }); + listBox.add(accountItem); + } + + JFXUtilities.runInFX(() -> Controllers.dialog(this)); + + try { + latch.await(); + + JFXUtilities.runInFX(Controllers::closeDialog); + + if (selectedProfile == null) + throw new NoSelectedCharacterException(account); + + return selectedProfile; + } catch (InterruptedException ignore) { + throw new NoSelectedCharacterException(account); + } + } + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java index b12a853fc..414ca9ae3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/LeftPaneController.java @@ -20,9 +20,11 @@ package org.jackhuang.hmcl.ui; import com.jfoenix.concurrency.JFXUtilities; import javafx.application.Platform; import javafx.scene.Node; +import javafx.scene.image.Image; import javafx.scene.layout.VBox; import javafx.scene.paint.Paint; import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.event.EventBus; import org.jackhuang.hmcl.event.ProfileChangedEvent; @@ -33,6 +35,7 @@ import org.jackhuang.hmcl.game.HMCLGameRepository; import org.jackhuang.hmcl.game.ModpackHelper; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.UnsupportedModpackException; +import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.task.Schedulers; @@ -81,13 +84,17 @@ public final class LeftPaneController { accountItem.setVersionName(Main.i18n("account.missing")); accountItem.setGameVersion(Main.i18n("message.unknown")); } else { - accountItem.setVersionName(it.getUsername()); + accountItem.setVersionName(Accounts.getCurrentCharacter(it)); accountItem.setGameVersion(AccountsPage.accountType(it)); } - if (it instanceof YggdrasilAccount) - accountItem.setImage(AccountHelper.getSkin((YggdrasilAccount) it, 4), AccountHelper.getViewport(4)); - else + if (it instanceof YggdrasilAccount) { + Image image = AccountHelper.getSkin((YggdrasilAccount) it, 4); + if (image == FXUtils.DEFAULT_ICON) + accountItem.setImage(FXUtils.DEFAULT_ICON, null); + else + accountItem.setImage(image, AccountHelper.getViewport(4)); + } else accountItem.setImage(FXUtils.DEFAULT_ICON, null); }); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.java index c0f2672e2..484821ebd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/YggdrasilAccountLoginPane.java @@ -26,10 +26,11 @@ import javafx.scene.layout.StackPane; import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.AuthInfo; -import org.jackhuang.hmcl.auth.yggdrasil.InvalidCredentialsException; +import org.jackhuang.hmcl.auth.SpecificCharacterSelector; +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.HMCLMultiCharacterSelector; +import org.jackhuang.hmcl.setting.Accounts; import org.jackhuang.hmcl.setting.Settings; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Task; @@ -68,7 +69,7 @@ public class YggdrasilAccountLoginPane extends StackPane { Task.ofResult("login", () -> { try { Account account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password); - return account.logIn(HMCLMultiCharacterSelector.INSTANCE, Settings.INSTANCE.getProxy()); + return account.logIn(new SpecificCharacterSelector(Accounts.getCurrentCharacter(oldAccount)), Settings.INSTANCE.getProxy()); } catch (Exception e) { return e; } diff --git a/HMCL/src/main/resources/assets/fxml/account-item.fxml b/HMCL/src/main/resources/assets/fxml/account-item.fxml index 063538463..cd420287c 100644 --- a/HMCL/src/main/resources/assets/fxml/account-item.fxml +++ b/HMCL/src/main/resources/assets/fxml/account-item.fxml @@ -22,7 +22,8 @@
diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index f6806f519..0c57ff7dc 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -16,10 +16,10 @@ # #author: huangyuhui, dxNeil account=Accounts +account.choose=Choose a character account.create=Create a new account account.email=Email account.failed.connect_authentication_server=Cannot connect the authentication server. Check your network. -account.failed.invalid_access_token=Invalid Access Token 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. 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 6bc5dc7ca..a4a623ff5 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -16,10 +16,10 @@ # #author: huangyuhui account=账户 +account.choose=选择一个角色 account.create=新建账户 account.email=邮箱 account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题 -account.failed.invalid_access_token=无效的访问令牌 account.failed.invalid_password=无效的密码 account.failed.invalid_token=请尝试登出并重新输入密码登录 account.failed.no_charactor=该帐号没有角色 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 c99b6f657..323849fcf 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/Account.java @@ -18,6 +18,7 @@ package org.jackhuang.hmcl.auth; import java.net.Proxy; +import java.util.HashMap; import java.util.Map; /** @@ -26,15 +27,48 @@ import java.util.Map; */ public abstract class Account { + /** + * @return the account name (mostly email) + */ public abstract String getUsername(); + /** + * log in. + * @param selector selects a character + * @return the specific player's info. + * @throws AuthenticationException if server error occurred. + */ public AuthInfo logIn(MultiCharacterSelector selector) throws AuthenticationException { return logIn(selector, Proxy.NO_PROXY); } + /** + * log in. + * @param selector selects a character + * @param proxy by which connect to the server + * @return the specific player's info. + * @throws AuthenticationException if server error occurred. + */ public abstract AuthInfo logIn(MultiCharacterSelector selector, Proxy proxy) throws AuthenticationException; public abstract void logOut(); - public abstract Map toStorage(); + protected abstract Map toStorageImpl(); + + public final Map toStorage() { + Map storage = toStorageImpl(); + if (!getProperties().isEmpty()) + storage.put("properties", getProperties()); + return storage; + } + + private Map properties = new HashMap<>(); + + /** + * To save some necessary extra information here. + * @return the property map. + */ + public final Map getProperties() { + return properties; + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java index 4babfeff4..dbb6b3acd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java @@ -17,6 +17,8 @@ */ package org.jackhuang.hmcl.auth; +import org.jackhuang.hmcl.util.Lang; + import java.util.Map; /** @@ -31,5 +33,13 @@ public abstract class AccountFactory { public abstract T fromUsername(String username, String password); - public abstract T fromStorage(Map storage); + protected abstract T fromStorageImpl(Map storage); + + public final T fromStorage(Map storage) { + T account = fromStorageImpl(storage); + Map properties = Lang.get(storage, "properties", Map.class, null); + if (properties == null) return account; + account.getProperties().putAll(properties); + return account; + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthenticationException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthenticationException.java index d4771358c..3c2036976 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthenticationException.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthenticationException.java @@ -22,9 +22,7 @@ package org.jackhuang.hmcl.auth; * @author huangyuhui */ public class AuthenticationException extends Exception { - public AuthenticationException() { - super(); } public AuthenticationException(String message) { @@ -34,4 +32,8 @@ public class AuthenticationException extends Exception { public AuthenticationException(String message, Throwable cause) { super(message, cause); } + + public AuthenticationException(Throwable cause) { + super(cause); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidCredentialsException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/InvalidCredentialsException.java similarity index 78% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidCredentialsException.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/auth/InvalidCredentialsException.java index 6cbbb4da0..4bf30f271 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidCredentialsException.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/InvalidCredentialsException.java @@ -15,23 +15,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.auth.yggdrasil; - -import org.jackhuang.hmcl.auth.AuthenticationException; +package org.jackhuang.hmcl.auth; /** - * + * throws if wrong password or logging fails for too many times. * @author huangyuhui */ public final class InvalidCredentialsException extends AuthenticationException { - private final YggdrasilAccount account; + private final Account account; - public InvalidCredentialsException(YggdrasilAccount account) { + public InvalidCredentialsException(Account account) { this.account = account; } - public YggdrasilAccount getAccount() { + public Account getAccount() { return account; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/InvalidPasswordException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/InvalidPasswordException.java new file mode 100644 index 000000000..c4ccba465 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/InvalidPasswordException.java @@ -0,0 +1,35 @@ +/* + * 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.auth; + +/** + * throws if wrong password. + */ +public class InvalidPasswordException extends AuthenticationException { + + private Account account; + + public InvalidPasswordException(Account account) { + super(); + this.account = account; + } + + public Account getAccount() { + return account; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidTokenException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/InvalidTokenException.java similarity index 92% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidTokenException.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/auth/InvalidTokenException.java index f452b88b6..42493743a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/InvalidTokenException.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/InvalidTokenException.java @@ -15,9 +15,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see {http://www.gnu.org/licenses/}. */ -package org.jackhuang.hmcl.auth.yggdrasil; +package org.jackhuang.hmcl.auth; import org.jackhuang.hmcl.auth.AuthenticationException; +import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; /** * 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 206ac9140..69b229aef 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccount.java @@ -68,7 +68,7 @@ public class OfflineAccount extends Account { } @Override - public Map toStorage() { + public Map toStorageImpl() { return Lang.mapOf( new Pair<>("uuid", uuid), new Pair<>("username", username) 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 c3f57dc75..35c2d2455 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/OfflineAccountFactory.java @@ -37,7 +37,7 @@ public class OfflineAccountFactory extends AccountFactory { } @Override - public OfflineAccount fromStorage(Map storage) { + public OfflineAccount fromStorageImpl(Map storage) { Object username = storage.get("username"); if (username == null || !(username instanceof String)) throw new IllegalStateException("Offline account configuration malformed."); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/ServerDisconnectException.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/ServerDisconnectException.java new file mode 100644 index 000000000..4ccbaefb2 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/ServerDisconnectException.java @@ -0,0 +1,35 @@ +/* + * 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.auth; + +public class ServerDisconnectException extends AuthenticationException { + public ServerDisconnectException() { + } + + public ServerDisconnectException(String message) { + super(message); + } + + public ServerDisconnectException(String message, Throwable cause) { + super(message, cause); + } + + public ServerDisconnectException(Throwable cause) { + super(cause); + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLMultiCharacterSelector.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/SpecificCharacterSelector.java similarity index 64% rename from HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLMultiCharacterSelector.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/auth/SpecificCharacterSelector.java index cf58e929c..7a31cd267 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLMultiCharacterSelector.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/SpecificCharacterSelector.java @@ -1,7 +1,7 @@ /* * 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 @@ -15,25 +15,28 @@ * 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.game; +package org.jackhuang.hmcl.auth; -import org.jackhuang.hmcl.auth.Account; -import org.jackhuang.hmcl.auth.MultiCharacterSelector; -import org.jackhuang.hmcl.auth.NoSelectedCharacterException; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; import java.util.List; /** - * @author huangyuhui + * Select character by name. */ -public final class HMCLMultiCharacterSelector implements MultiCharacterSelector { - public static final HMCLMultiCharacterSelector INSTANCE = new HMCLMultiCharacterSelector(); +public class SpecificCharacterSelector implements MultiCharacterSelector { + private final String id; - private HMCLMultiCharacterSelector() {} + /** + * Constructor. + * @param id character's name. + */ + public SpecificCharacterSelector(String id) { + this.id = id; + } @Override public GameProfile select(Account account, List names) throws NoSelectedCharacterException { - return names.stream().findFirst().orElseThrow(() -> new NoSelectedCharacterException(account)); + return names.stream().filter(profile -> profile.getName().equals(id)).findAny().orElseThrow(() -> new NoSelectedCharacterException(account)); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.java index 77bbceab2..50b464e01 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationRequest.java @@ -32,8 +32,8 @@ public final class AuthenticationRequest { private final String clientToken; private final Map agent = Lang.mapOf( - new Pair("name", "minecraft"), - new Pair("version", 1)); + new Pair<>("name", "minecraft"), + new Pair<>("version", 1)); private final boolean requestUser = true; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Response.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationResponse.java similarity index 88% rename from HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Response.java rename to HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationResponse.java index 88a850bf7..efd5488a7 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/Response.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthenticationResponse.java @@ -21,7 +21,7 @@ package org.jackhuang.hmcl.auth.yggdrasil; * * @author huangyuhui */ -public final class Response { +public final class AuthenticationResponse { private final String accessToken; private final String clientToken; @@ -32,11 +32,11 @@ public final class Response { private final String errorMessage; private final String cause; - public Response() { + public AuthenticationResponse() { this(null, null, null, null, null, null, null, null); } - public Response(String accessToken, String clientToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user, String error, String errorMessage, String cause) { + public AuthenticationResponse(String accessToken, String clientToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user, String error, String errorMessage, String cause) { this.accessToken = accessToken; this.clientToken = clientToken; this.selectedProfile = selectedProfile; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ProfileResponse.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ProfileResponse.java new file mode 100644 index 000000000..4a419099d --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ProfileResponse.java @@ -0,0 +1,46 @@ +/* + * 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.auth.yggdrasil; + +public class ProfileResponse { + private final String id; + private final String name; + private final PropertyMap properties; + + public ProfileResponse() { + this("", "", null); + } + + public ProfileResponse(String id, String name, PropertyMap properties) { + this.id = id; + this.name = name; + this.properties = properties; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public PropertyMap getProperties() { + return properties; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ProfileTexture.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ProfileTexture.java new file mode 100644 index 000000000..997cfdb4b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/ProfileTexture.java @@ -0,0 +1,53 @@ +/* + * 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.auth.yggdrasil; + +import org.jackhuang.hmcl.util.Immutable; + +import java.util.Map; + +@Immutable +public final class ProfileTexture { + + private final String url; + private final Map metadata; + + public ProfileTexture() { + this(null, null); + } + + public ProfileTexture(String url, Map metadata) { + this.url = url; + this.metadata = metadata; + } + + public String getUrl() { + return url; + } + + public String getMetadata(String key) { + if (metadata == null) + return null; + else + return metadata.get(key); + } + + public enum Type { + SKIN, CAPE + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/TextureResponse.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/TextureResponse.java new file mode 100644 index 000000000..9e9b43da1 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/TextureResponse.java @@ -0,0 +1,53 @@ +/* + * 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.auth.yggdrasil; + +import org.jackhuang.hmcl.util.Immutable; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +@Immutable +public final class TextureResponse { + private final UUID profileId; + private final String profileName; + private final Map textures; + + public TextureResponse() { + this(UUID.randomUUID(), "", Collections.emptyMap()); + } + + public TextureResponse(UUID profileId, String profileName, Map textures) { + this.profileId = profileId; + this.profileName = profileName; + this.textures = textures; + } + + public UUID getProfileId() { + return profileId; + } + + public String getProfileName() { + return profileName; + } + + public Map getTextures() { + return textures == null ? null : Collections.unmodifiableMap(textures); + } +} 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 3fa4bd2d7..4de9fddc3 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 @@ -21,6 +21,7 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonParseException; import org.jackhuang.hmcl.auth.*; +import org.jackhuang.hmcl.util.Charsets; import org.jackhuang.hmcl.util.NetworkUtils; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.UUIDTypeAdapter; @@ -28,10 +29,7 @@ import org.jackhuang.hmcl.util.UUIDTypeAdapter; import java.io.IOException; import java.net.Proxy; import java.net.URL; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; /** * @@ -63,7 +61,7 @@ public final class YggdrasilAccount extends Account { this.password = password; } - public String getUserId() { + public String getCurrentCharacterName() { return userId; } @@ -146,7 +144,7 @@ public final class YggdrasilAccount extends Account { } private void logIn1(URL url, Object input, Proxy proxy) throws AuthenticationException { - Response response = makeRequest(url, input, proxy); + AuthenticationResponse response = makeRequest(url, input, proxy); if (response == null || !clientToken.equals(response.getClientToken())) throw new AuthenticationException("Client token changed"); @@ -183,13 +181,13 @@ public final class YggdrasilAccount extends Account { } @Override - public Map toStorage() { + public Map toStorageImpl() { HashMap result = new HashMap<>(); result.put(STORAGE_KEY_USER_NAME, getUsername()); result.put(STORAGE_KEY_CLIENT_TOKEN, getClientToken()); - if (getUserId() != null) - result.put(STORAGE_KEY_USER_ID, getUserId()); + if (getCurrentCharacterName() != null) + result.put(STORAGE_KEY_USER_ID, getCurrentCharacterName()); if (!userProperties.isEmpty()) result.put(STORAGE_KEY_USER_PROPERTIES, userProperties.toList()); GameProfile profile = selectedProfile; @@ -207,10 +205,10 @@ public final class YggdrasilAccount extends Account { return result; } - private Response makeRequest(URL url, Object input, Proxy proxy) throws AuthenticationException { + private AuthenticationResponse makeRequest(URL url, Object input, Proxy proxy) throws AuthenticationException { try { String jsonResult = input == null ? NetworkUtils.doGet(url, proxy) : NetworkUtils.doPost(url, GSON.toJson(input), "application/json", proxy); - Response response = GSON.fromJson(jsonResult, Response.class); + AuthenticationResponse response = GSON.fromJson(jsonResult, AuthenticationResponse.class); if (response == null) return null; if (!StringUtils.isBlank(response.getError())) { @@ -219,12 +217,14 @@ public final class YggdrasilAccount extends Account { throw new InvalidCredentialsException(this); else if (response.getErrorMessage().contains("Invalid token")) throw new InvalidTokenException(this); + else if (response.getErrorMessage().contains("Invalid username or password")) + throw new InvalidPasswordException(this); throw new AuthenticationException(response.getError() + ": " + response.getErrorMessage()); } return response; } catch (IOException e) { - throw new AuthenticationException("Unable to connect to authentication server", e); + throw new ServerDisconnectException(e); } catch (JsonParseException e) { throw new AuthenticationException("Unable to parse server response", e); } @@ -242,12 +242,31 @@ public final class YggdrasilAccount extends Account { } } + public ProfileTexture getSkin(GameProfile profile) throws IOException, JsonParseException { + if (StringUtils.isBlank(userId)) + throw new IllegalStateException("Not logged in"); + + ProfileResponse response = GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(BASE_PROFILE + UUIDTypeAdapter.fromUUID(profile.getId()))), ProfileResponse.class); + if (response.getProperties() == null) return null; + Property textureProperty = response.getProperties().get("textures"); + if (textureProperty == null) return null; + + TextureResponse texture; + String json = new String(Base64.getDecoder().decode(textureProperty.getValue()), Charsets.UTF_8); + texture = GSON.fromJson(json, TextureResponse.class); + if (texture == null || texture.getTextures() == null) + return null; + + return texture.getTextures().get(ProfileTexture.Type.SKIN); + } + @Override public String toString() { return "YggdrasilAccount[username=" + getUsername() + "]"; } - private static final String BASE_URL = "https://authserver.mojang.com/"; + private static final String BASE_URL = "http://localhost:8080/authserver/"; //"https://authserver.mojang.com/"; + private static final String BASE_PROFILE = "http://localhost:8080/sessionserver/session/minecraft/profile/"; //"https://sessionserver.mojang.com/session/minecraft/profile/"; private static final URL ROUTE_AUTHENTICATE = NetworkUtils.toURL(BASE_URL + "authenticate"); private static final URL ROUTE_REFRESH = NetworkUtils.toURL(BASE_URL + "refresh"); private static final URL ROUTE_VALIDATE = NetworkUtils.toURL(BASE_URL + "validate"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java index 70453c3ed..1a96ddddc 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java @@ -45,7 +45,7 @@ public final class YggdrasilAccountFactory extends AccountFactory storage) { + public YggdrasilAccount fromStorageImpl(Map storage) { String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class) .orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME));