Launcher wip (1): basic server list

This commit is contained in:
Bixilon 2020-08-26 15:31:00 +02:00
parent b406706f0a
commit c7246956f4
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
14 changed files with 449 additions and 48 deletions

22
pom.xml
View File

@ -33,10 +33,10 @@
</plugins>
</build>
<properties>
<maven.compiler.source>14</maven.compiler.source>
<maven.compiler.target>14</maven.compiler.target>
</properties>
<properties>
<maven.compiler.source>14</maven.compiler.source>
<maven.compiler.target>14</maven.compiler.target>
</properties>
<dependencies>
<dependency>
@ -54,11 +54,23 @@
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<!-- https://mvnrepository.com/artifact/dnsjava/dnsjava -->
<dependency>
<groupId>dnsjava</groupId>
<artifactId>dnsjava</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>14.0.1</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>14.0.1</version>
<type>pom</type>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,49 @@
/*
* Codename 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;
import de.bixilon.minosoft.gui.main.GUITools;
import de.bixilon.minosoft.gui.main.Server;
import de.bixilon.minosoft.gui.main.ServerListCell;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class Launcher extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
ListView<Server> listView = new ListView<>();
listView.setCellFactory((lv) -> ServerListCell.newInstance());
ObservableList<Server> servers = FXCollections.observableArrayList();
servers.addAll(Minosoft.serverList);
listView.setItems(servers);
Scene scene = new Scene(new BorderPane(listView), 400, 450);
primaryStage.setScene(scene);
primaryStage.setTitle("Minosoft");
primaryStage.getIcons().add(GUITools.logo);
primaryStage.show();
}
}

View File

@ -15,12 +15,10 @@ package de.bixilon.minosoft;
import de.bixilon.minosoft.config.Configuration;
import de.bixilon.minosoft.config.GameConfiguration;
import de.bixilon.minosoft.game.datatypes.Player;
import de.bixilon.minosoft.game.datatypes.objectLoader.versions.Versions;
import de.bixilon.minosoft.gui.main.Server;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.logging.LogLevels;
import de.bixilon.minosoft.protocol.network.Connection;
import de.bixilon.minosoft.protocol.protocol.ConnectionReasons;
import de.bixilon.minosoft.util.FolderUtil;
import de.bixilon.minosoft.util.OSUtil;
import de.bixilon.minosoft.util.Util;
@ -33,7 +31,8 @@ import java.util.UUID;
public class Minosoft {
static Configuration config;
static ArrayList<MojangAccount> accountList;
public static ArrayList<MojangAccount> accountList;
public static ArrayList<Server> serverList;
public static void main(String[] args) {
// init log thread
@ -49,7 +48,7 @@ public class Minosoft {
e.printStackTrace();
return;
}
Log.info(String.format("Loaded config file (version=%s)", config.getInteger(GameConfiguration.CONFIG_VERSION)));
Log.info(String.format("Loaded config file (version=%s)", config.getInt(GameConfiguration.CONFIG_VERSION)));
// set log level from config
Log.setLevel(LogLevels.valueOf(config.getString(GameConfiguration.GENERAL_LOG_LEVEL)));
Log.info(String.format("Logging info with level: %s", Log.getLevel()));
@ -83,8 +82,9 @@ public class Minosoft {
} else {
Log.mojang("Could not refresh session, you will not be able to join premium servers!");
}
Connection c = new Connection(1, config.getString("debug.host"), new Player(account));
c.resolve(ConnectionReasons.CONNECT); // resolve dns address and connect
serverList = config.getServers();
Launcher.main(args);
}
/**

View File

@ -0,0 +1,18 @@
/*
* Codename 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;
public interface PingCallback {
void handle(ServerListPing ping);
}

View File

@ -15,6 +15,8 @@ package de.bixilon.minosoft;
import com.google.gson.JsonObject;
import de.bixilon.minosoft.game.datatypes.TextComponent;
import de.bixilon.minosoft.gui.main.GUITools;
import javafx.scene.image.Image;
public class ServerListPing {
final JsonObject raw;
@ -40,7 +42,14 @@ public class ServerListPing {
}
public String getBase64EncodedFavicon() {
return raw.get("favicon").getAsString();
if (raw.has("favicon")) {
return raw.get("favicon").getAsString().replace("data:image/png;base64,", "");
}
return null;
}
public Image getFavicon() {
return GUITools.getImageFromBase64(getBase64EncodedFavicon());
}
public TextComponent getMotd() {

View File

@ -14,6 +14,7 @@
package de.bixilon.minosoft.config;
import de.bixilon.minosoft.Config;
import de.bixilon.minosoft.gui.main.Server;
import de.bixilon.minosoft.util.mojang.api.MojangAccount;
import org.yaml.snakeyaml.Yaml;
@ -56,12 +57,12 @@ public class Configuration {
return getBoolean(config.getPath());
}
public int getInteger(String path) {
public int getInt(String path) {
return (int) get(path);
}
public int getInteger(ConfigEnum config) {
return getInteger(config.getPath());
public int getInt(ConfigEnum config) {
return getInt(config.getPath());
}
public String getString(String path) {
@ -80,11 +81,11 @@ public class Configuration {
put(path, value);
}
public void putInteger(ConfigEnum config, int value) {
putInteger(config.getPath(), value);
public void putInt(ConfigEnum config, int value) {
putInt(config.getPath(), value);
}
public void putInteger(String path, int value) {
public void putInt(String path, int value) {
put(path, value);
}
@ -104,6 +105,17 @@ public class Configuration {
putString(basePath + "playerName", account.getPlayerName());
}
public void putServer(Server server) {
String basePath = String.format("servers.%d.", server.getId());
putString(basePath + "name", server.getName());
putString(basePath + "address", server.getAddress());
putString(basePath + "account", server.getAccount());
putInt(basePath + "version", server.getDesiredVersion());
if (server.getBase64Favicon() != null) {
putString(basePath + "favicon", server.getBase64Favicon());
}
}
public Object get(String path) {
if (path.contains(".")) {
// split
@ -151,15 +163,34 @@ public class Configuration {
public ArrayList<MojangAccount> getMojangAccounts() {
ArrayList<MojangAccount> accounts = new ArrayList<>();
LinkedHashMap<String, Object> objects = (LinkedHashMap<String, Object>) get("account.accounts");
LinkedHashMap<String, LinkedHashMap<String, Object>> objects = (LinkedHashMap<String, LinkedHashMap<String, Object>>) get("account.accounts");
if (objects == null) {
return accounts;
}
for (Map.Entry<String, Object> set : objects.entrySet()) {
LinkedHashMap<String, Object> entry = (LinkedHashMap<String, Object>) set.getValue();
for (Map.Entry<String, LinkedHashMap<String, Object>> set : objects.entrySet()) {
LinkedHashMap<String, Object> entry = set.getValue();
accounts.add(new MojangAccount((String) entry.get("accessToken"), set.getKey(), UUID.fromString((String) entry.get("uuid")), (String) entry.get("playerName"), (String) entry.get("mojangUserName")));
}
return accounts;
}
public ArrayList<Server> getServers() {
ArrayList<Server> servers = new ArrayList<>();
LinkedHashMap<String, LinkedHashMap<String, Object>> objects = (LinkedHashMap<String, LinkedHashMap<String, Object>>) get("servers");
if (objects == null) {
return servers;
}
for (Map.Entry<String, LinkedHashMap<String, Object>> set : objects.entrySet()) {
LinkedHashMap<String, Object> entry = set.getValue();
String favicon = null;
if (entry.containsKey("favicon")) {
favicon = (String) entry.get("favicon");
}
servers.add(new Server(Integer.parseInt(set.getKey()), (String) entry.get("name"), (String) entry.get("address"), (String) entry.get("account"), (int) entry.get("version"), favicon));
}
return servers;
}
}

View File

@ -0,0 +1,30 @@
/*
* Codename 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;
import javafx.scene.image.Image;
import java.io.ByteArrayInputStream;
import java.util.Base64;
public class GUITools {
public final static Image logo = new Image(GUITools.class.getResourceAsStream("/icons/windowIcon.png"));
public static Image getImageFromBase64(String base64) {
if (base64 == null) {
return null;
}
return new Image(new ByteArrayInputStream(Base64.getDecoder().decode(base64)));
}
}

View File

@ -0,0 +1,101 @@
/*
* Codename 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;
import de.bixilon.minosoft.Config;
import de.bixilon.minosoft.Minosoft;
import javafx.scene.image.Image;
import javax.annotation.Nullable;
public class Server {
final int id;
String name;
String address;
String account;
int desiredVersion;
String favicon;
public Server(int id, String name, String address, String account, int desiredVersion) {
this.id = id;
this.name = name;
this.address = address;
this.account = account;
this.desiredVersion = desiredVersion;
}
public Server(int id, String name, String address, String account, int desiredVersion, String favicon) {
this.id = id;
this.name = name;
this.address = address;
this.account = account;
this.desiredVersion = desiredVersion;
this.favicon = favicon;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public int getDesiredVersion() {
return desiredVersion;
}
public void setDesiredVersion(int desiredVersion) {
this.desiredVersion = desiredVersion;
}
@Nullable
public String getBase64Favicon() {
return favicon;
}
public void setBase64Favicon(String favicon) {
this.favicon = favicon;
}
@Nullable
public Image getFavicon() {
return GUITools.getImageFromBase64(getBase64Favicon());
}
public int getId() {
return id;
}
public void saveToConfig() {
Minosoft.getConfig().putServer(this);
Minosoft.getConfig().saveToFile(Config.configFileName);
}
}

View File

@ -0,0 +1,129 @@
/*
* Codename 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;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.game.datatypes.Player;
import de.bixilon.minosoft.game.datatypes.objectLoader.versions.Version;
import de.bixilon.minosoft.game.datatypes.objectLoader.versions.Versions;
import de.bixilon.minosoft.protocol.network.Connection;
import de.bixilon.minosoft.protocol.protocol.ConnectionReasons;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
public class ServerListCell extends ListCell<Server> implements Initializable {
@FXML
public ImageView icon;
@FXML
public Label motd;
@FXML
public Label version;
@FXML
public ImageView ping; //ToDo
@FXML
public Label players;
@FXML
public MenuItem optionsConnect;
@FXML
public MenuItem optionsEdit;
@FXML
public MenuItem optionsDelete;
@FXML
private Label serverName;
@FXML
private AnchorPane root;
private Server model;
public static ServerListCell newInstance() {
FXMLLoader loader = new FXMLLoader(ServerListCell.class.getResource("/layout/cells/server.fxml"));
try {
loader.load();
return loader.getController();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
/**
* Initializes the controller class.
*/
@Override
public void initialize(URL url, ResourceBundle rb) {
updateSelected(false);
setGraphic(root);
}
public AnchorPane getRoot() {
return root;
}
@Override
protected void updateItem(Server server, boolean empty) {
super.updateItem(server, empty);
getRoot().getChildrenUnmodifiable().forEach(c -> c.setVisible(!empty));
if (!empty && server != null && !server.equals(this.model)) {
serverName.setText(server.getName());
Image favicon = server.getFavicon();
if (favicon == null) {
favicon = GUITools.logo;
}
icon.setImage(favicon);
Connection c = new Connection(server.getId(), server.getAddress(), new Player(Minosoft.accountList.get(0)));
c.addPingCallback(ping -> Platform.runLater(() -> {
if (ping == null) {
// Offline
players.setText("");
version.setText("Offline");
optionsConnect.setDisable(true);
return;
}
players.setText(String.format("%d/%d", ping.getPlayerOnline(), ping.getMaxPlayers()));
Version serverVersion = Versions.getVersionById(ping.getProtocolNumber());
if (serverVersion == null) {
version.setText(String.format("UNKNOWN(%d)", ping.getProtocolNumber()));
} else {
version.setText(serverVersion.getVersionName());
}
motd.setText(ping.getMotd().getRawMessage());
if (ping.getFavicon() != null) {
server.setBase64Favicon(ping.getBase64EncodedFavicon());
server.saveToConfig();
icon.setImage(ping.getFavicon());
}
}));
c.resolve(ConnectionReasons.PING); // resolve dns address and connect
}
this.model = server;
}
@Override
public void updateSelected(boolean selected) {
super.updateSelected(selected);
}
}

