fix(multiplayer): wrong port.

This commit is contained in:
huanghongxun 2021-09-27 22:18:48 +08:00
parent a58de31d4b
commit 8a3cdfb14c
9 changed files with 125 additions and 44 deletions

View File

@ -67,7 +67,6 @@ import java.util.function.Consumer;
import java.util.logging.Level;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
@ -489,6 +488,10 @@ public final class LauncherHelper {
}
}
if (!suggested) {
future.complete(javaVersion);
}
return Task.fromCompletableFuture(future);
}).thenAcceptAsync(Schedulers.javafx(), javaVersion -> {
if (javaVersion == null) {

View File

@ -22,6 +22,7 @@ import com.google.gson.annotations.JsonAdapter;
import javafx.beans.InvalidationListener;
import javafx.beans.property.*;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
@ -39,6 +40,8 @@ import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.stream.Collectors;
import static com.jfoenix.concurrency.JFXUtilities.runInFX;
/**
*
* @author huangyuhui
@ -590,10 +593,12 @@ public final class VersionSetting implements Cloneable {
}
public Task<JavaVersion> getJavaVersion(VersionNumber gameVersion, Version version, boolean checkJava) {
return Task.supplyAsync(() -> {
return Task.runAsync(Schedulers.javafx(), () -> {
if (StringUtils.isBlank(getJava())) {
setJava(StringUtils.isBlank(getJavaDir()) ? "Default" : "Custom");
}
}).thenSupplyAsync(() -> {
try {
if (StringUtils.isBlank(getJava()))
setJava(StringUtils.isBlank(getJavaDir()) ? "Default" : "Custom");
if ("Default".equals(getJava())) {
return JavaVersion.fromCurrentEnvironment();
} else if (isJavaAutoSelected()) {
@ -612,7 +617,9 @@ public final class VersionSetting implements Cloneable {
.filter(java -> java.getVersion().equals(getJava()))
.collect(Collectors.toList());
if (matchedJava.isEmpty()) {
setJava("Default");
runInFX(() -> {
setJava("Default");
});
return JavaVersion.fromCurrentEnvironment();
} else {
return matchedJava.stream()

View File

@ -32,6 +32,8 @@ public class MultiplayerClient extends Thread {
private final String id;
private final int port;
private int gamePort;
private final EventManager<ConnectedEvent> onConnected = new EventManager<>();
private final EventManager<Event> onDisconnected = new EventManager<>();
@ -43,6 +45,14 @@ public class MultiplayerClient extends Thread {
setDaemon(true);
}
public void setGamePort(int gamePort) {
this.gamePort = gamePort;
}
public int getGamePort() {
return gamePort;
}
public EventManager<ConnectedEvent> onConnected() {
return onConnected;
}
@ -53,6 +63,7 @@ public class MultiplayerClient extends Thread {
@Override
public void run() {
LOG.info("Connecting to 127.0.0.1:" + port);
try (Socket socket = new Socket(InetAddress.getLoopbackAddress(), port);
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) {
@ -68,6 +79,7 @@ public class MultiplayerClient extends Thread {
}
MultiplayerServer.JoinResponse response = JsonUtils.fromNonNullJson(line, MultiplayerServer.JoinResponse.class);
setGamePort(response.getPort());
onConnected.fireEvent(new ConnectedEvent(this, response.getPort()));
LOG.fine("Received join response with port " + response.getPort());
@ -85,6 +97,7 @@ public class MultiplayerClient extends Thread {
} catch (IOException | JsonParseException e) {
e.printStackTrace();
} finally {
LOG.info("Lost connection to 127.0.0.1:" + port);
onDisconnected.fireEvent(new Event(this));
}
}

View File

@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui.multiplayer;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager;
@ -62,6 +63,9 @@ public final class MultiplayerManager {
static final String CATO_VERSION = "1.0.9";
private static final String CATO_PATH = getCatoPath();
private static final String REMOTE_ADDRESS = "127.0.0.1";
private static final String LOCAL_ADDRESS = "127.0.0.1";
private MultiplayerManager() {
}
@ -96,8 +100,8 @@ public final class MultiplayerManager {
String[] commands = new String[]{exe.toString(),
"--token", StringUtils.isBlank(token) ? "new" : token,
"--id", peer,
"--local", String.format("0.0.0.0:%d", localPort),
"--remote", String.format("0.0.0.0:%d", remotePort)};
"--local", String.format("%s:%d", LOCAL_ADDRESS, localPort),
"--remote", String.format("%s:%d", REMOTE_ADDRESS, remotePort)};
Process process;
try {
process = new ProcessBuilder()
@ -129,7 +133,7 @@ public final class MultiplayerManager {
client.onConnected().register(connectedEvent -> {
try {
int port = findAvailablePort();
writer.write(String.format("net add %s 0.0.0.0:%d 0.0.0.0:%d p2p\n", peer, port, connectedEvent.getPort()));
writer.write(String.format("net add %s %s:%d %s:%d p2p\n", peer, LOCAL_ADDRESS, port, REMOTE_ADDRESS, connectedEvent.getPort()));
future.complete(session);
} catch (IOException e) {
future.completeExceptionally(e);
@ -142,18 +146,18 @@ public final class MultiplayerManager {
});
}
public static CatoSession createSession(String token, String sessionName, int port) throws IOException {
public static CatoSession createSession(String token, String sessionName, int gamePort) throws IOException {
Path exe = getCatoExecutable();
if (!Files.isRegularFile(exe)) {
throw new IllegalStateException("Cato file not found");
}
MultiplayerServer server = new MultiplayerServer(port);
MultiplayerServer server = new MultiplayerServer(gamePort);
server.startServer();
String[] commands = new String[]{exe.toString(),
"--token", StringUtils.isBlank(token) ? "new" : token,
"--allows", String.format("0.0.0.0:%d/0.0.0.0:%d", port, server.getPort())};
"--allows", String.format("%s:%d/%s:%d", REMOTE_ADDRESS, server.getPort(), REMOTE_ADDRESS, gamePort)};
Process process = new ProcessBuilder()
.command(commands)
.start();
@ -291,11 +295,11 @@ public final class MultiplayerManager {
return id;
}
public String generateInvitationCode(int gamePort, int serverPort) {
public String generateInvitationCode(int serverPort) {
if (id == null) {
throw new IllegalStateException("id not generated");
}
String json = JsonUtils.GSON.toJson(new Invitation(CATO_VERSION, id, name, gamePort, serverPort));
String json = JsonUtils.GSON.toJson(new Invitation(CATO_VERSION, id, name, serverPort));
return new String(Base64.getEncoder().encode(json.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
}
@ -312,7 +316,7 @@ public final class MultiplayerManager {
}
private static final Pattern TEMP_TOKEN_PATTERN = Pattern.compile("id\\(mix(?<id>\\w+)\\)");
private static final Pattern PEER_CONNECTED_PATTERN = Pattern.compile("Peer connected");
private static final Pattern PEER_CONNECTED_PATTERN = Pattern.compile("Connection established");
private static final Pattern LOG_PATTERN = Pattern.compile("(\\[\\d+])\\s+(\\w+)\\s+(\\w+-{0,1}\\w+):\\s(.*)");
}
@ -353,17 +357,18 @@ public final class MultiplayerManager {
}
public static class Invitation {
@SerializedName("v")
private final String version;
private final String id;
@SerializedName("n")
private final String sessionName;
private final int gamePort;
@SerializedName("p")
private final int channelPort;
public Invitation(String version, String id, String sessionName, int gamePort, int channelPort) {
public Invitation(String version, String id, String sessionName, int channelPort) {
this.version = version;
this.id = id;
this.sessionName = sessionName;
this.gamePort = gamePort;
this.channelPort = channelPort;
}
@ -379,10 +384,6 @@ public final class MultiplayerManager {
return sessionName;
}
public int getGamePort() {
return gamePort;
}
public int getChannelPort() {
return channelPort;
}

View File

@ -21,6 +21,8 @@ import de.javawi.jstun.test.DiscoveryInfo;
import de.javawi.jstun.test.DiscoveryTest;
import javafx.application.Platform;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import org.jackhuang.hmcl.event.Event;
@ -49,8 +51,9 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
private final ObjectProperty<MultiplayerManager.State> multiplayerState = new SimpleObjectProperty<>(MultiplayerManager.State.DISCONNECTED);
private final ReadOnlyStringWrapper token = new ReadOnlyStringWrapper();
private final ReadOnlyObjectWrapper<DiscoveryInfo> natState = new ReadOnlyObjectWrapper<>();
private final ReadOnlyIntegerWrapper port = new ReadOnlyIntegerWrapper(-1);
private final ReadOnlyIntegerWrapper gamePort = new ReadOnlyIntegerWrapper(-1);
private final ReadOnlyObjectWrapper<MultiplayerManager.CatoSession> session = new ReadOnlyObjectWrapper<>();
private final ObservableList<MultiplayerServer.CatoClient> clients = FXCollections.observableArrayList();
private Consumer<MultiplayerManager.CatoExitEvent> onExit;
private Consumer<MultiplayerManager.CatoIdEvent> onIdGenerated;
@ -70,6 +73,10 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
return new MultiplayerPageSkin(this);
}
public ObservableList<MultiplayerServer.CatoClient> getClients() {
return clients;
}
public MultiplayerManager.State getMultiplayerState() {
return multiplayerState.get();
}
@ -98,12 +105,12 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
return token.getReadOnlyProperty();
}
public int getPort() {
return port.get();
public int getGamePort() {
return gamePort.get();
}
public ReadOnlyIntegerProperty portProperty() {
return port.getReadOnlyProperty();
public ReadOnlyIntegerProperty gamePortProperty() {
return gamePort.getReadOnlyProperty();
}
public MultiplayerManager.CatoSession getSession() {
@ -158,11 +165,11 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
}
public void copyInvitationCode() {
if (getSession() == null || !getSession().isReady() || port.get() < 0 || getMultiplayerState() != MultiplayerManager.State.MASTER) {
if (getSession() == null || !getSession().isReady() || gamePort.get() < 0 || getMultiplayerState() != MultiplayerManager.State.MASTER) {
throw new IllegalStateException("CatoSession not ready");
}
FXUtils.copyText(getSession().generateInvitationCode(port.get(), 0));
FXUtils.copyText(getSession().generateInvitationCode(getSession().getServer().getPort()));
}
public void createRoom() {
@ -171,16 +178,22 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
}
Controllers.dialog(new CreateMultiplayerRoomDialog((result, resolve, reject) -> {
int port = result.getAd();
int gamePort = result.getAd();
try {
initCatoSession(MultiplayerManager.createSession(config().getMultiplayerToken(), result.getMotd(), port));
MultiplayerManager.CatoSession session = MultiplayerManager.createSession(config().getMultiplayerToken(), result.getMotd(), gamePort);
session.getServer().onClientAdded().register(event -> {
runInFX(() -> {
clients.add(event);
});
});
initCatoSession(session);
} catch (Exception e) {
LOG.log(Level.WARNING, "Failed to create session", e);
reject.accept(i18n("multiplayer.session.create.error"));
return;
}
this.port.set(port);
this.gamePort.set(gamePort);
setMultiplayerState(MultiplayerManager.State.CONNECTING);
resolve.run();
}));
@ -202,7 +215,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
return;
}
int localPort;
int localPort; // invitation channel
try {
localPort = MultiplayerManager.findAvailablePort();
} catch (Exception e) {
@ -211,7 +224,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
}
try {
MultiplayerManager.joinSession(config().getMultiplayerToken(), invitation.getVersion(), invitation.getSessionName(), invitation.getId(), invitation.getGamePort(), localPort)
MultiplayerManager.joinSession(config().getMultiplayerToken(), invitation.getVersion(), invitation.getSessionName(), invitation.getId(), invitation.getChannelPort(), localPort)
.thenAcceptAsync(session -> {
initCatoSession(session);
@ -222,7 +235,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
});
});
port.set(localPort);
gamePort.set(session.getClient().getGamePort());
setMultiplayerState(MultiplayerManager.State.CONNECTING);
resolve.run();
}, Platform::runLater).exceptionally(throwable -> {
@ -270,6 +283,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
onIdGenerated = session.onIdGenerated().registerWeak(this::onCatoIdGenerated);
onPeerConnected = session.onPeerConnected().registerWeak(this::onCatoPeerConnected);
this.clients.clear();
this.session.set(session);
});
}
@ -282,7 +296,7 @@ public class MultiplayerPage extends Control implements DecoratorPage, PageAware
private void clearCatoSession() {
this.session.set(null);
this.token.set(null);
this.port.set(-1);
this.gamePort.set(-1);
this.multiplayerState.set(MultiplayerManager.State.DISCONNECTED);
}

View File

@ -21,8 +21,10 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import de.javawi.jstun.test.DiscoveryInfo;
import javafx.beans.binding.Bindings;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SkinBase;
@ -38,6 +40,7 @@ import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
@ -45,6 +48,8 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class MultiplayerPageSkin extends SkinBase<MultiplayerPage> {
private ObservableList<Node> clients;
/**
* Constructor for all SkinBase instances.
*
@ -174,9 +179,14 @@ public class MultiplayerPageSkin extends SkinBase<MultiplayerPage> {
Label label = new Label(i18n("multiplayer.state.master"));
label.textProperty().bind(Bindings.createStringBinding(() ->
i18n("multiplayer.state.master", control.getSession() == null ? "" : control.getSession().getName(), control.getPort()),
control.portProperty(), control.sessionProperty()));
masterPane.getChildren().setAll(masterHintPane, label);
i18n("multiplayer.state.master", control.getSession() == null ? "" : control.getSession().getName(), control.getGamePort()),
control.gamePortProperty(), control.sessionProperty()));
VBox clientsPane = new VBox(8);
clients = MappedObservableList.create(control.getClients(), client -> new ClientItem(client));
Bindings.bindContent(clientsPane.getChildren(), clients);
masterPane.getChildren().setAll(masterHintPane, label, clientsPane);
}
BorderPane slavePane = new BorderPane();
@ -187,13 +197,13 @@ public class MultiplayerPageSkin extends SkinBase<MultiplayerPage> {
Label label = new Label();
label.textProperty().bind(Bindings.createStringBinding(() ->
i18n("multiplayer.state.slave", control.getSession() == null ? "" : control.getSession().getName(), "0.0.0.0:" + control.getPort()),
control.sessionProperty(), control.portProperty()));
i18n("multiplayer.state.slave", control.getSession() == null ? "" : control.getSession().getName(), "0.0.0.0:" + control.getGamePort()),
control.sessionProperty(), control.gamePortProperty()));
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
slavePane.setCenter(label);
JFXButton copyButton = new JFXButton(i18n("multiplayer.state.slave.copy"));
copyButton.setOnAction(e -> FXUtils.copyText("0.0.0.0:" + control.getPort()));
copyButton.setOnAction(e -> FXUtils.copyText("0.0.0.0:" + control.getGamePort()));
slavePane.setRight(copyButton);
}
@ -289,4 +299,15 @@ public class MultiplayerPageSkin extends SkinBase<MultiplayerPage> {
return i18n("multiplayer.nat.type.unknown");
}
}
private static class ClientItem extends StackPane {
ClientItem(MultiplayerServer.CatoClient client) {
BorderPane pane = new BorderPane();
pane.setLeft(new Label(client.getUsername()));
RipplerContainer container = new RipplerContainer(pane);
getChildren().setAll(container);
getStyleClass().add("md-list-cell");
}
}
}

View File

@ -18,6 +18,8 @@
package org.jackhuang.hmcl.ui.multiplayer;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.gson.JsonSubtype;
import org.jackhuang.hmcl.util.gson.JsonType;
@ -33,6 +35,8 @@ public class MultiplayerServer extends Thread {
private ServerSocket socket;
private final int gamePort;
private final EventManager<CatoClient> onClientAdded = new EventManager<CatoClient>();
public MultiplayerServer(int gamePort) {
this.gamePort = gamePort;
@ -40,6 +44,10 @@ public class MultiplayerServer extends Thread {
setDaemon(true);
}
public EventManager<CatoClient> onClientAdded() {
return onClientAdded;
}
public void startServer() throws IOException {
if (socket != null) {
throw new IllegalStateException("MultiplayerServer already started");
@ -59,6 +67,7 @@ public class MultiplayerServer extends Thread {
@Override
public void run() {
LOG.info("Multiplayer Server listening 127.0.0.1:" + socket.getLocalPort());
try {
while (!isInterrupted()) {
Socket clientSocket = socket.accept();
@ -117,6 +126,8 @@ public class MultiplayerServer extends Thread {
LOG.fine("Received join request with clientVersion=" + clientVersion + ", id=" + username);
writer.write(JsonUtils.GSON.toJson(new JoinResponse(server.gamePort)));
server.onClientAdded.fireEvent(new CatoClient(server, username));
}
}
@ -171,4 +182,17 @@ public class MultiplayerServer extends Thread {
return timestamp;
}
}
public static class CatoClient extends Event {
private final String username;
public CatoClient(Object source, String username) {
super(source);
this.username = username;
}
public String getUsername() {
return username;
}
}
}

View File

@ -770,7 +770,6 @@ settings.game.java_directory=Java 路径
settings.game.java_directory.auto=自动选择合适的 Java
settings.game.java_directory.bit=%s 位
settings.game.java_directory.choose=选择 Java 路径
settings.game.
settings.game.management=管理
settings.game.working_directory=版本隔离(修改后请自行移动相关游戏文件,如存档模组配置等)
settings.game.working_directory.choose=选择运行路径

View File

@ -20,8 +20,7 @@ package org.jackhuang.hmcl.util;
import java.util.Comparator;
import java.util.Objects;
public class Range<T> {
public final class Range<T> {
@SuppressWarnings({"rawtypes", "unchecked"})
private enum ComparableComparator implements Comparator {