diff --git a/build.gradle b/build.gradle index c2d799d..a0e2bf2 100644 --- a/build.gradle +++ b/build.gradle @@ -29,8 +29,8 @@ group = 'com.skcraft' version = '4.4-SNAPSHOT' - sourceCompatibility = 1.6 - targetCompatibility = 1.6 + sourceCompatibility = 1.8 + targetCompatibility = 1.8 repositories { mavenCentral() diff --git a/launcher/src/main/java/com/skcraft/launcher/Launcher.java b/launcher/src/main/java/com/skcraft/launcher/Launcher.java index 5b4aa18..8ca6638 100644 --- a/launcher/src/main/java/com/skcraft/launcher/Launcher.java +++ b/launcher/src/main/java/com/skcraft/launcher/Launcher.java @@ -12,8 +12,9 @@ import com.google.common.base.Supplier; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; -import com.skcraft.launcher.auth.AccountList; import com.skcraft.launcher.auth.LoginService; +import com.skcraft.launcher.auth.NewAccountList; +import com.skcraft.launcher.auth.UserType; import com.skcraft.launcher.auth.YggdrasilLoginService; import com.skcraft.launcher.launch.LaunchSupervisor; import com.skcraft.launcher.model.minecraft.Library; @@ -63,7 +64,7 @@ @Getter private final Properties properties; @Getter private final InstanceList instances; @Getter private final Configuration config; - @Getter private final AccountList accounts; + @Getter private final NewAccountList accounts; @Getter private final AssetsRoot assets; @Getter private final LaunchSupervisor launchSupervisor = new LaunchSupervisor(this); @Getter private final UpdateManager updateManager = new UpdateManager(this); @@ -96,14 +97,10 @@ this.instances = new InstanceList(this); this.assets = new AssetsRoot(new File(baseDir, "assets")); this.config = Persistence.load(new File(configDir, "config.json"), Configuration.class); - this.accounts = Persistence.load(new File(configDir, "accounts.dat"), AccountList.class); + this.accounts = Persistence.load(new File(configDir, "accounts.dat"), NewAccountList.class); setDefaultConfig(); - if (accounts.getSize() > 0) { - accounts.setSelectedItem(accounts.getElementAt(0)); - } - executor.submit(new Runnable() { @Override public void run() { @@ -161,12 +158,20 @@ } /** - * Get a login service. + * Get the Yggdrasil login service. * - * @return a login service + * @return the Yggdrasil (legacy) login service */ - public LoginService getLoginService() { - return new YggdrasilLoginService(HttpRequest.url(getProperties().getProperty("yggdrasilAuthUrl"))); + public YggdrasilLoginService getYggdrasil() { + return new YggdrasilLoginService(HttpRequest.url(getProperties().getProperty("yggdrasilAuthUrl")), accounts.getClientId()); + } + + public LoginService getLoginService(UserType type) { + if (type == UserType.MICROSOFT) { + return null; // TODO: Microsoft login service + } else { + return getYggdrasil(); + } } /** diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/AccountList.java b/launcher/src/main/java/com/skcraft/launcher/auth/AccountList.java index ddf9cb0..a86356d 100644 --- a/launcher/src/main/java/com/skcraft/launcher/auth/AccountList.java +++ b/launcher/src/main/java/com/skcraft/launcher/auth/AccountList.java @@ -29,7 +29,7 @@ getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE) -public class AccountList extends AbstractListModel implements ComboBoxModel { +public class AccountList extends AbstractListModel implements ComboBoxModel { @JsonProperty @Getter diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/LoginService.java b/launcher/src/main/java/com/skcraft/launcher/auth/LoginService.java index 60419ed..f6da3d2 100644 --- a/launcher/src/main/java/com/skcraft/launcher/auth/LoginService.java +++ b/launcher/src/main/java/com/skcraft/launcher/auth/LoginService.java @@ -7,7 +7,6 @@ package com.skcraft.launcher.auth; import java.io.IOException; -import java.util.List; /** * A service for creating authenticated sessions. @@ -15,17 +14,15 @@ public interface LoginService { /** - * Attempt to login with the given details. + * Attempt to restore a saved session into an active session. * - * @param agent the game to authenticate for, such as "Minecraft" - * @param id the login ID - * @param password the password - * @return a list of authenticated sessions, which corresponds to identities + * @param savedSession Session to restore + * @return An authenticated session, which corresponds to a Minecraft account * @throws IOException thrown on I/O error * @throws InterruptedException thrown if interrupted * @throws AuthenticationException thrown on an authentication error */ - List login(String agent, String id, String password) + Session restore(SavedSession savedSession) throws IOException, InterruptedException, AuthenticationException; } diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/NewAccountList.java b/launcher/src/main/java/com/skcraft/launcher/auth/NewAccountList.java new file mode 100644 index 0000000..c88419f --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/auth/NewAccountList.java @@ -0,0 +1,74 @@ +package com.skcraft.launcher.auth; + +import com.beust.jcommander.internal.Lists; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.skcraft.launcher.dialog.component.ListListenerReducer; +import com.skcraft.launcher.persistence.Scrambled; +import lombok.Data; +import org.apache.commons.lang.RandomStringUtils; + +import javax.swing.*; +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import java.util.List; + +/** + * Persisted account list + */ +@Scrambled("ACCOUNT_LIST_NOT_SECURITY!") +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class NewAccountList implements ListModel { + private List accounts = Lists.newArrayList(); + private String clientId = RandomStringUtils.randomAlphanumeric(24); + + @JsonIgnore private final ListListenerReducer listeners = new ListListenerReducer(); + + public synchronized void add(SavedSession session) { + accounts.add(session); + + int index = accounts.size() - 1; + listeners.intervalAdded(new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, index, index)); + } + + public synchronized void remove(SavedSession session) { + int index = accounts.indexOf(session); + + if (index > -1) { + accounts.remove(index); + listeners.intervalRemoved(new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, index, index)); + } + } + + public synchronized void update(SavedSession newSavedSession) { + int index = accounts.indexOf(newSavedSession); + + if (index > -1) { + accounts.set(index, newSavedSession); + listeners.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, index, index)); + } else { + this.add(newSavedSession); + } + } + + @Override + public int getSize() { + return accounts.size(); + } + + @Override + public SavedSession getElementAt(int index) { + return accounts.get(index); + } + + @Override + public void addListDataListener(ListDataListener l) { + listeners.addListDataListener(l); + } + + @Override + public void removeListDataListener(ListDataListener l) { + listeners.removeListDataListener(l); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/SavedSession.java b/launcher/src/main/java/com/skcraft/launcher/auth/SavedSession.java new file mode 100644 index 0000000..5dffd88 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/auth/SavedSession.java @@ -0,0 +1,36 @@ +package com.skcraft.launcher.auth; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import org.apache.commons.lang.builder.HashCodeBuilder; + +/** + * Represents a session saved to disk. + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SavedSession { + private UserType type; + private String uuid; + private String username; + private String accessToken; + private String refreshToken; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + SavedSession that = (SavedSession) o; + + return getUuid().equals(that.getUuid()); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(uuid) + .toHashCode(); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/Session.java b/launcher/src/main/java/com/skcraft/launcher/auth/Session.java index 95b3e58..00d75a5 100644 --- a/launcher/src/main/java/com/skcraft/launcher/auth/Session.java +++ b/launcher/src/main/java/com/skcraft/launcher/auth/Session.java @@ -71,4 +71,19 @@ */ boolean isOnline(); + /** + * Convert this session to a saved session + * @return Saved session that represents this active session + */ + default SavedSession toSavedSession() { + SavedSession savedSession = new SavedSession(); + + savedSession.setType(getUserType()); + savedSession.setUsername(getName()); + savedSession.setUuid(getUuid()); + savedSession.setAccessToken(getAccessToken()); + + return savedSession; + } + } diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/UserType.java b/launcher/src/main/java/com/skcraft/launcher/auth/UserType.java index 7dfeff6..1d60b29 100644 --- a/launcher/src/main/java/com/skcraft/launcher/auth/UserType.java +++ b/launcher/src/main/java/com/skcraft/launcher/auth/UserType.java @@ -18,7 +18,11 @@ /** * Mojang accounts login with an email address. */ - MOJANG; + MOJANG, + /** + * Microsoft accounts login via OAuth. + */ + MICROSOFT; /** * Return a lowercase version of the enum type. diff --git a/launcher/src/main/java/com/skcraft/launcher/auth/YggdrasilLoginService.java b/launcher/src/main/java/com/skcraft/launcher/auth/YggdrasilLoginService.java index b686953..43bf72c 100644 --- a/launcher/src/main/java/com/skcraft/launcher/auth/YggdrasilLoginService.java +++ b/launcher/src/main/java/com/skcraft/launcher/auth/YggdrasilLoginService.java @@ -11,45 +11,63 @@ import lombok.Data; import lombok.NonNull; import lombok.ToString; +import lombok.extern.java.Log; import java.io.IOException; import java.net.URL; import java.util.Collections; -import java.util.List; import java.util.Map; /** * Creates authenticated sessions using the Mojang Yggdrasil login protocol. */ +@Log public class YggdrasilLoginService implements LoginService { private final URL authUrl; + private final String clientId; /** * Create a new login service with the given authentication URL. * * @param authUrl the authentication URL + * @param clientId */ - public YggdrasilLoginService(@NonNull URL authUrl) { + public YggdrasilLoginService(@NonNull URL authUrl, String clientId) { this.authUrl = authUrl; + this.clientId = clientId; + } + + public Session login(String agent, String id, String password) + throws IOException, InterruptedException, AuthenticationException { + AuthenticatePayload payload = new AuthenticatePayload(new Agent(agent), id, password, clientId); + + return call(this.authUrl, payload); } @Override - public List login(String agent, String id, String password) + public Session restore(SavedSession savedSession) throws IOException, InterruptedException, AuthenticationException { - Object payload = new AuthenticatePayload(new Agent(agent), id, password); + RefreshPayload payload = new RefreshPayload(savedSession.getAccessToken(), clientId); - HttpRequest request = HttpRequest - .post(authUrl) + return call(new URL(this.authUrl, "/refresh"), payload); + } + + private Session call(URL url, Object payload) + throws IOException, InterruptedException, AuthenticationException { + HttpRequest req = HttpRequest + .post(url) .bodyJson(payload) .execute(); - if (request.getResponseCode() != 200) { - ErrorResponse error = request.returnContent().asJson(ErrorResponse.class); + if (req.getResponseCode() != 200) { + ErrorResponse error = req.returnContent().asJson(ErrorResponse.class); + log.warning(error.toString()); throw new AuthenticationException(error.getErrorMessage(), error.getErrorMessage()); } else { - AuthenticateResponse response = request.returnContent().asJson(AuthenticateResponse.class); - return response.getAvailableProfiles(); + AuthenticateResponse response = req.returnContent().asJson(AuthenticateResponse.class); + + return response.getSelectedProfile(); } } @@ -64,6 +82,14 @@ private final Agent agent; private final String username; private final String password; + private final String clientToken; + } + + @Data + private static class RefreshPayload { + private final String accessToken; + private final String clientToken; + private boolean requestUser = true; } @Data @@ -71,8 +97,7 @@ private static class AuthenticateResponse { private String accessToken; private String clientToken; - @JsonManagedReference private List availableProfiles; - private Profile selectedProfile; + @JsonManagedReference private Profile selectedProfile; } @Data diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/AccountSelectDialog.java b/launcher/src/main/java/com/skcraft/launcher/dialog/AccountSelectDialog.java new file mode 100644 index 0000000..b43a625 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/AccountSelectDialog.java @@ -0,0 +1,178 @@ +package com.skcraft.launcher.dialog; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.skcraft.concurrency.ObservableFuture; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.auth.LoginService; +import com.skcraft.launcher.auth.SavedSession; +import com.skcraft.launcher.auth.Session; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.swing.LinedBoxPanel; +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.util.SharedLocale; +import com.skcraft.launcher.util.SwingExecutor; +import lombok.RequiredArgsConstructor; + +import javax.swing.*; +import java.awt.*; +import java.net.URL; +import java.util.concurrent.Callable; + +import static com.skcraft.launcher.util.HttpRequest.url; + +public class AccountSelectDialog extends JDialog { + private final JList accountList; + private final JButton loginButton = new JButton(SharedLocale.tr("login.login")); + private final JButton cancelButton = new JButton(SharedLocale.tr("button.cancel")); + private final JButton addAccountButton = new JButton(SharedLocale.tr("accounts.addNew")); + private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); + + private final Launcher launcher; + private Session selected; + + public AccountSelectDialog(Window owner, Launcher launcher) { + super(owner, ModalityType.DOCUMENT_MODAL); + + this.launcher = launcher; + this.accountList = new JList<>(launcher.getAccounts()); + + setTitle(SharedLocale.tr("accounts.title")); + initComponents(); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setMinimumSize(new Dimension(420, 50)); + setResizable(false); + pack(); + setLocationRelativeTo(owner); + } + + private void initComponents() { + accountList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + accountList.setLayoutOrientation(JList.HORIZONTAL_WRAP); + accountList.setVisibleRowCount(0); + accountList.setCellRenderer(new AccountRenderer()); + + JScrollPane accountPane = new JScrollPane(accountList); + accountPane.setPreferredSize(new Dimension(250, 100)); + accountPane.setAlignmentX(LEFT_ALIGNMENT); + + loginButton.setFont(loginButton.getFont().deriveFont(Font.BOLD)); + + buttonsPanel.setBorder(BorderFactory.createEmptyBorder(26, 13, 13, 13)); + buttonsPanel.addGlue(); + buttonsPanel.addElement(loginButton); + buttonsPanel.addElement(cancelButton); + + JPanel listPane = new JPanel(); + listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS)); + listPane.add(accountPane); + listPane.add(Box.createVerticalStrut(5)); + listPane.add(addAccountButton); + listPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + add(listPane, BorderLayout.CENTER); + add(buttonsPanel, BorderLayout.SOUTH); + + loginButton.addActionListener(ev -> attemptLogin(accountList.getSelectedValue())); + cancelButton.addActionListener(ev -> dispose()); + + addAccountButton.addActionListener(ev -> { + Session newSession = LoginDialog.showLoginRequest(this, launcher); + + if (newSession != null) { + launcher.getAccounts().add(newSession.toSavedSession()); + } + }); + } + + @Override + public void dispose() { + accountList.setModel(new DefaultListModel<>()); + super.dispose(); + } + + public static Session showAccountRequest(Window owner, Launcher launcher) { + AccountSelectDialog dialog = new AccountSelectDialog(owner, launcher); + dialog.setVisible(true); + + if (dialog.selected != null) { + launcher.getAccounts().update(dialog.selected.toSavedSession()); + Persistence.commitAndForget(launcher.getAccounts()); + } + + return dialog.selected; + } + + private void setResult(Session result) { + this.selected = result; + dispose(); + } + + private void attemptLogin(SavedSession session) { + LoginService loginService = launcher.getLoginService(session.getType()); + RestoreSessionCallable callable = new RestoreSessionCallable(loginService, session); + + ObservableFuture future = new ObservableFuture<>(launcher.getExecutor().submit(callable), callable); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(Session result) { +// session.setAccessToken(result.getAccessToken()); + setResult(result); + } + + @Override + public void onFailure(Throwable t) { + t.printStackTrace(); + } + }, SwingExecutor.INSTANCE); + + ProgressDialog.showProgress(this, future, SharedLocale.tr("login.loggingInTitle"), + SharedLocale.tr("login.loggingInStatus")); + SwingHelper.addErrorDialogCallback(this, future); + } + + @RequiredArgsConstructor + private static class RestoreSessionCallable implements Callable, ProgressObservable { + private final LoginService service; + private final SavedSession session; + + @Override + public Session call() throws Exception { + return service.restore(session); + } + + @Override + public String getStatus() { + return SharedLocale.tr("accounts.refreshingStatus"); + } + + @Override + public double getProgress() { + return -1; + } + } + + private static class AccountRenderer extends JLabel implements ListCellRenderer { + public AccountRenderer() { + setHorizontalAlignment(CENTER); + } + + @Override + public Component getListCellRendererComponent(JList list, SavedSession value, int index, boolean isSelected, boolean cellHasFocus) { + setText(value.getUsername()); + + URL avatarUrl = url("https://visage.surgeplay.com/face/24/" + value.getUuid() + ".png"); + setIcon(new ImageIcon(avatarUrl)); + + if (isSelected) { + setOpaque(true); + setBackground(new Color(0x397BBF)); + } else { + setOpaque(false); + } + + return this; + } + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/LoginDialog.java b/launcher/src/main/java/com/skcraft/launcher/dialog/LoginDialog.java index 41a6611..1fc0709 100644 --- a/launcher/src/main/java/com/skcraft/launcher/dialog/LoginDialog.java +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/LoginDialog.java @@ -6,16 +6,18 @@ package com.skcraft.launcher.dialog; -import com.google.common.base.Strings; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.skcraft.concurrency.ObservableFuture; import com.skcraft.concurrency.ProgressObservable; import com.skcraft.launcher.Configuration; import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.auth.*; -import com.skcraft.launcher.swing.*; +import com.skcraft.launcher.auth.Account; +import com.skcraft.launcher.auth.AuthenticationException; +import com.skcraft.launcher.auth.Session; +import com.skcraft.launcher.auth.YggdrasilLoginService; import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.swing.*; import com.skcraft.launcher.util.SharedLocale; import com.skcraft.launcher.util.SwingExecutor; import lombok.Getter; @@ -23,10 +25,10 @@ import javax.swing.*; import java.awt.*; -import java.awt.event.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import java.io.IOException; import java.util.Date; -import java.util.List; import java.util.concurrent.Callable; /** @@ -35,16 +37,12 @@ public class LoginDialog extends JDialog { private final Launcher launcher; - @Getter private final AccountList accounts; @Getter private Session session; - private final JComboBox idCombo = new JComboBox(); + private final JTextField usernameText = new JTextField(); private final JPasswordField passwordText = new JPasswordField(); - private final JCheckBox rememberIdCheck = new JCheckBox(SharedLocale.tr("login.rememberId")); - private final JCheckBox rememberPassCheck = new JCheckBox(SharedLocale.tr("login.rememberPassword")); private final JButton loginButton = new JButton(SharedLocale.tr("login.login")); private final LinkButton recoverButton = new LinkButton(SharedLocale.tr("login.recoverAccount")); - private final JButton offlineButton = new JButton(SharedLocale.tr("login.playOffline")); private final JButton cancelButton = new JButton(SharedLocale.tr("button.cancel")); private final FormPanel formPanel = new FormPanel(); private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); @@ -59,7 +57,6 @@ super(owner, ModalityType.DOCUMENT_MODAL); this.launcher = launcher; - this.accounts = launcher.getAccounts(); setTitle(SharedLocale.tr("login.title")); initComponents(); @@ -73,39 +70,21 @@ addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent event) { - removeListeners(); dispose(); } }); } @SuppressWarnings("unchecked") - private void removeListeners() { - idCombo.setModel(new DefaultComboBoxModel()); - } - - @SuppressWarnings("unchecked") private void initComponents() { - idCombo.setModel(getAccounts()); - updateSelection(); - - rememberIdCheck.setBorder(BorderFactory.createEmptyBorder()); - rememberPassCheck.setBorder(BorderFactory.createEmptyBorder()); - idCombo.setEditable(true); - idCombo.getEditor().selectAll(); + usernameText.setEditable(true); loginButton.setFont(loginButton.getFont().deriveFont(Font.BOLD)); - formPanel.addRow(new JLabel(SharedLocale.tr("login.idEmail")), idCombo); + formPanel.addRow(new JLabel(SharedLocale.tr("login.idEmail")), usernameText); formPanel.addRow(new JLabel(SharedLocale.tr("login.password")), passwordText); - formPanel.addRow(new JLabel(), rememberIdCheck); - formPanel.addRow(new JLabel(), rememberPassCheck); buttonsPanel.setBorder(BorderFactory.createEmptyBorder(26, 13, 13, 13)); - if (launcher.getConfig().isOfflineEnabled()) { - buttonsPanel.addElement(offlineButton); - buttonsPanel.addElement(Box.createHorizontalStrut(2)); - } buttonsPanel.addElement(recoverButton); buttonsPanel.addGlue(); buttonsPanel.addElement(loginButton); @@ -118,162 +97,24 @@ passwordText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); - idCombo.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - updateSelection(); - } - }); - - idCombo.getEditor().getEditorComponent().addMouseListener(new PopupMouseAdapter() { - @Override - protected void showPopup(MouseEvent e) { - popupManageMenu(e.getComponent(), e.getX(), e.getY()); - } - }); - recoverButton.addActionListener( ActionListeners.openURL(recoverButton, launcher.getProperties().getProperty("resetPasswordUrl"))); - loginButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - prepareLogin(); - } - }); - - offlineButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - setResult(new OfflineSession(launcher.getProperties().getProperty("offlinePlayerName"))); - removeListeners(); - dispose(); - } - }); - - cancelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - removeListeners(); - dispose(); - } - }); - - rememberPassCheck.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (rememberPassCheck.isSelected()) { - rememberIdCheck.setSelected(true); - } - } - }); - - rememberIdCheck.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (!rememberIdCheck.isSelected()) { - rememberPassCheck.setSelected(false); - } - } - }); - } - - private void popupManageMenu(Component component, int x, int y) { - Object selected = idCombo.getSelectedItem(); - JPopupMenu popup = new JPopupMenu(); - JMenuItem menuItem; - - if (selected != null && selected instanceof Account) { - final Account account = (Account) selected; - - menuItem = new JMenuItem(SharedLocale.tr("login.forgetUser")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - accounts.remove(account); - Persistence.commitAndForget(accounts); - } - }); - popup.add(menuItem); - - if (!Strings.isNullOrEmpty(account.getPassword())) { - menuItem = new JMenuItem(SharedLocale.tr("login.forgetPassword")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - account.setPassword(null); - Persistence.commitAndForget(accounts); - } - }); - popup.add(menuItem); - } - } - - menuItem = new JMenuItem(SharedLocale.tr("login.forgetAllPasswords")); - menuItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (SwingHelper.confirmDialog(LoginDialog.this, - SharedLocale.tr("login.confirmForgetAllPasswords"), - SharedLocale.tr("login.forgetAllPasswordsTitle"))) { - accounts.forgetPasswords(); - Persistence.commitAndForget(accounts); - } - } - }); - popup.add(menuItem); - - popup.show(component, x, y); - } - - private void updateSelection() { - Object selected = idCombo.getSelectedItem(); - - if (selected != null && selected instanceof Account) { - Account account = (Account) selected; - String password = account.getPassword(); - - rememberIdCheck.setSelected(true); - if (!Strings.isNullOrEmpty(password)) { - rememberPassCheck.setSelected(true); - passwordText.setText(password); - } else { - rememberPassCheck.setSelected(false); - } - } else { - passwordText.setText(""); - rememberIdCheck.setSelected(true); - rememberPassCheck.setSelected(false); - } + loginButton.addActionListener(e -> prepareLogin()); + cancelButton.addActionListener(e -> dispose()); } @SuppressWarnings("deprecation") private void prepareLogin() { - Object selected = idCombo.getSelectedItem(); - - if (selected != null && selected instanceof Account) { - Account account = (Account) selected; + if (!usernameText.getText().isEmpty()) { String password = passwordText.getText(); if (password == null || password.isEmpty()) { SwingHelper.showErrorDialog(this, SharedLocale.tr("login.noPasswordError"), SharedLocale.tr("login.noPasswordTitle")); } else { - if (rememberPassCheck.isSelected()) { - account.setPassword(password); - } else { - account.setPassword(null); - } - - if (rememberIdCheck.isSelected()) { - accounts.add(account); - } else { - accounts.remove(account); - } - + Account account = new Account(usernameText.getText()); account.setLastUsed(new Date()); - Persistence.commitAndForget(accounts); - attemptLogin(account, password); } } else { @@ -303,7 +144,6 @@ private void setResult(Session session) { this.session = session; - removeListeners(); dispose(); } @@ -324,12 +164,12 @@ @Override public Session call() throws AuthenticationException, IOException, InterruptedException { - LoginService service = launcher.getLoginService(); - List identities = service.login(launcher.getProperties().getProperty("agentName"), account.getId(), password); + YggdrasilLoginService service = launcher.getYggdrasil(); + Session identity = service.login(launcher.getProperties().getProperty("agentName"), account.getId(), password); - // The list of identities (profiles in Mojang terms) corresponds to whether the account + // The presence of the identity (profile in Mojang terms) corresponds to whether the account // owns the game, so we need to check that - if (identities.size() > 0) { + if (identity != null) { // Set offline enabled flag to true Configuration config = launcher.getConfig(); if (!config.isOfflineEnabled()) { @@ -337,8 +177,7 @@ Persistence.commitAndForget(config); } - Persistence.commitAndForget(getAccounts()); - return identities.get(0); + return identity; } else { throw new AuthenticationException("Minecraft not owned", SharedLocale.tr("login.minecraftNotOwnedError")); } diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/component/ListListenerReducer.java b/launcher/src/main/java/com/skcraft/launcher/dialog/component/ListListenerReducer.java new file mode 100644 index 0000000..acbddba --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/component/ListListenerReducer.java @@ -0,0 +1,34 @@ +package com.skcraft.launcher.dialog.component; + +import com.beust.jcommander.internal.Lists; + +import javax.swing.event.ListDataEvent; +import javax.swing.event.ListDataListener; +import java.util.List; + +public class ListListenerReducer implements ListDataListener { + private final List listeners = Lists.newArrayList(); + + @Override + public void intervalAdded(ListDataEvent e) { + listeners.forEach(it -> it.intervalAdded(e)); + } + + @Override + public void intervalRemoved(ListDataEvent e) { + listeners.forEach(it -> it.intervalRemoved(e)); + } + + @Override + public void contentsChanged(ListDataEvent e) { + listeners.forEach(it -> it.contentsChanged(e)); + } + + public void addListDataListener(ListDataListener l) { + listeners.add(l); + } + + public void removeListDataListener(ListDataListener l) { + listeners.remove(l); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/launch/LaunchSupervisor.java b/launcher/src/main/java/com/skcraft/launcher/launch/LaunchSupervisor.java index 809ff34..9dd8a27 100644 --- a/launcher/src/main/java/com/skcraft/launcher/launch/LaunchSupervisor.java +++ b/launcher/src/main/java/com/skcraft/launcher/launch/LaunchSupervisor.java @@ -13,7 +13,7 @@ import com.skcraft.launcher.Instance; import com.skcraft.launcher.Launcher; import com.skcraft.launcher.auth.Session; -import com.skcraft.launcher.dialog.LoginDialog; +import com.skcraft.launcher.dialog.AccountSelectDialog; import com.skcraft.launcher.dialog.ProgressDialog; import com.skcraft.launcher.launch.LaunchOptions.UpdatePolicy; import com.skcraft.launcher.persistence.Persistence; @@ -61,7 +61,7 @@ if (options.getSession() != null) { session = options.getSession(); } else { - session = LoginDialog.showLoginRequest(window, launcher); + session = AccountSelectDialog.showAccountRequest(window, launcher); if (session == null) { return; } diff --git a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties index 23b54eb..18e99bc 100644 --- a/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties +++ b/launcher/src/main/resources/com/skcraft/launcher/lang/Launcher.properties @@ -87,6 +87,10 @@ launcher.requiresUpdateHint=(requires update) launcher.updatePendingHint=(pending update) +accounts.title=Select the account to play with +accounts.refreshingStatus=Refreshing login session... +accounts.addNew=Add a new account... + login.rememberId=Remember my account in the list login.rememberPassword=Remember my password login.login=Login...