View File

@ -14,6 +14,8 @@
package de.bixilon.minosoft.protocol.network;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.PingCallback;
import de.bixilon.minosoft.ServerListPing;
import de.bixilon.minosoft.config.GameConfiguration;
import de.bixilon.minosoft.game.datatypes.Player;
import de.bixilon.minosoft.game.datatypes.VelocityHandler;
@ -45,13 +47,15 @@ public class Connection {
final PacketSender sender = new PacketSender(this);
final ArrayList<ClientboundPacket> handlingQueue = new ArrayList<>();
final VelocityHandler velocityHandler = new VelocityHandler(this);
final ArrayList<PingCallback> pingCallbacks = new ArrayList<>();
final int connectionId;
final Player player;
int desiredVersionNumber = -1;
ServerAddress address;
PluginChannelHandler pluginChannelHandler;
Thread handleThread;
Version version = Versions.getLowestVersionSupported(); // default
final CustomMapping customMapping = new CustomMapping(version);
final Player player;
ConnectionStates state = ConnectionStates.DISCONNECTED;
ConnectionReasons reason;
ConnectionReasons nextReason;
@ -166,8 +170,10 @@ public class Connection {
Log.warn(String.format("Could not connect to %s, trying next hostname: %s", address, nextAddress));
this.address = nextAddress;
resolve(address);
} else {
// no connection and no servers available anymore... sorry, but you can not play today :(
handleCallbacks(null);
}
// else: failed
break;
}
}
@ -295,7 +301,7 @@ public class Connection {
public int getPacketCommand(Packets.Serverbound packet) {
Integer command = null;
if (getReason() != ConnectionReasons.GET_VERSION) {
if (getReason() == ConnectionReasons.CONNECT) {
command = version.getCommandByPacket(packet);
}
if (command == null) {
@ -306,7 +312,7 @@ public class Connection {
public Packets.Clientbound getPacketByCommand(ConnectionStates state, int command) {
Packets.Clientbound packet = null;
if (getReason() != ConnectionReasons.GET_VERSION) {
if (getReason() == ConnectionReasons.CONNECT) {
packet = version.getPacketByCommand(state, command);
}
if (packet == null) {
@ -322,4 +328,26 @@ public class Connection {
public int getConnectionId() {
return connectionId;
}
public void addPingCallback(PingCallback pingCallback) {
pingCallbacks.add(pingCallback);
}
public ArrayList<PingCallback> getPingCallbacks() {
return pingCallbacks;
}
public int getDesiredVersionNumber() {
return desiredVersionNumber;
}
public void setDesiredVersionNumber(int desiredVersionNumber) {
this.desiredVersionNumber = desiredVersionNumber;
}
public void handleCallbacks(ServerListPing ping) {
for (PingCallback callback : getPingCallbacks()) {
callback.handle(ping);
}
}
}

View File

@ -31,9 +31,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.*;
import java.util.ArrayList;
public class Network {
@ -201,7 +199,7 @@ public class Network {
connection.setConnectionState(ConnectionStates.DISCONNECTED);
} catch (IOException e) {
// Could not connect
if (e instanceof SocketTimeoutException) {
if (e instanceof SocketTimeoutException || e instanceof ConnectException || e instanceof UnknownHostException) {
connection.setConnectionState(ConnectionStates.FAILED);
}
e.printStackTrace();

View File

@ -13,7 +13,6 @@
package de.bixilon.minosoft.protocol.protocol;
import de.bixilon.minosoft.Minosoft;
import de.bixilon.minosoft.game.datatypes.GameModes;
import de.bixilon.minosoft.game.datatypes.entities.Entity;
import de.bixilon.minosoft.game.datatypes.entities.meta.HumanMetaData;
@ -56,21 +55,21 @@ public class PacketHandler {
}
public void handle(PacketStatusResponse pkg) {
if (connection.getReason() == ConnectionReasons.GET_VERSION) {
// now we know the version, set it, if the config allows it
Version version;
int versionId = Minosoft.getConfig().getInteger("debug.version");
if (versionId == -1) {
versionId = pkg.getResponse().getProtocolNumber();
}
version = Versions.getVersionById(versionId);
if (version == null) {
Log.fatal(String.format("Server is running on unknown version or a invalid version was forced (version=%d). Exiting...", versionId));
System.exit(1);
}
Log.info(String.format("Status response received: %s/%s online. MotD: '%s'", pkg.getResponse().getPlayerOnline(), pkg.getResponse().getMaxPlayers(), pkg.getResponse().getMotd().getColoredMessage()));
// now we know the version, set it, if the config allows it
Version version;
int versionId = connection.getDesiredVersionNumber();
if (versionId == -1) {
versionId = pkg.getResponse().getProtocolNumber();
}
version = Versions.getVersionById(versionId);
if (version == null) {
Log.fatal(String.format("Server is running on unknown version or a invalid version was forced (version=%d). Disconnecting...", versionId));
connection.disconnect();
} else {
connection.setVersion(version);
}
Log.info(String.format("Status response received: %s/%s online. MotD: '%s'", pkg.getResponse().getPlayerOnline(), pkg.getResponse().getMaxPlayers(), pkg.getResponse().getMotd().getColoredMessage()));
connection.handleCallbacks(pkg.getResponse());
}
public void handle(PacketStatusPong pkg) {

View File

@ -15,7 +15,4 @@ account:
clientToken: "randomGenerated"
accounts:
# this will be removed soon, only for debugging (pre alpha stage): some features are not implemented yet -/-
debug:
server: "127.0.0.1"
version: -1
servers:

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB