Improvements, WIP microsoft accounts (1): multiple login endpoints

This commit is contained in:
Bixilon 2020-12-22 23:01:37 +01:00
parent 569b9f4ba4
commit 9adb18681a
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
28 changed files with 490 additions and 368 deletions

View File

@ -18,6 +18,7 @@ import com.jfoenix.controls.JFXDialogLayout;
import de.bixilon.minosoft.config.Configuration;
import de.bixilon.minosoft.config.ConfigurationPaths;
import de.bixilon.minosoft.config.StaticConfiguration;
import de.bixilon.minosoft.data.accounts.Account;
import de.bixilon.minosoft.data.assets.AssetsManager;
import de.bixilon.minosoft.data.locale.LocaleManager;
import de.bixilon.minosoft.data.locale.minecraft.MinecraftLocaleManager;
@ -35,7 +36,6 @@ import de.bixilon.minosoft.protocol.protocol.LANServerListener;
import de.bixilon.minosoft.util.CountUpAndDownLatch;
import de.bixilon.minosoft.util.MinosoftCommandLineArguments;
import de.bixilon.minosoft.util.Util;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import de.bixilon.minosoft.util.task.AsyncTaskWorker;
import de.bixilon.minosoft.util.task.Task;
import de.bixilon.minosoft.util.task.TaskImportance;
@ -51,7 +51,6 @@ import java.util.UUID;
public final class Minosoft {
public static final HashSet<EventManager> EVENT_MANAGERS = new HashSet<>();
private static final CountUpAndDownLatch START_STATUS_LATCH = new CountUpAndDownLatch(1);
public static MojangAccount selectedAccount;
public static Configuration config;
public static void main(String[] args) {
@ -136,7 +135,7 @@ public final class Minosoft {
taskWorker.addTask(new Task(progress -> {
Log.debug("Refreshing account token...");
checkClientToken();
selectAccount(config.getAccountList().get(config.getString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED)));
selectAccount(config.getSccounts().get(config.getString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED)));
}, "Token refresh", "Refresh selected account token", Priorities.LOW, TaskImportance.OPTIONAL, "Configuration"));
taskWorker.addTask(new Task(progress -> {
@ -191,37 +190,31 @@ public final class Minosoft {
}
}
public static void selectAccount(MojangAccount account) {
public static void selectAccount(Account account) {
if (account == null) {
selectedAccount = null;
config.putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, "");
config.saveToFile();
return;
}
if (account.needsRefresh()) {
MojangAccount.RefreshStates refreshState = account.refreshToken();
if (refreshState == MojangAccount.RefreshStates.ERROR) {
account.delete();
if (!account.select()) {
account.logout();
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().remove(account);
selectedAccount = null;
config.removeAccount(account);
config.saveToFile();
return;
}
}
config.putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, account.getUserId());
config.putAccount(account);
config.selectAccount(account);
if (Launcher.getMainWindow() != null) {
Launcher.getMainWindow().selectAccount(selectedAccount);
Launcher.getMainWindow().selectAccount(account);
}
account.saveToConfig();
selectedAccount = account;
config.saveToFile();
}
public static Configuration getConfig() {
return config;
}
public static MojangAccount getSelectedAccount() {
return selectedAccount;
}
/**
* Waits until all critical components are started

View File

@ -15,10 +15,12 @@ package de.bixilon.minosoft.config;
import com.google.common.collect.HashBiMap;
import com.google.gson.*;
import de.bixilon.minosoft.data.accounts.Account;
import de.bixilon.minosoft.data.accounts.MojangAccount;
import de.bixilon.minosoft.data.accounts.OfflineAccount;
import de.bixilon.minosoft.gui.main.Server;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.util.Util;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import java.io.*;
import java.nio.file.Files;
@ -30,7 +32,7 @@ public class Configuration {
public static final int LATEST_CONFIG_VERSION = 1;
private static final JsonObject DEFAULT_CONFIGURATION = JsonParser.parseReader(new InputStreamReader(Configuration.class.getResourceAsStream("/config/" + StaticConfiguration.CONFIG_FILENAME))).getAsJsonObject();
private final HashMap<ConfigurationPaths.ConfigurationPath, Object> config = new HashMap<>();
private final HashBiMap<String, MojangAccount> accountList = HashBiMap.create();
private final HashBiMap<String, Account> accountList = HashBiMap.create();
private final HashBiMap<Integer, Server> serverList = HashBiMap.create();
private final Object lock = new Object();
@ -71,8 +73,13 @@ public class Configuration {
// accounts
for (Map.Entry<String, JsonElement> entry : json.getAsJsonObject("accounts").getAsJsonObject("entries").entrySet()) {
MojangAccount account = MojangAccount.deserialize(entry.getValue().getAsJsonObject());
this.accountList.put(account.getUserId(), account);
JsonObject data = entry.getValue().getAsJsonObject();
Account account = switch (data.get("type").getAsString()) {
case "mojang" -> MojangAccount.deserialize(data);
case "offline" -> OfflineAccount.deserialize(data);
default -> throw new IllegalStateException("Unexpected value: " + data.get("type").getAsString());
};
this.accountList.put(account.getId(), account);
}
final File finalFile = file;
@ -106,7 +113,7 @@ public class Configuration {
// servers
JsonObject serversEntriesJson = jsonObject.getAsJsonObject("accounts").getAsJsonObject("entries");
for (Map.Entry<String, MojangAccount> entry : this.accountList.entrySet()) {
for (Map.Entry<String, Account> entry : this.accountList.entrySet()) {
serversEntriesJson.add(entry.getKey(), entry.getValue().serialize());
}
@ -159,8 +166,8 @@ public class Configuration {
this.config.put(path, value);
}
public void putMojangAccount(MojangAccount account) {
this.accountList.put(account.getUserId(), account);
public void putAccount(Account account) {
this.accountList.put(account.getId(), account);
}
public void putServer(Server server) {
@ -177,15 +184,26 @@ public class Configuration {
}
}
public void removeAccount(MojangAccount account) {
this.accountList.remove(account.getUserId());
public Account getSelectedAccount() {
return this.accountList.get(getString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED));
}
public void selectAccount(Account account) {
putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, account.getId());
}
public void removeAccount(Account account) {
this.accountList.remove(account.getId());
if (getString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED).equals(account.getId())) {
putString(ConfigurationPaths.StringPaths.ACCOUNT_SELECTED, "");
}
}
public HashBiMap<Integer, Server> getServerList() {
return this.serverList;
}
public HashBiMap<String, MojangAccount> getAccountList() {
public HashBiMap<String, Account> getSccounts() {
return this.accountList;
}

View File

@ -13,6 +13,7 @@
package de.bixilon.minosoft.data;
import de.bixilon.minosoft.data.accounts.Account;
import de.bixilon.minosoft.data.entities.entities.player.PlayerEntity;
import de.bixilon.minosoft.data.inventory.Inventory;
import de.bixilon.minosoft.data.inventory.InventoryProperties;
@ -23,7 +24,6 @@ import de.bixilon.minosoft.data.scoreboard.ScoreboardManager;
import de.bixilon.minosoft.data.text.ChatComponent;
import de.bixilon.minosoft.data.world.BlockPosition;
import de.bixilon.minosoft.data.world.World;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import java.util.HashMap;
import java.util.UUID;
@ -32,7 +32,7 @@ import static de.bixilon.minosoft.protocol.protocol.ProtocolDefinition.PLAYER_IN
public class Player {
public final HashMap<UUID, PlayerListItem> playerList = new HashMap<>();
final MojangAccount account;
final Account account;
final ScoreboardManager scoreboardManager = new ScoreboardManager();
final World world = new World();
final HashMap<Integer, Inventory> inventories = new HashMap<>();
@ -50,21 +50,21 @@ public class Player {
ChatComponent tabHeader;
ChatComponent tabFooter;
public Player(MojangAccount account) {
public Player(Account account) {
this.account = account;
// create our own inventory without any properties
this.inventories.put(PLAYER_INVENTORY_ID, new Inventory(null));
}
public String getPlayerName() {
return this.account.getPlayerName();
return this.account.getUsername();
}
public UUID getPlayerUUID() {
return this.account.getUUID();
}
public MojangAccount getAccount() {
public Account getAccount() {
return this.account;
}

View File

@ -0,0 +1,68 @@
/*
* 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.data.accounts;
import com.google.gson.JsonObject;
import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException;
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
import java.util.UUID;
public abstract class Account {
private final String username;
private final UUID uuid;
protected Account(String username, UUID uuid) {
this.username = username;
this.uuid = uuid;
}
public String getUsername() {
return this.username;
}
public UUID getUUID() {
return this.uuid;
}
public abstract JsonObject serialize();
public abstract void join(String serverId) throws MojangJoinServerErrorException, NoNetworkConnectionException;
public abstract boolean select();
public abstract void logout();
public abstract String getId();
@Override
public int hashCode() {
return getId().hashCode();
}
@Override
public boolean equals(Object obj) {
if (super.equals(obj)) {
return true;
}
if (obj == null) {
return false;
}
if (hashCode() != obj.hashCode()) {
return false;
}
Account account = (Account) obj;
return getId().equals(account.getId());
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.data.accounts;
import com.google.gson.JsonObject;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.util.Util;
import de.bixilon.minosoft.util.mojang.api.MojangAuthentication;
import de.bixilon.minosoft.util.mojang.api.exceptions.AuthenticationException;
import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException;
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
import java.util.UUID;
public class MojangAccount extends Account {
private final String id;
private final String email;
private String accessToken;
private RefreshStates lastRefreshStatus;
private boolean needsRefresh = true;
public MojangAccount(String username, JsonObject json) {
super(json.getAsJsonObject("selectedProfile").get("name").getAsString(), Util.getUUIDFromString(json.getAsJsonObject("selectedProfile").get("id").getAsString()));
this.accessToken = json.get("accessToken").getAsString();
this.id = json.getAsJsonObject("user").get("id").getAsString();
this.email = username;
}
public MojangAccount(String accessToken, String id, UUID uuid, String username, String email) {
super(username, uuid);
this.accessToken = accessToken;
this.id = id;
this.email = email;
}
public static MojangAccount deserialize(JsonObject json) {
return new MojangAccount(json.get("accessToken").getAsString(), json.get("id").getAsString(), Util.getUUIDFromString(json.get("uuid").getAsString()), json.get("username").getAsString(), json.get("email").getAsString());
}
public JsonObject serialize() {
JsonObject json = new JsonObject();
json.addProperty("id", this.id);
json.addProperty("accessToken", this.accessToken);
json.addProperty("uuid", getUUID().toString());
json.addProperty("username", getUsername());
json.addProperty("email", this.email);
json.addProperty("type", "mojang");
return json;
}
public void join(String serverId) throws MojangJoinServerErrorException, NoNetworkConnectionException {
MojangAuthentication.joinServer(this, serverId);
}
@Override
public boolean select() {
if (this.needsRefresh) {
return refreshToken() != RefreshStates.ERROR;
}
return true;
}
@Override
public void logout() {
}
@Override
public String getId() {
return this.id;
}
public RefreshStates refreshToken() {
try {
this.accessToken = MojangAuthentication.refresh(this.accessToken);
this.lastRefreshStatus = RefreshStates.SUCCESSFUL;
} catch (NoNetworkConnectionException e) {
e.printStackTrace();
this.lastRefreshStatus = RefreshStates.FAILED;
} catch (AuthenticationException e) {
e.printStackTrace();
this.lastRefreshStatus = RefreshStates.ERROR;
}
return this.lastRefreshStatus;
}
public String getAccessToken() {
return this.accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
this.needsRefresh = false;
}
public String getEmail() {
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() {
return getId();
}
public boolean needsRefresh() {
return this.needsRefresh;
}
public void setNeedRefresh(boolean needsRefresh) {
this.needsRefresh = needsRefresh;
}
public enum RefreshStates {
SUCCESSFUL,
ERROR, // account not valid anymore
FAILED // error occurred while checking -> Unknown state
}
}

View File

@ -0,0 +1,71 @@
/*
* 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.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) {
return new OfflineAccount(json.get("username").getAsString(), Util.getUUIDFromString(json.get("uuid").getAsString()));
}
@Override
public JsonObject serialize() {
JsonObject json = new JsonObject();
json.addProperty("username", getUsername());
json.addProperty("uuid", getUUID().toString());
json.addProperty("type", "offline");
return json;
}
@Override
public void join(String serverId) {
}
@Override
public boolean select() {
return true;
}
@Override
public void logout() {
}
@Override
public String getId() {
return getUsername();
}
}

View File

@ -70,7 +70,7 @@ public class AssetsManager {
return ret;
}
public static void downloadAllAssets(CountUpAndDownLatch latch) throws IOException {
public static void downloadAllAssets(CountUpAndDownLatch latch) throws IOException, InterruptedException {
if (!ASSETS_MAP.isEmpty()) {
return;
}
@ -184,7 +184,7 @@ public class AssetsManager {
return false;
}
public static void generateJarAssets() throws IOException {
public static void generateJarAssets() throws IOException, InterruptedException {
long startTime = System.currentTimeMillis();
Log.verbose("Generating client.jar assets...");
if (verifyAssetHash(ASSETS_CLIENT_JAR_HASH)) {

View File

@ -80,7 +80,6 @@ public enum Strings {
LOGIN_DIALOG_TITLE,
LOGIN_DIALOG_HEADER,
LOGIN_ACCOUNT_ALREADY_PRESENT,
MINOSOFT_STILL_STARTING_TITLE,
MINOSOFT_STILL_STARTING_HEADER,

View File

@ -14,10 +14,10 @@
package de.bixilon.minosoft.gui.main;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.data.accounts.Account;
import de.bixilon.minosoft.data.locale.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
@ -28,8 +28,8 @@ import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
public class AccountListCell extends ListCell<MojangAccount> implements Initializable {
public static final ListView<MojangAccount> MOJANG_ACCOUNT_LIST_VIEW = new ListView<>();
public class AccountListCell extends ListCell<Account> implements Initializable {
public static final ListView<Account> MOJANG_ACCOUNT_LIST_VIEW = new ListView<>();
public MenuButton optionsMenu;
public Label playerName;
@ -38,7 +38,7 @@ public class AccountListCell extends ListCell<MojangAccount> implements Initiali
public MenuItem optionsDelete;
public AnchorPane root;
private MojangAccount account;
private Account account;
public static AccountListCell newInstance() {
FXMLLoader loader = new FXMLLoader(AccountListCell.class.getResource("/layout/cells/account.fxml"));
@ -67,7 +67,7 @@ public class AccountListCell extends ListCell<MojangAccount> implements Initiali
}
@Override
protected void updateItem(MojangAccount account, boolean empty) {
protected void updateItem(Account account, boolean empty) {
super.updateItem(account, empty);
this.root.setVisible(!empty);
@ -85,9 +85,9 @@ public class AccountListCell extends ListCell<MojangAccount> implements Initiali
resetCell();
this.account = account;
this.playerName.setText(account.getPlayerName());
this.email.setText(account.getMojangUserName());
if (Minosoft.getSelectedAccount() == account) {
this.playerName.setText(account.getUsername());
// this.email.setText(account.getEmail());
if (Minosoft.getConfig().getSelectedAccount() == account) {
setStyle("-fx-background-color: darkseagreen;");
this.optionsSelect.setDisable(true);
}
@ -99,17 +99,19 @@ public class AccountListCell extends ListCell<MojangAccount> implements Initiali
this.optionsSelect.setDisable(false);
}
public void delete() {
this.account.delete();
if (Minosoft.getSelectedAccount() == this.account) {
if (Minosoft.getConfig().getAccountList().isEmpty()) {
public void logout() {
this.account.logout();
Minosoft.getConfig().removeAccount(this.account);
Minosoft.getConfig().saveToFile();
if (Minosoft.getConfig().getSelectedAccount() == this.account) {
if (Minosoft.getConfig().getSccounts().isEmpty()) {
Minosoft.selectAccount(null);
} else {
Minosoft.selectAccount(Minosoft.getConfig().getAccountList().values().iterator().next());
Minosoft.selectAccount(Minosoft.getConfig().getSccounts().values().iterator().next());
}
MOJANG_ACCOUNT_LIST_VIEW.refresh();
}
Log.info(String.format("Deleted account (email=\"%s\", playerName=\"%s\")", this.account.getMojangUserName(), this.account.getPlayerName()));
Log.info(String.format("Deleted account (id=%s, username=%s)", this.account.getId(), this.account.getUsername()));
MOJANG_ACCOUNT_LIST_VIEW.getItems().remove(this.account);
}

View File

@ -14,9 +14,9 @@
package de.bixilon.minosoft.gui.main;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.data.accounts.Account;
import de.bixilon.minosoft.data.locale.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
@ -30,7 +30,6 @@ import java.net.URL;
import java.util.ResourceBundle;
public class AccountWindow implements Initializable {
public BorderPane accountPane;
public MenuItem menuAddAccount;
@ -38,7 +37,7 @@ public class AccountWindow implements Initializable {
public void initialize(URL url, ResourceBundle resourceBundle) {
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.setCellFactory((lv) -> AccountListCell.newInstance());
ObservableList<MojangAccount> accounts = FXCollections.observableArrayList(Minosoft.getConfig().getAccountList().values());
ObservableList<Account> accounts = FXCollections.observableArrayList(Minosoft.getConfig().getSccounts().values());
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.setItems(accounts);
this.accountPane.setCenter(AccountListCell.MOJANG_ACCOUNT_LIST_VIEW);

View File

@ -92,7 +92,7 @@ public class Launcher {
stage.show();
if (Minosoft.getSelectedAccount() == null) {
if (Minosoft.getConfig().getSelectedAccount() == null) {
MainWindow.manageAccounts();
}
latch.countDown();

View File

@ -19,6 +19,7 @@ import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.RequiredFieldValidator;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.data.accounts.Account;
import de.bixilon.minosoft.data.locale.LocaleManager;
import de.bixilon.minosoft.data.locale.Strings;
import de.bixilon.minosoft.data.mappings.versions.Versions;
@ -26,7 +27,6 @@ import de.bixilon.minosoft.data.text.BaseComponent;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.protocol.protocol.LANServerListener;
import de.bixilon.minosoft.util.DNSUtil;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@ -71,7 +71,7 @@ public class MainWindow implements Initializable {
GUITools.initializeScene(stage.getScene());
Platform.setImplicitExit(false);
stage.setOnCloseRequest(event -> {
if (Minosoft.getSelectedAccount() == null) {
if (Minosoft.getConfig().getSelectedAccount() == null) {
event.consume();
JFXAlert<?> alert = new JFXAlert<>();
GUITools.initializePane(alert.getDialogPane());
@ -212,10 +212,22 @@ public class MainWindow implements Initializable {
dialog.showAndWait();
}
public void selectAccount(MojangAccount account) {
public void selectAccount(Account account) {
Runnable runnable = () -> {
if (account != null) {
MainWindow.this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS_SELECTED, account.getUsername()));
} else {
MainWindow.this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS));
}
};
if (Platform.isFxApplicationThread()) {
runnable.run();
} else {
Platform.runLater(runnable);
}
Platform.runLater(() -> {
if (account != null) {
this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS_SELECTED, account.getPlayerName()));
this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS_SELECTED, account.getUsername()));
} else {
this.menuAccount.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS));
}
@ -235,7 +247,7 @@ public class MainWindow implements Initializable {
this.menuHelp.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_HELP));
this.menuHelpAbout.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_HELP_ABOUT));
this.menuAccountManage.setText(LocaleManager.translate(Strings.MAIN_WINDOW_MENU_SERVERS_ACCOUNTS_MANAGE));
selectAccount(Minosoft.getSelectedAccount());
selectAccount(Minosoft.getConfig().getSelectedAccount());
}
@FXML

View File

@ -281,7 +281,7 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
if (!this.canConnect || this.server.getLastPing() == null) {
return;
}
Connection connection = new Connection(Connection.lastConnectionId++, this.server.getAddress(), new Player(Minosoft.getSelectedAccount()));
Connection connection = new Connection(Connection.lastConnectionId++, this.server.getAddress(), new Player(Minosoft.getConfig().getSelectedAccount()));
Version version;
if (this.server.getDesiredVersionId() == ProtocolDefinition.QUERY_PROTOCOL_VERSION_ID) {
version = this.server.getLastPing().getVersion();
@ -317,7 +317,7 @@ public class ServerListCell extends ListCell<Server> implements Initializable {
}
}
this.optionsConnect.setDisable(Minosoft.getSelectedAccount() == connection.getPlayer().getAccount());
this.optionsConnect.setDisable(Minosoft.getConfig().getSelectedAccount() == connection.getPlayer().getAccount());
getStyleClass().add("list-cell-connected");
this.optionsSessions.setDisable(false);
});

View File

@ -85,7 +85,7 @@ public class SessionListCell extends ListCell<Connection> implements Initializab
this.connection = connection;
connection.registerEvent(new EventInvokerCallback<>(ConnectionStateChangeEvent.class, this::handleConnectionCallback));
this.connectionId.setText(String.format("#%d", connection.getConnectionId()));
this.account.setText(connection.getPlayer().getAccount().getPlayerName());
this.account.setText(connection.getPlayer().getAccount().getUsername());
}
private void handleConnectionCallback(ConnectionStateChangeEvent event) {

View File

@ -17,13 +17,14 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXTextField;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.data.accounts.MojangAccount;
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.util.mojang.api.MojangAccount;
import de.bixilon.minosoft.util.mojang.api.MojangAccountAuthenticationAttempt;
import de.bixilon.minosoft.util.mojang.api.MojangAuthentication;
import de.bixilon.minosoft.util.mojang.api.exceptions.AuthenticationException;
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
@ -49,7 +50,6 @@ public class MojangLoginController implements Initializable {
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// translate
this.header.setText(LocaleManager.translate(Strings.LOGIN_DIALOG_HEADER));
this.emailLabel.setText(LocaleManager.translate(Strings.EMAIL));
this.passwordLabel.setText(LocaleManager.translate(Strings.PASSWORD));
@ -83,45 +83,31 @@ public class MojangLoginController implements Initializable {
new Thread(() -> { // ToDo: recycle thread
MojangAccountAuthenticationAttempt attempt = MojangAuthentication.login(this.email.getText(), this.password.getText());
if (attempt.succeeded()) {
// login okay
final MojangAccount account = attempt.getAccount();
if (Minosoft.getConfig().getAccountList().containsValue(account)) {
// account already present
// replace access token
MojangAccount existingAccount = Minosoft.getConfig().getAccountList().get(account.getUserId());
existingAccount.setAccessToken(attempt.getAccount().getAccessToken());
existingAccount.saveToConfig();
Platform.runLater(() -> {
this.errorMessage.setText(LocaleManager.translate(Strings.LOGIN_ACCOUNT_ALREADY_PRESENT));
this.errorMessage.setVisible(true);
this.email.setDisable(false);
this.password.setDisable(false);
this.loginButton.setDisable(true);
});
return;
}
Minosoft.getConfig().putMojangAccount(account);
try {
MojangAccount account = MojangAuthentication.login(this.email.getText(), this.password.getText());
account.setNeedRefresh(false);
Minosoft.getConfig().putAccount(account);
account.saveToConfig();
Log.info(String.format("Added and saved account (playerName=%s, email=%s, uuid=%s)", account.getPlayerName(), account.getMojangUserName(), account.getUUID()));
Log.info(String.format("Added and saved account (username=%s, email=%s, uuid=%s)", account.getUsername(), account.getEmail(), account.getUUID()));
Platform.runLater(() -> {
AccountListCell.MOJANG_ACCOUNT_LIST_VIEW.getItems().add(account);
close();
});
if (Minosoft.getSelectedAccount() == null) {
if (Minosoft.getConfig().getSelectedAccount() == null) {
// select account
Minosoft.selectAccount(account);
}
return;
}
} catch (AuthenticationException | NoNetworkConnectionException e) {
e.printStackTrace();
Platform.runLater(() -> {
this.errorMessage.setText(attempt.getError());
this.errorMessage.setText(e.getMessage());
this.errorMessage.setVisible(true);
this.email.setDisable(false);
this.password.setDisable(false);
this.loginButton.setDisable(true);
});
}
}, "AccountLoginThread").start();
}

View File

@ -17,7 +17,7 @@ import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.protocol.exceptions.PacketNotImplementedException;
import de.bixilon.minosoft.protocol.exceptions.PacketParseException;
import de.bixilon.minosoft.protocol.exceptions.UnknownPacketException;
import de.bixilon.minosoft.protocol.network.socket.NonBlockingSocketNetwork;
import de.bixilon.minosoft.protocol.network.socket.BlockingSocketNetwork;
import de.bixilon.minosoft.protocol.packets.ClientboundPacket;
import de.bixilon.minosoft.protocol.packets.ServerboundPacket;
import de.bixilon.minosoft.protocol.packets.clientbound.interfaces.CompressionThresholdChange;
@ -36,7 +36,7 @@ public abstract class Network {
}
public static Network getNetworkInstance(Connection connection) {
return new NonBlockingSocketNetwork(connection);
return new BlockingSocketNetwork(connection);
}
public abstract void connect(ServerAddress address);

View File

@ -40,7 +40,6 @@ import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.LinkedBlockingQueue;
@Deprecated
public class BlockingSocketNetwork extends Network {
private final LinkedBlockingQueue<ServerboundPacket> queue = new LinkedBlockingQueue<>();
private Thread socketReceiveThread;

View File

@ -19,6 +19,8 @@ import de.bixilon.minosoft.protocol.packets.ClientboundPacket;
import de.bixilon.minosoft.protocol.packets.serverbound.login.PacketEncryptionResponse;
import de.bixilon.minosoft.protocol.protocol.CryptManager;
import de.bixilon.minosoft.protocol.protocol.InByteBuffer;
import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException;
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
import javax.crypto.SecretKey;
import java.math.BigInteger;
@ -43,7 +45,13 @@ public class PacketEncryptionRequest extends ClientboundPacket {
SecretKey secretKey = CryptManager.createNewSharedKey();
PublicKey publicKey = CryptManager.decodePublicKey(getPublicKey());
String serverHash = new BigInteger(CryptManager.getServerHash(getServerId(), publicKey, secretKey)).toString(16);
try {
connection.getPlayer().getAccount().join(serverHash);
} catch (MojangJoinServerErrorException | NoNetworkConnectionException e) {
e.printStackTrace();
connection.disconnect();
return;
}
connection.sendPacket(new PacketEncryptionResponse(secretKey, getVerifyToken(), publicKey));
}

View File

@ -16,8 +16,6 @@ package de.bixilon.minosoft.util;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.logging.LogLevels;
import java.io.IOException;
import java.net.URI;
@ -27,32 +25,22 @@ import java.net.http.HttpResponse;
public final class HTTP {
public static HttpResponse<String> postJson(String url, JsonObject json) {
public static HttpResponse<String> postJson(String url, JsonObject json) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).POST(HttpRequest.BodyPublishers.ofString(json.toString())).header("Content-Type", "application/json").build();
try {
return client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (Exception e) {
Log.printException(e, LogLevels.DEBUG);
}
return null;
}
public static HttpResponse<String> get(String url) {
public static HttpResponse<String> get(String url) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();
try {
return client.send(request, HttpResponse.BodyHandlers.ofString());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
return null;
}
public static JsonElement getJson(String url) {
public static JsonElement getJson(String url) throws IOException, InterruptedException {
HttpResponse<String> response = get(url);
if (response == null || response.statusCode() != 200) {
return null;
if (response.statusCode() != 200) {
throw new IOException();
}
return JsonParser.parseString(response.body());
}

View File

@ -1,156 +0,0 @@
/*
* 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.util.mojang.api;
import com.google.gson.JsonObject;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.util.Util;
import java.util.UUID;
public class MojangAccount {
final String userId;
final UUID uuid;
final String playerName;
final String mojangUserName;
String accessToken;
RefreshStates lastRefreshStatus;
private boolean needsRefresh = true;
public MojangAccount(String username, JsonObject json) {
this.accessToken = json.get("accessToken").getAsString();
JsonObject profile = json.get("selectedProfile").getAsJsonObject();
this.uuid = Util.getUUIDFromString(profile.get("id").getAsString());
this.playerName = profile.get("name").getAsString();
JsonObject mojang = json.get("user").getAsJsonObject();
this.userId = mojang.get("id").getAsString();
this.mojangUserName = username;
}
public MojangAccount(String accessToken, String userId, UUID uuid, String playerName, String mojangUserName) {
this.accessToken = accessToken;
this.userId = userId;
this.uuid = uuid;
this.playerName = playerName;
this.mojangUserName = mojangUserName;
}
public static MojangAccount deserialize(JsonObject json) {
return new MojangAccount(json.get("accessToken").getAsString(), json.get("userId").getAsString(), Util.getUUIDFromString(json.get("uuid").getAsString()), json.get("playerName").getAsString(), json.get("userName").getAsString());
}
public JsonObject serialize() {
JsonObject json = new JsonObject();
json.addProperty("accessToken", this.accessToken);
json.addProperty("userId", this.userId);
json.addProperty("uuid", this.uuid.toString());
json.addProperty("playerName", this.playerName);
json.addProperty("userName", this.mojangUserName);
return json;
}
public void join(String serverId) {
MojangAuthentication.joinServer(this, serverId);
}
public RefreshStates refreshToken() {
if (this.lastRefreshStatus != null) {
return this.lastRefreshStatus;
}
String accessToken = MojangAuthentication.refresh(this.accessToken);
if (accessToken == null) {
this.lastRefreshStatus = RefreshStates.FAILED;
return this.lastRefreshStatus;
}
if (accessToken.isBlank()) {
this.lastRefreshStatus = RefreshStates.ERROR;
return this.lastRefreshStatus;
}
this.accessToken = accessToken;
this.lastRefreshStatus = RefreshStates.SUCCESSFUL;
return this.lastRefreshStatus;
}
public UUID getUUID() {
return this.uuid;
}
public String getPlayerName() {
return this.playerName;
}
public String getAccessToken() {
return this.accessToken;
}
public String getMojangUserName() {
return this.mojangUserName;
}
public void saveToConfig() {
Minosoft.getConfig().putMojangAccount(this);
Minosoft.getConfig().saveToFile();
}
public void delete() {
Minosoft.getConfig().removeAccount(this);
Minosoft.getConfig().saveToFile();
}
@Override
public String toString() {
return getUserId();
}
public String getUserId() {
return this.userId;
}
@Override
public int hashCode() {
return this.userId.hashCode();
}
@Override
public boolean equals(Object obj) {
if (super.equals(obj)) {
return true;
}
if (obj == null) {
return false;
}
if (hashCode() != obj.hashCode()) {
return false;
}
MojangAccount account = (MojangAccount) obj;
return account.getUserId().equals(getUserId());
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
this.needsRefresh = false;
}
public boolean needsRefresh() {
return this.needsRefresh;
}
public enum RefreshStates {
SUCCESSFUL,
ERROR, // account not valid anymore
FAILED // error occurred while checking -> Unknown state
}
}

View File

@ -18,19 +18,25 @@ import com.google.gson.JsonParser;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.config.ConfigurationPaths;
import de.bixilon.minosoft.config.StaticConfiguration;
import de.bixilon.minosoft.data.accounts.MojangAccount;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.logging.LogLevels;
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
import de.bixilon.minosoft.util.HTTP;
import de.bixilon.minosoft.util.mojang.api.exceptions.AuthenticationException;
import de.bixilon.minosoft.util.mojang.api.exceptions.MojangJoinServerErrorException;
import de.bixilon.minosoft.util.mojang.api.exceptions.NoNetworkConnectionException;
import java.io.IOException;
import java.net.http.HttpResponse;
public final class MojangAuthentication {
public static MojangAccountAuthenticationAttempt login(String username, String password) {
public static MojangAccount login(String username, String password) throws AuthenticationException, NoNetworkConnectionException {
return login(Minosoft.getConfig().getString(ConfigurationPaths.StringPaths.CLIENT_TOKEN), username, password);
}
public static MojangAccountAuthenticationAttempt login(String clientToken, String username, String password) {
public static MojangAccount login(String clientToken, String username, String password) throws NoNetworkConnectionException, AuthenticationException {
JsonObject agent = new JsonObject();
agent.addProperty("name", "Minecraft");
agent.addProperty("version", 1);
@ -42,22 +48,28 @@ public final class MojangAuthentication {
payload.addProperty("clientToken", clientToken);
payload.addProperty("requestUser", true);
HttpResponse<String> response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_LOGIN, payload);
HttpResponse<String> response;
try {
response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_LOGIN, payload);
} catch (IOException | InterruptedException e) {
Log.printException(e, LogLevels.DEBUG);
throw new NoNetworkConnectionException(e);
}
if (response == null) {
Log.mojang(String.format("Failed to login with username %s", username));
return new MojangAccountAuthenticationAttempt("Unknown error, check your Internet connection");
throw new NoNetworkConnectionException("Unknown error, check your Internet connection");
}
JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject();
if (response.statusCode() != 200) {
Log.mojang(String.format("Failed to login with error code %d: %s", response.statusCode(), jsonResponse.get("errorMessage").getAsString()));
return new MojangAccountAuthenticationAttempt(jsonResponse.get("errorMessage").getAsString());
throw new AuthenticationException(jsonResponse.get("errorMessage").getAsString());
}
// now it is okay
return new MojangAccountAuthenticationAttempt(new MojangAccount(username, jsonResponse));
return new MojangAccount(username, jsonResponse);
}
public static void joinServer(MojangAccount account, String serverId) {
public static void joinServer(MojangAccount account, String serverId) throws NoNetworkConnectionException, MojangJoinServerErrorException {
if (StaticConfiguration.SKIP_MOJANG_AUTHENTICATION) {
return;
}
@ -67,26 +79,31 @@ public final class MojangAuthentication {
payload.addProperty("selectedProfile", account.getUUID().toString().replace("-", ""));
payload.addProperty("serverId", serverId);
HttpResponse<String> response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_JOIN, payload);
HttpResponse<String> response;
try {
response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_JOIN, payload);
} catch (IOException | InterruptedException e) {
throw new NoNetworkConnectionException(e);
}
if (response == null) {
Log.mojang(String.format("Failed to join server: %s", serverId));
return;
throw new MojangJoinServerErrorException();
}
if (response.statusCode() != 204) {
JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject();
Log.mojang(String.format("Failed to join server with error code %d: %s", response.statusCode(), jsonResponse.has("errorMessage") ? jsonResponse.get("errorMessage").getAsString() : "null"));
return;
throw new MojangJoinServerErrorException(jsonResponse.get("errorMessage").getAsString());
}
// joined
Log.mojang("Joined server successfully");
}
public static String refresh(String accessToken) {
public static String refresh(String accessToken) throws NoNetworkConnectionException, AuthenticationException {
return refresh(Minosoft.getConfig().getString(ConfigurationPaths.StringPaths.CLIENT_TOKEN), accessToken);
}
public static String refresh(String clientToken, String accessToken) {
public static String refresh(String clientToken, String accessToken) throws NoNetworkConnectionException, AuthenticationException {
if (StaticConfiguration.SKIP_MOJANG_AUTHENTICATION) {
return clientToken;
}
@ -97,18 +114,18 @@ public final class MojangAuthentication {
HttpResponse<String> response;
try {
response = HTTP.postJson(ProtocolDefinition.MOJANG_URL_REFRESH, payload);
} catch (Exception e) {
} catch (IOException | InterruptedException e) {
Log.mojang(String.format("Could not connect to mojang server: %s", e.getCause().toString()));
return null;
throw new NoNetworkConnectionException(e);
}
if (response == null) {
Log.mojang("Failed to refresh session");
return null;
throw new NoNetworkConnectionException();
}
JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject();
if (response.statusCode() != 200) {
Log.mojang(String.format("Failed to refresh session with error code %d: %s", response.statusCode(), jsonResponse.get("errorMessage").getAsString()));
return "";
throw new AuthenticationException(jsonResponse.get("errorMessage").getAsString());
}
// now it is okay
Log.mojang("Refreshed 1 session token");

View File

@ -1,51 +0,0 @@
/*
* 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.util.mojang.api;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
import de.bixilon.minosoft.util.HTTP;
import de.bixilon.minosoft.util.Util;
import java.net.http.HttpResponse;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public final class MojangBlockedServers {
public static ArrayList<String> getBlockedServers() {
HttpResponse<String> response = HTTP.get(ProtocolDefinition.MOJANG_URL_BLOCKED_SERVERS);
if (response == null) {
Log.mojang("Failed to fetch blocked servers");
return null;
}
if (response.statusCode() != 200) {
Log.mojang(String.format("Failed to fetch blocked server error code %d", response.statusCode()));
return null;
}
// now it is hopefully okay
return new ArrayList<>(Arrays.asList(response.body().split("\n")));
}
public boolean isServerBlocked(List<String> list, String hostname) {
for (String hash : list) {
if (hash.equals(Util.sha1(hostname))) {
return true;
}
// ToDo: check subdomains and ip addresses
}
return false;
}
}

View File

@ -0,0 +1,21 @@
/*
* 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.util.mojang.api.exceptions;
public class AuthenticationException extends Exception {
public AuthenticationException(String message) {
super(message);
}
}

View File

@ -0,0 +1,24 @@
/*
* 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.util.mojang.api.exceptions;
public class MojangJoinServerErrorException extends Exception {
public MojangJoinServerErrorException(String message) {
super(message);
}
public MojangJoinServerErrorException() {
}
}

View File

@ -11,31 +11,19 @@
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
*/
package de.bixilon.minosoft.util.mojang.api;
package de.bixilon.minosoft.util.mojang.api.exceptions;
public class MojangAccountAuthenticationAttempt {
final MojangAccount account;
final String error;
public class NoNetworkConnectionException extends Exception {
public MojangAccountAuthenticationAttempt(MojangAccount account) {
this.account = account;
this.error = null;
public NoNetworkConnectionException(Exception cause) {
super(cause);
}
public MojangAccountAuthenticationAttempt(String error) {
this.account = null;
this.error = error;
public NoNetworkConnectionException(String message) {
super(message);
}
public String getError() {
return this.error;
}
public NoNetworkConnectionException() {
public MojangAccount getAccount() {
return this.account;
}
public boolean succeeded() {
return this.account != null;
}
}

View File

@ -56,7 +56,6 @@
"SETTINGS_DOWNLOAD": "Download",
"LOGIN_DIALOG_TITLE": "Anmelden (Mojang) - Minosoft",
"LOGIN_DIALOG_HEADER": "Bitte gib deine Mojang Zugangsdaten ein dich anzumelden",
"LOGIN_ACCOUNT_ALREADY_PRESENT": "Account existiert bereits!",
"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

@ -57,7 +57,6 @@
"SETTINGS_DOWNLOAD": "Download",
"LOGIN_DIALOG_TITLE": "Login (Mojang) - Minosoft",
"LOGIN_DIALOG_HEADER": "Please login to your mojang account",
"LOGIN_ACCOUNT_ALREADY_PRESENT": "Account is already present!",
"ERROR": "Error",
"MINOSOFT_STILL_STARTING_TITLE": "Please wait",
"MINOSOFT_STILL_STARTING_HEADER": "Minosoft is still starting up...",

View File

@ -1,7 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<!--
~ Minosoft
~ Copyright (C) 2020 Moritz Zwerger
@ -14,6 +11,9 @@
~
~ This software is not affiliated with Mojang AB, the original developer of Minecraft.
-->
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<AnchorPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:id="root" maxHeight="-Infinity" maxWidth="500.0" minHeight="-Infinity" minWidth="500.0" onMouseClicked="#clicked" prefWidth="500.0" fx:controller="de.bixilon.minosoft.gui.main.AccountListCell">
<Label fx:id="playerName" layoutX="111.0" layoutY="14.0" maxWidth="200.0" minWidth="10.0" text="#Player name#" underline="true" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="5.0">
<font>
@ -23,7 +23,7 @@
<MenuButton fx:id="optionsMenu" layoutX="389.0" layoutY="81.0" mnemonicParsing="false" text="⋮" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0">
<items>
<MenuItem fx:id="optionsSelect" mnemonicParsing="false" onAction="#select" text="-Select-" />
<MenuItem fx:id="optionsDelete" mnemonicParsing="false" onAction="#delete" style="-fx-text-fill: red" text="-Delete-" />
<MenuItem fx:id="optionsDelete" mnemonicParsing="false" onAction="#logout" style="-fx-text-fill: red" text="-Delete-"/>
</items>
</MenuButton>
<Label fx:id="email" layoutX="121.0" layoutY="24.0" maxWidth="200.0" minWidth="10.0" text="#Email#" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="30.0" />