mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-17 11:24:56 -04:00
eros: multiple server types (custom + LAN servers)
This commit is contained in:
parent
43d8988ef9
commit
b951322066
@ -22,7 +22,7 @@ import java.util.*
|
||||
|
||||
abstract class AbstractCard<T> : ListCell<T>(), Initializable {
|
||||
@FXML
|
||||
protected lateinit var root: HBox
|
||||
lateinit var root: HBox
|
||||
|
||||
override fun initialize(url: URL?, resourceBundle: ResourceBundle?) {
|
||||
this.graphic = root
|
||||
|
@ -19,12 +19,12 @@ import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.control.ListCell
|
||||
|
||||
interface CardFactory<T : ListCell<*>> {
|
||||
val FXML: ResourceLocation
|
||||
val LAYOUT: ResourceLocation
|
||||
|
||||
fun build(): T {
|
||||
val loader = FXMLLoader()
|
||||
|
||||
loader.load<Any>(Minosoft.MINOSOFT_ASSETS_MANAGER.readAssetAsStream(FXML))
|
||||
loader.load<Any>(Minosoft.MINOSOFT_ASSETS_MANAGER.readAssetAsStream(LAYOUT))
|
||||
|
||||
return loader.getController()
|
||||
}
|
||||
|
@ -13,23 +13,30 @@
|
||||
|
||||
package de.bixilon.minosoft.gui.eros.main.play
|
||||
|
||||
import de.bixilon.minosoft.Minosoft
|
||||
import de.bixilon.minosoft.gui.eros.controller.EmbeddedJavaFXController
|
||||
import de.bixilon.minosoft.gui.eros.main.play.server.Refreshable
|
||||
import de.bixilon.minosoft.gui.eros.main.play.server.ServerListController
|
||||
import de.bixilon.minosoft.gui.eros.main.play.server.type.ServerType
|
||||
import de.bixilon.minosoft.gui.eros.main.play.server.type.ServerTypeCardController
|
||||
import de.bixilon.minosoft.gui.eros.modding.invoker.JavaFXEventInvoker
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.text
|
||||
import de.bixilon.minosoft.modding.event.events.LANServerDiscoverEvent
|
||||
import de.bixilon.minosoft.protocol.protocol.LANServerListener
|
||||
import de.bixilon.minosoft.util.KUtil.asResourceLocation
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.ListView
|
||||
import javafx.scene.layout.AnchorPane
|
||||
import javafx.scene.layout.Pane
|
||||
import org.kordamp.ikonli.fontawesome5.FontAwesomeSolid
|
||||
|
||||
class PlayMainController : EmbeddedJavaFXController<Pane>() {
|
||||
@FXML
|
||||
private lateinit var playTypeContentFX: Pane
|
||||
|
||||
@FXML
|
||||
private lateinit var playTypeListViewFX: ListView<*>
|
||||
private lateinit var playTypeListViewFX: ListView<ServerType>
|
||||
|
||||
@FXML
|
||||
private lateinit var refreshPaneFX: AnchorPane
|
||||
@ -38,12 +45,38 @@ class PlayMainController : EmbeddedJavaFXController<Pane>() {
|
||||
private lateinit var currentController: EmbeddedJavaFXController<*>
|
||||
|
||||
override fun init() {
|
||||
currentController = JavaFXUtil.loadEmbeddedController<ServerListController>(ServerListController.LAYOUT)
|
||||
playTypeContentFX.children.setAll(currentController.root)
|
||||
playTypeListViewFX.setCellFactory { ServerTypeCardController.build() }
|
||||
playTypeListViewFX.items += ServerType(FontAwesomeSolid.SERVER, "Custom", "0 Servers", "") {
|
||||
return@ServerType JavaFXUtil.loadEmbeddedController<ServerListController>(ServerListController.LAYOUT).apply {
|
||||
servers = Minosoft.config.config.server.entries.values
|
||||
refreshList()
|
||||
}
|
||||
}
|
||||
playTypeListViewFX.items += ServerType(FontAwesomeSolid.NETWORK_WIRED, "LAN", "12 Servers", "") {
|
||||
return@ServerType JavaFXUtil.loadEmbeddedController<ServerListController>(ServerListController.LAYOUT).apply {
|
||||
readOnly = true
|
||||
customRefresh = {
|
||||
LANServerListener.SERVERS.clear()
|
||||
refreshList()
|
||||
}
|
||||
servers = LANServerListener.SERVERS.values
|
||||
Minosoft.GLOBAL_EVENT_MASTER.registerEvent(JavaFXEventInvoker.of<LANServerDiscoverEvent> { refreshList() }) // ToDo: Unregister event when hiding pane
|
||||
refreshList()
|
||||
}
|
||||
}
|
||||
|
||||
JavaFXUtil.loadEmbeddedController<ServerTypeCardController>(ServerTypeCardController.LAYOUT).apply {
|
||||
playTypeListViewFX.selectionModel.selectedItemProperty().addListener { _, _, new ->
|
||||
currentController = new.content()
|
||||
playTypeContentFX.children.setAll(currentController.root)
|
||||
}
|
||||
|
||||
playTypeListViewFX.selectionModel.select(0)
|
||||
|
||||
ServerTypeCardController.build().apply {
|
||||
refreshPaneFX.children.setAll(root)
|
||||
iconFX.iconLiteral = "fas-sync-alt"
|
||||
iconFX.isVisible = true
|
||||
|
||||
headerFX.text = REFRESH_HEADER
|
||||
text1FX.text = REFRESH_TEXT1
|
||||
text2FX.text = REFRESH_TEXT2
|
||||
|
@ -66,6 +66,16 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
|
||||
@FXML
|
||||
private lateinit var serverInfoFX: AnchorPane
|
||||
|
||||
var customRefresh: (() -> Unit)? = null
|
||||
|
||||
var servers: MutableCollection<Server> = mutableListOf()
|
||||
|
||||
var readOnly: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
addServerButtonFX.isVisible = !value
|
||||
}
|
||||
|
||||
|
||||
override fun init() {
|
||||
serverListViewFX.setCellFactory { ServerCardController.build() }
|
||||
@ -86,7 +96,7 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
|
||||
val selected = serverListViewFX.selectionModel.selectedItem
|
||||
serverListViewFX.items.clear()
|
||||
|
||||
for (server in Minosoft.config.config.server.entries.values) {
|
||||
for (server in servers) {
|
||||
updateServer(server)
|
||||
}
|
||||
|
||||
@ -168,43 +178,45 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
|
||||
it.columnConstraints += ColumnConstraints()
|
||||
it.columnConstraints += ColumnConstraints(0.0, -1.0, Double.POSITIVE_INFINITY, Priority.ALWAYS, HPos.LEFT, true)
|
||||
|
||||
it.add(Button("Delete").apply {
|
||||
setOnAction {
|
||||
SimpleErosConfirmationDialog(
|
||||
confirmButtonText = "minosoft:general.delete".asResourceLocation(),
|
||||
description = TranslatableComponents.EROS_DELETE_SERVER_CONFIRM_DESCRIPTION(Minosoft.LANGUAGE_MANAGER, serverCard.server.name, serverCard.server.address),
|
||||
onConfirm = {
|
||||
Minosoft.config.config.server.entries.remove(serverCard.server.id)
|
||||
if (!readOnly) {
|
||||
it.add(Button("Delete").apply {
|
||||
setOnAction {
|
||||
SimpleErosConfirmationDialog(
|
||||
confirmButtonText = "minosoft:general.delete".asResourceLocation(),
|
||||
description = TranslatableComponents.EROS_DELETE_SERVER_CONFIRM_DESCRIPTION(Minosoft.LANGUAGE_MANAGER, serverCard.server.name, serverCard.server.address),
|
||||
onConfirm = {
|
||||
Minosoft.config.config.server.entries.remove(serverCard.server.id)
|
||||
Minosoft.config.saveToFile()
|
||||
Platform.runLater { refreshList() }
|
||||
}
|
||||
).show()
|
||||
}
|
||||
}, 1, 0)
|
||||
it.add(Button("Edit").apply {
|
||||
setOnAction {
|
||||
val server = serverCard.server
|
||||
UpdateServerDialog(server = server, onUpdate = { name, address, forcedVersion ->
|
||||
server.name = ChatComponent.of(name)
|
||||
server.forcedVersion = forcedVersion
|
||||
if (server.address != address) {
|
||||
server.favicon = null
|
||||
|
||||
server.address = address
|
||||
|
||||
// disconnect all ping connections, re ping
|
||||
// ToDo: server.connections.clear()
|
||||
|
||||
serverCard.unregister()
|
||||
server.ping?.disconnect()
|
||||
server.ping = null
|
||||
server.ping()
|
||||
}
|
||||
Minosoft.config.saveToFile()
|
||||
Platform.runLater { refreshList() }
|
||||
}
|
||||
).show()
|
||||
}
|
||||
}, 1, 0)
|
||||
it.add(Button("Edit").apply {
|
||||
setOnAction {
|
||||
val server = serverCard.server
|
||||
UpdateServerDialog(server = server, onUpdate = { name, address, forcedVersion ->
|
||||
server.name = ChatComponent.of(name)
|
||||
server.forcedVersion = forcedVersion
|
||||
if (server.address != address) {
|
||||
server.favicon = null
|
||||
|
||||
server.address = address
|
||||
|
||||
// disconnect all ping connections, re ping
|
||||
// ToDo: server.connections.clear()
|
||||
|
||||
serverCard.unregister()
|
||||
server.ping?.disconnect()
|
||||
server.ping = null
|
||||
server.ping()
|
||||
}
|
||||
Minosoft.config.saveToFile()
|
||||
Platform.runLater { refreshList() }
|
||||
}).show()
|
||||
}
|
||||
}, 2, 0)
|
||||
}).show()
|
||||
}
|
||||
}, 2, 0)
|
||||
}
|
||||
|
||||
it.add(Button("Refresh").apply {
|
||||
setOnAction {
|
||||
@ -256,15 +268,20 @@ class ServerListController : EmbeddedJavaFXController<Pane>(), Refreshable {
|
||||
|
||||
@FXML
|
||||
fun addServer() {
|
||||
UpdateServerDialog(onUpdate = { name, address, focedVersion ->
|
||||
val server = Server(name = ChatComponent.of(name), address = address, forcedVersion = focedVersion)
|
||||
Minosoft.config.config.server.entries[server.id] = server
|
||||
UpdateServerDialog(onUpdate = { name, address, forcedVersion ->
|
||||
val server = Server(name = ChatComponent.of(name), address = address, forcedVersion = forcedVersion)
|
||||
Minosoft.config.config.server.entries[server.id] = server // ToDo
|
||||
Minosoft.config.saveToFile()
|
||||
Platform.runLater { refreshList() }
|
||||
}).show()
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
customRefresh?.let {
|
||||
it()
|
||||
return
|
||||
}
|
||||
|
||||
for (serverCard in serverListViewFX.items) {
|
||||
serverCard.server.ping?.let {
|
||||
if (it.state != StatusConnectionStates.PING_DONE && it.state != StatusConnectionStates.ERROR) {
|
||||
|
@ -127,6 +127,6 @@ class ServerCardController : AbstractCard<ServerCard>() {
|
||||
}
|
||||
|
||||
companion object : CardFactory<ServerCardController> {
|
||||
override val FXML: ResourceLocation = "minosoft:eros/main/play/server/server_card.fxml".asResourceLocation()
|
||||
override val LAYOUT: ResourceLocation = "minosoft:eros/main/play/server/server_card.fxml".asResourceLocation()
|
||||
}
|
||||
}
|
||||
|
@ -11,30 +11,15 @@
|
||||
* This software is not affiliated with Mojang AB, the original developer of Minecraft.
|
||||
*/
|
||||
|
||||
package de.bixilon.minosoft.gui.eros.main.play
|
||||
package de.bixilon.minosoft.gui.eros.main.play.server.type
|
||||
|
||||
import de.bixilon.minosoft.gui.eros.controller.EmbeddedJavaFXController
|
||||
import de.bixilon.minosoft.util.KUtil.asResourceLocation
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.layout.HBox
|
||||
import javafx.scene.text.TextFlow
|
||||
import org.kordamp.ikonli.javafx.FontIcon
|
||||
import org.kordamp.ikonli.fontawesome5.FontAwesomeSolid
|
||||
|
||||
class ServerTypeCardController : EmbeddedJavaFXController<HBox>() {
|
||||
@FXML
|
||||
lateinit var iconFX: FontIcon
|
||||
|
||||
@FXML
|
||||
lateinit var headerFX: TextFlow
|
||||
|
||||
@FXML
|
||||
lateinit var text1FX: TextFlow
|
||||
|
||||
@FXML
|
||||
lateinit var text2FX: TextFlow
|
||||
|
||||
|
||||
companion object {
|
||||
val LAYOUT = "minosoft:eros/main/play/server_type_card.fxml".asResourceLocation()
|
||||
}
|
||||
}
|
||||
data class ServerType(
|
||||
val icon: FontAwesomeSolid,
|
||||
val header: Any?,
|
||||
val text1: Any?,
|
||||
val text2: Any?,
|
||||
val content: () -> EmbeddedJavaFXController<*>,
|
||||
)
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2021 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.eros.main.play.server.type
|
||||
|
||||
import de.bixilon.minosoft.gui.eros.card.AbstractCard
|
||||
import de.bixilon.minosoft.gui.eros.card.CardFactory
|
||||
import de.bixilon.minosoft.gui.eros.util.JavaFXUtil.text
|
||||
import de.bixilon.minosoft.util.KUtil.asResourceLocation
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.text.TextFlow
|
||||
import org.kordamp.ikonli.javafx.FontIcon
|
||||
|
||||
class ServerTypeCardController : AbstractCard<ServerType>() {
|
||||
@FXML
|
||||
lateinit var iconFX: FontIcon
|
||||
|
||||
@FXML
|
||||
lateinit var headerFX: TextFlow
|
||||
|
||||
@FXML
|
||||
lateinit var text1FX: TextFlow
|
||||
|
||||
@FXML
|
||||
lateinit var text2FX: TextFlow
|
||||
|
||||
override fun updateItem(item: ServerType?, empty: Boolean) {
|
||||
super.updateItem(item, empty)
|
||||
item ?: return
|
||||
|
||||
|
||||
iconFX.isVisible = true
|
||||
iconFX.iconCode = item.icon
|
||||
headerFX.text = item.header
|
||||
text1FX.text = item.text1
|
||||
text2FX.text = item.text2
|
||||
}
|
||||
|
||||
|
||||
override fun clear() {
|
||||
iconFX.isVisible = false
|
||||
headerFX.children.clear()
|
||||
text1FX.children.clear()
|
||||
text2FX.children.clear()
|
||||
}
|
||||
|
||||
companion object : CardFactory<ServerTypeCardController> {
|
||||
override val LAYOUT = "minosoft:eros/main/play/server_type_card.fxml".asResourceLocation()
|
||||
}
|
||||
}
|
@ -68,19 +68,19 @@ object JavaFXUtil {
|
||||
return controller
|
||||
}
|
||||
|
||||
var TextFlow.text: Any
|
||||
var TextFlow.text: Any?
|
||||
get() = TODO()
|
||||
set(value) {
|
||||
this.children.setAll(Minosoft.LANGUAGE_MANAGER.translate(value).javaFXText)
|
||||
}
|
||||
|
||||
var TextField.placeholder: Any
|
||||
var TextField.placeholder: Any?
|
||||
get() = this.promptText
|
||||
set(value) {
|
||||
this.promptText = Minosoft.LANGUAGE_MANAGER.translate(value).message
|
||||
}
|
||||
|
||||
var Labeled.ctext: Any
|
||||
var Labeled.ctext: Any?
|
||||
get() = this.text
|
||||
set(value) {
|
||||
this.text = Minosoft.LANGUAGE_MANAGER.translate(value).message
|
||||
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Minosoft
|
||||
* Copyright (C) 2021 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.modding.event.events
|
||||
|
||||
import de.bixilon.minosoft.config.server.Server
|
||||
import java.net.InetAddress
|
||||
|
||||
class LANServerDiscoverEvent(
|
||||
val remoteAddress: InetAddress,
|
||||
val serer: Server,
|
||||
) : Event(), CancelableEvent
|
@ -1,96 +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.protocol.protocol;
|
||||
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import de.bixilon.minosoft.config.server.Server;
|
||||
import de.bixilon.minosoft.util.Util;
|
||||
import de.bixilon.minosoft.util.logging.Log;
|
||||
import de.bixilon.minosoft.util.logging.LogLevels;
|
||||
import de.bixilon.minosoft.util.logging.LogMessageType;
|
||||
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class LANServerListener {
|
||||
public static final HashBiMap<InetAddress, Server> SERVER_MAP = HashBiMap.create();
|
||||
private static final String MOTD_BEGIN_STRING = "[MOTD]";
|
||||
private static final String MOTD_END_STRING = "[/MOTD]";
|
||||
private static final String PORT_START_STRING = "[AD]";
|
||||
private static final String PORT_END_STRING = "[/AD]";
|
||||
private static final String[] BROADCAST_MUST_CONTAIN = {MOTD_BEGIN_STRING, MOTD_END_STRING, PORT_START_STRING, PORT_END_STRING};
|
||||
|
||||
public static void listen() throws InterruptedException {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
MulticastSocket socket = new MulticastSocket(ProtocolDefinition.LAN_SERVER_BROADCAST_PORT);
|
||||
socket.joinGroup(new InetSocketAddress(ProtocolDefinition.LAN_SERVER_BROADCAST_INET_ADDRESS, ProtocolDefinition.LAN_SERVER_BROADCAST_PORT), NetworkInterface.getByInetAddress(ProtocolDefinition.LAN_SERVER_BROADCAST_INET_ADDRESS));
|
||||
byte[] buf = new byte[256]; // this should be enough, if the packet is longer, it is probably invalid
|
||||
Log.log(LogMessageType.NETWORK_STATUS, LogLevels.INFO, () -> "Listening for LAN servers");
|
||||
latch.countDown();
|
||||
while (true) {
|
||||
try {
|
||||
DatagramPacket packet = new DatagramPacket(buf, buf.length);
|
||||
socket.receive(packet);
|
||||
Log.protocol(String.format("LAN UDP Broadcast from %s:%s -> %s", packet.getAddress().getHostAddress(), packet.getPort(), new String(buf)));
|
||||
InetAddress sender = packet.getAddress();
|
||||
if (SERVER_MAP.containsKey(sender)) {
|
||||
// This guy sent us already a server, maybe just the regular 1.5 second interval, a duplicate or a DOS attack...We don't care
|
||||
continue;
|
||||
}
|
||||
Server server = getServerByBroadcast(sender, packet.getData());
|
||||
if (SERVER_MAP.containsValue(server)) {
|
||||
continue;
|
||||
}
|
||||
if (SERVER_MAP.size() > ProtocolDefinition.LAN_SERVER_MAXIMUM_SERVERS) {
|
||||
continue;
|
||||
}
|
||||
SERVER_MAP.put(sender, server);
|
||||
Log.debug(String.format("Discovered new LAN Server: %s", server));
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
latch.countDown();
|
||||
}
|
||||
SERVER_MAP.clear();
|
||||
Log.warn("Stopping LAN Server Listener Thread");
|
||||
}, "LAN Server Listener").start();
|
||||
latch.await();
|
||||
}
|
||||
|
||||
private static Server getServerByBroadcast(InetAddress address, byte[] broadcast) {
|
||||
String parsed = new String(broadcast, StandardCharsets.UTF_8); // example: [MOTD]Bixilon - New World[/MOTD][AD]41127[/AD]
|
||||
for (String mustContain : BROADCAST_MUST_CONTAIN) {
|
||||
if (!parsed.contains(mustContain)) {
|
||||
throw new IllegalArgumentException("Broadcast is invalid!");
|
||||
}
|
||||
}
|
||||
String rawAddress = Util.getStringBetween(parsed, PORT_START_STRING, PORT_END_STRING);
|
||||
if (rawAddress.contains(":")) {
|
||||
// weird, just extract the port
|
||||
rawAddress = rawAddress.split(":")[1];
|
||||
}
|
||||
int port = Integer.parseInt(rawAddress);
|
||||
if (port < 0 || port > 65535) {
|
||||
throw new IllegalArgumentException(String.format("Invalid port: %d", port));
|
||||
}
|
||||
// return new Server(new ServerAddress(address.getHostAddress(), port));
|
||||
throw new RuntimeException("TODO");
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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.protocol.protocol
|
||||
|
||||
import com.google.common.collect.HashBiMap
|
||||
import de.bixilon.minosoft.Minosoft
|
||||
import de.bixilon.minosoft.config.server.Server
|
||||
import de.bixilon.minosoft.modding.event.events.LANServerDiscoverEvent
|
||||
import de.bixilon.minosoft.util.Util
|
||||
import de.bixilon.minosoft.util.logging.Log
|
||||
import de.bixilon.minosoft.util.logging.LogLevels
|
||||
import de.bixilon.minosoft.util.logging.LogMessageType
|
||||
import java.net.*
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
object LANServerListener {
|
||||
val SERVERS: HashBiMap<InetAddress, Server> = HashBiMap.create()
|
||||
private const val MOTD_BEGIN_STRING = "[MOTD]"
|
||||
private const val MOTD_END_STRING = "[/MOTD]"
|
||||
private const val PORT_START_STRING = "[AD]"
|
||||
private const val PORT_END_STRING = "[/AD]"
|
||||
private val BROADCAST_MUST_CONTAIN = arrayOf(MOTD_BEGIN_STRING, MOTD_END_STRING, PORT_START_STRING, PORT_END_STRING)
|
||||
|
||||
fun listen() {
|
||||
val latch = CountDownLatch(1)
|
||||
Thread({
|
||||
try {
|
||||
val socket = MulticastSocket(ProtocolDefinition.LAN_SERVER_BROADCAST_PORT)
|
||||
socket.joinGroup(InetSocketAddress(ProtocolDefinition.LAN_SERVER_BROADCAST_INET_ADDRESS, ProtocolDefinition.LAN_SERVER_BROADCAST_PORT), NetworkInterface.getByInetAddress(ProtocolDefinition.LAN_SERVER_BROADCAST_INET_ADDRESS))
|
||||
val buffer = ByteArray(256) // this should be enough, if the packet is longer, it is probably invalid
|
||||
Log.log(LogMessageType.NETWORK_STATUS, LogLevels.INFO) { "Listening for LAN servers..." }
|
||||
latch.countDown()
|
||||
while (true) {
|
||||
try {
|
||||
val packet = DatagramPacket(buffer, buffer.size)
|
||||
socket.receive(packet)
|
||||
val broadcast = String(buffer, 0, packet.length, StandardCharsets.UTF_8)
|
||||
Log.log(LogMessageType.NETWORK_PACKETS_IN, LogLevels.INFO) { "Received LAN servers broadcast (${packet.address.hostAddress}:${packet.port}): $broadcast" }
|
||||
val sender = packet.address
|
||||
if (SERVERS.containsKey(sender)) {
|
||||
// This guy sent us already a server, maybe just the regular 1.5 second interval, a duplicate or a DOS attack...We don't care
|
||||
continue
|
||||
}
|
||||
val server = getServerByBroadcast(sender, broadcast)
|
||||
if (SERVERS.containsValue(server)) {
|
||||
continue
|
||||
}
|
||||
if (SERVERS.size > ProtocolDefinition.LAN_SERVER_MAXIMUM_SERVERS) {
|
||||
continue
|
||||
}
|
||||
if (Minosoft.GLOBAL_EVENT_MASTER.fireEvent(LANServerDiscoverEvent(packet.address, server))) {
|
||||
continue
|
||||
}
|
||||
SERVERS[sender] = server
|
||||
Log.log(LogMessageType.NETWORK_PACKETS_IN, LogLevels.INFO) { "Discovered LAN servers: $server" }
|
||||
} catch (ignored: Throwable) {
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
latch.countDown()
|
||||
}
|
||||
SERVERS.clear()
|
||||
Log.log(LogMessageType.NETWORK_STATUS, LogLevels.INFO) { "Stop listening for LAN servers..." }
|
||||
}, "LAN Server Listener").start()
|
||||
latch.await()
|
||||
}
|
||||
|
||||
private fun getServerByBroadcast(address: InetAddress, broadcast: String): Server {
|
||||
// example: [MOTD]Bixilon - New World[/MOTD][AD]41127[/AD]
|
||||
for (mustContain in BROADCAST_MUST_CONTAIN) {
|
||||
require(broadcast.contains(mustContain)) { "Broadcast is invalid!" }
|
||||
}
|
||||
var rawAddress = Util.getStringBetween(broadcast, PORT_START_STRING, PORT_END_STRING)
|
||||
if (rawAddress.contains(":")) {
|
||||
// weird, just extract the port
|
||||
rawAddress = rawAddress.split(":").toTypedArray()[1]
|
||||
}
|
||||
val port = rawAddress.toInt()
|
||||
require(!(port < 0 || port > 65535)) { String.format("Invalid port: %d", port) }
|
||||
return Server(address = address.hostAddress + ":" + rawAddress) // ToDo: Name
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import org.kordamp.ikonli.javafx.FontIcon?>
|
||||
<HBox xmlns:fx="http://javafx.com/fxml/1" prefHeight="80.0" prefWidth="190.0" xmlns="http://javafx.com/javafx/16" fx:controller="de.bixilon.minosoft.gui.eros.main.play.ServerTypeCardController">
|
||||
<HBox xmlns:fx="http://javafx.com/fxml/1" fx:id="root" prefHeight="80.0" prefWidth="190.0" xmlns="http://javafx.com/javafx/16" fx:controller="de.bixilon.minosoft.gui.eros.main.play.server.type.ServerTypeCardController">
|
||||
<GridPane HBox.hgrow="ALWAYS">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="NEVER"/>
|
||||
|
Loading…
x
Reference in New Issue
Block a user