launcher: add offline accounts

This commit is contained in:
Bixilon 2020-12-23 13:46:46 +01:00
parent 9adb18681a
commit 2fa7fb1225
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
15 changed files with 256 additions and 44 deletions

View File

@ -135,7 +135,7 @@ public final class Minosoft {
taskWorker.addTask(new Task(progress -> {
Log.debug("Refreshing account token...");
checkClientToken();
selectAccount(config.getSccounts().get(config.getString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED)));
selectAccount(config.getAccounts().get(config.getString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED)));
}, "Token refresh", "Refresh selected account token", Priorities.LOW, TaskImportance.OPTIONAL, "Configuration"));
taskWorker.addTask(new Task(progress -> {

View File

@ -203,7 +203,7 @@ public class Configuration {
return this.serverList;
}
public HashBiMap<String, Account> getSccounts() {
public HashBiMap<String, Account> getAccounts() {
return this.accountList;
}

View File

@ -14,6 +14,7 @@
package de.bixilon.minosoft.data.accounts;
import com.google.gson.JsonObject;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException;
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
@ -65,4 +66,9 @@ public abstract class Account {
Account account = (Account) obj;
return getId().equals(account.getId());
}
public void saveToConfig() {
Minosoft.getConfig().putAccount(this);
Minosoft.getConfig().saveToFile();
}
}

View File

@ -73,6 +73,8 @@ public class MojangAccount extends Account {
@Override
public void logout() {
Minosoft.getConfig().removeAccount(this);
Minosoft.getConfig().saveToFile();
}
@Override
@ -107,15 +109,6 @@ public class MojangAccount extends Account {
return this.email;
}
public void saveToConfig() {
Minosoft.getConfig().putAccount(this);
Minosoft.getConfig().saveToFile();
}
public void delete() {
Minosoft.getConfig().removeAccount(this);
Minosoft.getConfig().saveToFile();
}
@Override
public String toString() {

View File

@ -14,28 +14,18 @@
package de.bixilon.minosoft.data.accounts;
import com.google.gson.JsonObject;
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
import de.bixilon.minosoft.util.Util;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.regex.Matcher;
public class OfflineAccount extends Account {
public OfflineAccount(String username) {
super(username, UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)));
Matcher matcher = ProtocolDefinition.MINECRAFT_NAME_VALIDATOR.matcher(username);
if (!matcher.find() || !matcher.group().equals(username)) {
throw new IllegalArgumentException(String.format("%s is not valid Minecraft username", username));
}
}
public OfflineAccount(String username, UUID uuid) {
super(username, uuid);
Matcher matcher = ProtocolDefinition.MINECRAFT_NAME_VALIDATOR.matcher(username);
if (!matcher.find() || !matcher.group().equals(username)) {
throw new IllegalArgumentException(String.format("%s is not valid Minecraft username", username));
}
}
public static OfflineAccount deserialize(JsonObject json) {
@ -66,6 +56,6 @@ public class OfflineAccount extends Account {
@Override
public String getId() {
return getUsername();
return getUsername() + ":" + getUUID().toString();
}
}

View File

@ -52,7 +52,8 @@ public enum Strings {
SERVER_ACTION_DELETE,
SESSIONS_ACTION_DISCONNECT,
ACCOUNT_MODAL_MENU_ADD_ACCOUNT,
ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT,
ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT,
MAIN_WINDOW_TITLE,
MAIN_WINDOW_MENU_FILE,
MAIN_WINDOW_MENU_FILE_PREFERENCES,
@ -78,8 +79,14 @@ public enum Strings {
MANAGE_ACCOUNTS_NO_ACCOUNT_ERROR_ERROR,
MANAGE_ACCOUNTS_NO_ACCOUNT_ERROR,
LOGIN_DIALOG_TITLE,
LOGIN_DIALOG_HEADER,
LOGIN_MOJANG_DIALOG_TITLE,
LOGIN_MOJANG_DIALOG_HEADER,
LOGIN_OFFLINE_DIALOG_TITLE,
LOGIN_OFFLINE_DIALOG_HEADER,
LOGIN_OFFLINE_USERNAME,
LOGIN_OFFLINE_UUID,
LOGIN_OFFLINE_ADD_BUTTON,
MINOSOFT_STILL_STARTING_TITLE,
MINOSOFT_STILL_STARTING_HEADER,

View File

@ -104,10 +104,10 @@ public class AccountListCell extends ListCell<Account> implements Initializable
Minosoft.getConfig().removeAccount(this.account);
Minosoft.getConfig().saveToFile();
if (Minosoft.getConfig().getSelectedAccount() == this.account) {
if (Minosoft.getConfig().getSccounts().isEmpty()) {
if (Minosoft.getConfig().getAccounts().isEmpty()) {
Minosoft.selectAccount(null);
} else {
Minosoft.selectAccount(Minosoft.getConfig().getSccounts().values().iterator().next());
Minosoft.selectAccount(Minosoft.getConfig().getAccounts().values().iterator().next());
}
MOJANG_ACCOUNT_LIST_VIEW.refresh();
}

View File

@ -19,7 +19,6 @@ import de.bixilon.minosoft.data.locale.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
@ -31,23 +30,33 @@ import java.util.ResourceBundle;
public class AccountWindow implements Initializable {
public BorderPane accountPane;
public MenuItem menuAddAccount;
public MenuItem menuAddMojangAccount;
public MenuItem menuAddOfflineAccount;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.setCellFactory((lv) -> AccountListCell.newInstance());
ObservableList<Account> accounts = FXCollections.observableArrayList(Minosoft.getConfig().getSccounts().values());
ObservableList<Account> accounts = FXCollections.observableArrayList(Minosoft.getConfig().getAccounts().values());
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.setItems(accounts);
this.accountPane.setCenter(AccountListCell.MOJANG_ACCOUNT_LIST_VIEW);
this.menuAddAccount.setText(LocaleManager.translate(Strings.ACCOUNT_MODAL_MENU_ADD_ACCOUNT));
this.menuAddMojangAccount.setText(LocaleManager.translate(Strings.ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT));
this.menuAddOfflineAccount.setText(LocaleManager.translate(Strings.ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT));
}
@FXML
public void addAccount() {
public void addMojangAccount() {
try {
GUITools.showPane("/layout/dialogs/login_mojang.fxml", Modality.APPLICATION_MODAL, LocaleManager.translate(Strings.LOGIN_DIALOG_TITLE));
GUITools.showPane("/layout/dialogs/login_mojang.fxml", Modality.APPLICATION_MODAL, LocaleManager.translate(Strings.LOGIN_MOJANG_DIALOG_TITLE));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void addOfflineAccount() {
try {
GUITools.showPane("/layout/dialogs/login_offline.fxml", Modality.APPLICATION_MODAL, LocaleManager.translate(Strings.LOGIN_OFFLINE_DIALOG_TITLE));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);

View File

@ -50,7 +50,7 @@ public class MojangLoginController implements Initializable {
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// translate
this.header.setText(LocaleManager.translate(Strings.LOGIN_DIALOG_HEADER));
this.header.setText(LocaleManager.translate(Strings.LOGIN_MOJANG_DIALOG_HEADER));
this.emailLabel.setText(LocaleManager.translate(Strings.EMAIL));
this.passwordLabel.setText(LocaleManager.translate(Strings.PASSWORD));
this.loginButton.setText(LocaleManager.translate(Strings.BUTTON_LOGIN));
@ -89,7 +89,7 @@ public class MojangLoginController implements Initializable {
account.setNeedRefresh(false);
Minosoft.getConfig().putAccount(account);
account.saveToConfig();
Log.info(String.format("Added and saved account (username=%s, email=%s, uuid=%s)", account.getUsername(), account.getEmail(), account.getUUID()));
Log.info(String.format("Added and saved account (type=mojang, username=%s, email=%s, uuid=%s)", account.getUsername(), account.getEmail(), account.getUUID()));
Platform.runLater(() -> {
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().add(account);
close();

View File

@ -0,0 +1,122 @@
/*
* Minosoft
* Copyright (C) 2020 Moritz Zwerger
*
* 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 <https://www.gnu.org/licenses/>.
*
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.gui.main.dialogs;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.data.accounts.OfflineAccount;
import de.bixilon.minosoft.data.locale.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings;
import de.bixilon.minosoft.gui.main.AccountListCell;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
import de.bixilon.minosoft.util.Util;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.regex.Matcher;
public class OfflineLoginController implements Initializable {
public HBox hBox;
public Label header;
public Label usernameLabel;
public JFXTextField username;
public Label uuidLabel;
public JFXTextField uuid;
public JFXButton addButton;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// translate
this.header.setText(LocaleManager.translate(Strings.LOGIN_OFFLINE_DIALOG_HEADER));
this.usernameLabel.setText(LocaleManager.translate(Strings.LOGIN_OFFLINE_USERNAME));
this.uuidLabel.setText(LocaleManager.translate(Strings.LOGIN_OFFLINE_UUID));
this.addButton.setText(LocaleManager.translate(Strings.LOGIN_OFFLINE_ADD_BUTTON));
this.username.textProperty().addListener(this::checkData);
this.uuid.textProperty().addListener(this::checkData);
this.hBox.setOnKeyReleased(keyEvent -> {
if (keyEvent.getCode() == KeyCode.ENTER) {
if (this.addButton.isDisable()) {
return;
}
this.addButton.fire();
return;
}
if (keyEvent.getCode() == KeyCode.ESCAPE) {
close();
}
});
}
public void add(ActionEvent event) {
event.consume();
OfflineAccount account;
if (this.uuid.getText().isBlank()) {
account = new OfflineAccount(this.username.getText());
} else {
account = new OfflineAccount(this.username.getText(), Util.getUUIDFromString(this.uuid.getText()));
}
Minosoft.getConfig().putAccount(account);
account.saveToConfig();
Log.info(String.format("Added and saved account (type=offline, username=%s, uuid=%s)", account.getUsername(), account.getUUID()));
Platform.runLater(() -> {
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().add(account);
close();
});
if (Minosoft.getConfig().getSelectedAccount() == null) {
// select account
Minosoft.selectAccount(account);
}
}
private void checkData(ObservableValue<? extends String> observableValue, String oldValue, String newValue) {
Matcher matcher = ProtocolDefinition.MINECRAFT_NAME_VALIDATOR.matcher(this.username.getText());
if (!matcher.find() || !matcher.group().equals(this.username.getText())) {
this.addButton.setDisable(true);
return;
}
if (!this.uuid.getText().isBlank()) {
try {
Util.getUUIDFromString(this.uuid.getText());
} catch (IllegalArgumentException e) {
this.addButton.setDisable(true);
return;
}
}
this.addButton.setDisable(false);
}
public void close() {
getStage().close();
}
public Stage getStage() {
return ((Stage) this.hBox.getScene().getWindow());
}
}

View File

@ -42,6 +42,7 @@ public final class Util {
private static final Random THREAD_LOCAL_RANDOM = ThreadLocalRandom.current();
public static UUID getUUIDFromString(String uuid) {
uuid = uuid.trim();
if (uuid.length() == 36) {
return UUID.fromString(uuid);
}

View File

@ -35,7 +35,8 @@
"SERVER_ACTION_SESSIONS": "Verbindungen",
"SERVER_ACTION_DELETE": "Löschen",
"SESSIONS_ACTION_DISCONNECT": "Trennen",
"ACCOUNT_MODAL_MENU_ADD_ACCOUNT": "Hinzufügen",
"ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT": "Anmelden (Mojang)",
"ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT": "Hinzufügen (Offline)",
"MAIN_WINDOW_TITLE": "Minosoft",
"MAIN_WINDOW_MENU_FILE": "_Datei",
"MAIN_WINDOW_MENU_FILE_PREFERENCES": "Einstellungen",
@ -54,8 +55,13 @@
"SETTINGS_GENERAL": "Allgemein",
"SETTINGS_GENERAL_LOG_LEVEL": "Log Level",
"SETTINGS_DOWNLOAD": "Download",
"LOGIN_DIALOG_TITLE": "Anmelden (Mojang) - Minosoft",
"LOGIN_DIALOG_HEADER": "Bitte gib deine Mojang Zugangsdaten ein dich anzumelden",
"LOGIN_MOJANG_DIALOG_TITLE": "Anmelden (Mojang) - Minosoft",
"LOGIN_MOJANG_DIALOG_HEADER": "Bitte gib deine Mojang Zugangsdaten ein dich anzumelden",
"LOGIN_OFFLINE_DIALOG_TITLE": "Anmelden (Offline) - Minosoft",
"LOGIN_OFFLINE_DIALOG_HEADER": "Bitte gib einen Benutzernamen ein (und eine UUID)",
"LOGIN_OFFLINE_USERNAME": "Benutzername",
"LOGIN_OFFLINE_UUID": "(UUID)",
"LOGIN_OFFLINE_ADD_BUTTON": "Hinzufügen",
"ERROR": "Fehler",
"MINOSOFT_STILL_STARTING_TITLE": "Bitte warten",
"MINOSOFT_STILL_STARTING_HEADER": "Minosoft muss noch ein paar Dinge erledigen, bevor du es verwenden kannst.\nBitte warte noch ein paar Sekunden...",

View File

@ -36,7 +36,8 @@
"SERVER_ACTION_SESSIONS": "Sessions",
"SERVER_ACTION_DELETE": "Delete",
"SESSIONS_ACTION_DISCONNECT": "Disconnect",
"ACCOUNT_MODAL_MENU_ADD_ACCOUNT": "Add",
"ACCOUNT_MODAL_MENU_ADD_MOJANG_ACCOUNT": "Login (Mojang)",
"ACCOUNT_MODAL_MENU_ADD_OFFLINE_ACCOUNT": "Add (Offline)",
"MAIN_WINDOW_TITLE": "Minosoft",
"MAIN_WINDOW_MENU_FILE": "_File",
"MAIN_WINDOW_MENU_FILE_PREFERENCES": "Preferences",
@ -55,8 +56,13 @@
"SETTINGS_GENERAL": "General",
"SETTINGS_GENERAL_LOG_LEVEL": "Log level",
"SETTINGS_DOWNLOAD": "Download",
"LOGIN_DIALOG_TITLE": "Login (Mojang) - Minosoft",
"LOGIN_DIALOG_HEADER": "Please login to your mojang account",
"LOGIN_MOJANG_DIALOG_TITLE": "Login (Mojang) - Minosoft",
"LOGIN_MOJANG_DIALOG_HEADER": "Please login to your mojang account",
"LOGIN_OFFLINE_DIALOG_TITLE": "Login (Offline) - Minosoft",
"LOGIN_OFFLINE_DIALOG_HEADER": "Please enter a username (and a UUID)",
"LOGIN_OFFLINE_USERNAME": "Username",
"LOGIN_OFFLINE_UUID": "(UUID)",
"LOGIN_OFFLINE_ADD_BUTTON": "Add",
"ERROR": "Error",
"MINOSOFT_STILL_STARTING_TITLE": "Please wait",
"MINOSOFT_STILL_STARTING_HEADER": "Minosoft is still starting up...",

View File

@ -11,11 +11,13 @@
~
~ This software is not affiliated with Mojang AB, the original developer of Minecraft.
-->
<?import javafx.scene.control.Menu?><?import javafx.scene.control.MenuBar?><?import javafx.scene.control.MenuItem?><?import javafx.scene.control.ScrollPane?><?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="640.0" fx:controller="de.bixilon.minosoft.gui.main.AccountWindow">
<MenuBar VBox.vgrow="NEVER">
<Menu mnemonicParsing="false" text="_Accounts">
<MenuItem fx:id="menuAddAccount" mnemonicParsing="false" onAction="#addAccount" text="-Add-" />
<MenuItem fx:id="menuAddMojangAccount" mnemonicParsing="false" onAction="#addMojangAccount" text="-Add Mojang account-"/>
<MenuItem fx:id="menuAddOfflineAccount" mnemonicParsing="false" onAction="#addOfflineAccount" text="-Add Offline account-"/>
</Menu>
</MenuBar>
<AnchorPane VBox.vgrow="ALWAYS">

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXTextField?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:id="hBox" minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="500.0" xmlns="http://javafx.com/javafx/11.0.1" fx:controller="de.bixilon.minosoft.gui.main.dialogs.OfflineLoginController">
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</padding>
<GridPane HBox.hgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="NEVER"/>
<RowConstraints vgrow="ALWAYS"/>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS"/>
<RowConstraints vgrow="ALWAYS"/>
</rowConstraints>
<Label fx:id="header" text="Please enter a username (and UUID)" textAlignment="CENTER" GridPane.halignment="CENTER">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
<font>
<Font size="23.0"/>
</font>
</Label>
<GridPane GridPane.rowIndex="1">
<columnConstraints>
<ColumnConstraints hgrow="NEVER"/>
<ColumnConstraints hgrow="ALWAYS"/>
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="ALWAYS"/>
<RowConstraints vgrow="ALWAYS"/>
</rowConstraints>
<Label fx:id="usernameLabel" text="USERNAME">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
</Label>
<JFXTextField fx:id="username" GridPane.columnIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
</JFXTextField>
<Label fx:id="uuidLabel" text="UUID" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
</Label>
<JFXTextField fx:id="uuid" minWidth="-Infinity" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
</JFXTextField>
</GridPane>
<JFXButton fx:id="addButton" disable="true" maxHeight="Infinity" maxWidth="Infinity" mnemonicParsing="false" text="ADD" GridPane.rowIndex="3" onAction="#add">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0"/>
</GridPane.margin>
<font>
<Font name="System Bold" size="31.0"/>
</font>
</JFXButton>
</GridPane>
</HBox>