feat(multiplayer): WIP: update cato 1.1.0
This commit is contained in:
@@ -52,17 +52,18 @@ import java.util.logging.Level;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.util.Lang.wrap;
|
||||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cato Management.
|
* Cato Management.
|
||||||
*/
|
*/
|
||||||
public final class MultiplayerManager {
|
public final class MultiplayerManager {
|
||||||
private static final String CATO_DOWNLOAD_URL = "https://files.huangyuhui.net/maven/";
|
static final String CATO_VERSION = "1.1.0";
|
||||||
static final String CATO_VERSION = "1.0.c";
|
// private static final String CATO_DOWNLOAD_URL = "https://files.huangyuhui.net/maven/cato/cato/" + MultiplayerManager.CATO_VERSION;
|
||||||
|
private static final String CATO_DOWNLOAD_URL = "https://codechina.csdn.net/to/ioi_bin/-/raw/e2d1b04805764e77f7c17c2e5a64d0ccb1831e16/client/";
|
||||||
private static final String CATO_PATH = getCatoPath();
|
private static final String CATO_PATH = getCatoPath();
|
||||||
public static final int CATO_AGREEMENT_VERSION = 2;
|
public static final int CATO_AGREEMENT_VERSION = 2;
|
||||||
|
|
||||||
private static final String REMOTE_ADDRESS = "127.0.0.1";
|
private static final String REMOTE_ADDRESS = "127.0.0.1";
|
||||||
private static final String LOCAL_ADDRESS = "0.0.0.0";
|
private static final String LOCAL_ADDRESS = "0.0.0.0";
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ public final class MultiplayerManager {
|
|||||||
|
|
||||||
public static Task<Void> downloadCato() {
|
public static Task<Void> downloadCato() {
|
||||||
return new FileDownloadTask(
|
return new FileDownloadTask(
|
||||||
NetworkUtils.toURL(CATO_DOWNLOAD_URL + CATO_PATH),
|
NetworkUtils.toURL(CATO_DOWNLOAD_URL + getCatoFileName()),
|
||||||
getCatoExecutable().toFile()
|
getCatoExecutable().toFile()
|
||||||
).thenRunAsync(() -> {
|
).thenRunAsync(() -> {
|
||||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||||
@@ -86,14 +87,8 @@ public final class MultiplayerManager {
|
|||||||
return Metadata.HMCL_DIRECTORY.resolve("libraries").resolve(CATO_PATH);
|
return Metadata.HMCL_DIRECTORY.resolve("libraries").resolve(CATO_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompletableFuture<CatoSession> joinSession(String token, String version, String sessionName, String peer, Mode mode, int remotePort, int localPort, JoinSessionHandler handler) throws IncompatibleCatoVersionException {
|
private static CompletableFuture<CatoSession> startCato(String sessionName, String token, State state) {
|
||||||
if (!CATO_VERSION.equals(version)) {
|
return CompletableFuture.completedFuture(null).thenApplyAsync(unused -> {
|
||||||
throw new IncompatibleCatoVersionException(version, CATO_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.info(String.format("Joining session (token=%s,version=%s,sessionName=%s,peer=%s,mode=%s,remotePort=%d,localPort=%d)", token, version, sessionName, peer, mode, remotePort, localPort));
|
|
||||||
|
|
||||||
return CompletableFuture.completedFuture(null).thenComposeAsync(unused -> {
|
|
||||||
Path exe = getCatoExecutable();
|
Path exe = getCatoExecutable();
|
||||||
if (!Files.isRegularFile(exe)) {
|
if (!Files.isRegularFile(exe)) {
|
||||||
throw new CatoNotExistsException(exe);
|
throw new CatoNotExistsException(exe);
|
||||||
@@ -103,12 +98,7 @@ public final class MultiplayerManager {
|
|||||||
throw new CatoAlreadyStartedException();
|
throw new CatoAlreadyStartedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] commands = new String[]{exe.toString(),
|
String[] commands = new String[]{exe.toString(), "--token", StringUtils.isBlank(token) ? "new" : token};
|
||||||
"--token", StringUtils.isBlank(token) ? "new" : token,
|
|
||||||
"--id", peer,
|
|
||||||
"--local", String.format("%s:%d", LOCAL_ADDRESS, localPort),
|
|
||||||
"--remote", String.format("%s:%d", REMOTE_ADDRESS, remotePort),
|
|
||||||
"--mode", mode.getName()};
|
|
||||||
Process process;
|
Process process;
|
||||||
try {
|
try {
|
||||||
process = new ProcessBuilder()
|
process = new ProcessBuilder()
|
||||||
@@ -118,11 +108,21 @@ public final class MultiplayerManager {
|
|||||||
throw new UncheckedIOException(e);
|
throw new UncheckedIOException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
CatoSession session = new CatoSession(sessionName, State.SLAVE, process, Arrays.asList(commands));
|
return new CatoSession(sessionName, state, process, Arrays.asList(commands));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompletableFuture<CatoSession> joinSession(String token, String version, String sessionName, String peer, Mode mode, int remotePort, int localPort, JoinSessionHandler handler) throws IncompatibleCatoVersionException {
|
||||||
|
if (!CATO_VERSION.equals(version)) {
|
||||||
|
throw new IncompatibleCatoVersionException(version, CATO_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info(String.format("Joining session (token=%s,version=%s,sessionName=%s,peer=%s,mode=%s,remotePort=%d,localPort=%d)", token, version, sessionName, peer, mode, remotePort, localPort));
|
||||||
|
|
||||||
|
return startCato(sessionName, token, State.SLAVE).thenComposeAsync(wrap(session -> {
|
||||||
CompletableFuture<CatoSession> future = new CompletableFuture<>();
|
CompletableFuture<CatoSession> future = new CompletableFuture<>();
|
||||||
|
|
||||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8));
|
session.forwardPort(peer, LOCAL_ADDRESS, localPort, REMOTE_ADDRESS, remotePort, mode);
|
||||||
|
|
||||||
Consumer<CatoExitEvent> onExit = event -> {
|
Consumer<CatoExitEvent> onExit = event -> {
|
||||||
boolean ready = session.isReady();
|
boolean ready = session.isReady();
|
||||||
@@ -137,14 +137,6 @@ public final class MultiplayerManager {
|
|||||||
};
|
};
|
||||||
session.onExit.register(onExit);
|
session.onExit.register(onExit);
|
||||||
|
|
||||||
session.onExit().register(() -> {
|
|
||||||
try {
|
|
||||||
writer.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.log(Level.WARNING, "Failed to close cato session stdin writer", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
TimerTask peerConnectionTimeoutTask = Lang.setTimeout(() -> {
|
TimerTask peerConnectionTimeoutTask = Lang.setTimeout(() -> {
|
||||||
future.completeExceptionally(new PeerConnectionTimeoutException());
|
future.completeExceptionally(new PeerConnectionTimeoutException());
|
||||||
session.stop();
|
session.stop();
|
||||||
@@ -165,13 +157,9 @@ public final class MultiplayerManager {
|
|||||||
client.onConnected().register(connectedEvent -> {
|
client.onConnected().register(connectedEvent -> {
|
||||||
try {
|
try {
|
||||||
int port = findAvailablePort();
|
int port = findAvailablePort();
|
||||||
String command = String.format("net add %s %s:%d %s:%d %s", peer, LOCAL_ADDRESS, port, REMOTE_ADDRESS, connectedEvent.getPort(), mode.getName());
|
session.forwardPort(peer, LOCAL_ADDRESS, port, REMOTE_ADDRESS, connectedEvent.getPort(), mode);
|
||||||
LOG.info("Invoking cato: " + command);
|
|
||||||
session.addRelatedThread(Lang.thread(new LocalServerBroadcaster(port, session), "LocalServerBroadcaster", true));
|
session.addRelatedThread(Lang.thread(new LocalServerBroadcaster(port, session), "LocalServerBroadcaster", true));
|
||||||
client.setGamePort(port);
|
client.setGamePort(port);
|
||||||
writer.write(command);
|
|
||||||
writer.newLine();
|
|
||||||
writer.flush();
|
|
||||||
session.onExit.unregister(onExit);
|
session.onExit.unregister(onExit);
|
||||||
future.complete(session);
|
future.complete(session);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -200,45 +188,21 @@ public final class MultiplayerManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CompletableFuture<CatoSession> createSession(String token, String sessionName, int gamePort, boolean allowAllJoinRequests) {
|
public static CompletableFuture<CatoSession> createSession(String token, String sessionName, int gamePort, boolean allowAllJoinRequests) {
|
||||||
return CompletableFuture.completedFuture(null).thenComposeAsync(unused -> {
|
LOG.info(String.format("Creating session (token=%s,sessionName=%s,gamePort=%d)", token, sessionName, gamePort));
|
||||||
Path exe = getCatoExecutable();
|
|
||||||
if (!Files.isRegularFile(exe)) {
|
|
||||||
throw new CatoNotExistsException(exe);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isPortAvailable(3478)) {
|
|
||||||
throw new CatoAlreadyStartedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.info(String.format("Creating session (token=%s,sessionName=%s,gamePort=%d)", token, sessionName, gamePort));
|
|
||||||
|
|
||||||
MultiplayerServer server;
|
|
||||||
try {
|
|
||||||
server = new MultiplayerServer(gamePort, allowAllJoinRequests);
|
|
||||||
server.startServer();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] commands = new String[]{exe.toString(),
|
|
||||||
"--token", StringUtils.isBlank(token) ? "new" : token,
|
|
||||||
"--allows", String.format("%s:%d/%s:%d", REMOTE_ADDRESS, server.getPort(), REMOTE_ADDRESS, gamePort)};
|
|
||||||
Process process;
|
|
||||||
try {
|
|
||||||
process = new ProcessBuilder()
|
|
||||||
.command(commands)
|
|
||||||
.start();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new UncheckedIOException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return startCato(sessionName, token, State.MASTER).thenComposeAsync(wrap(session -> {
|
||||||
CompletableFuture<CatoSession> future = new CompletableFuture<>();
|
CompletableFuture<CatoSession> future = new CompletableFuture<>();
|
||||||
|
|
||||||
CatoSession session = new CatoSession(sessionName, State.MASTER, process, Arrays.asList(commands));
|
MultiplayerServer server = new MultiplayerServer(gamePort, allowAllJoinRequests);
|
||||||
|
server.startServer();
|
||||||
|
|
||||||
|
session.allowForwardingAddress(REMOTE_ADDRESS, server.getPort());
|
||||||
|
session.allowForwardingAddress(REMOTE_ADDRESS, gamePort);
|
||||||
|
session.showAllowedAddress();
|
||||||
|
|
||||||
Consumer<CatoExitEvent> onExit = event -> {
|
Consumer<CatoExitEvent> onExit = event -> {
|
||||||
boolean ready = session.isReady();
|
boolean ready = session.isReady();
|
||||||
@@ -268,7 +232,7 @@ public final class MultiplayerManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return future;
|
return future;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Invitation parseInvitationCode(String invitationCode) throws JsonParseException {
|
public static Invitation parseInvitationCode(String invitationCode) throws JsonParseException {
|
||||||
@@ -290,28 +254,33 @@ public final class MultiplayerManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getCatoPath() {
|
private static String getCatoFileName() {
|
||||||
switch (OperatingSystem.CURRENT_OS) {
|
switch (OperatingSystem.CURRENT_OS) {
|
||||||
case WINDOWS:
|
case WINDOWS:
|
||||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64
|
if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
||||||
|| (Architecture.SYSTEM_ARCH == Architecture.ARM64 && OperatingSystem.SYSTEM_BUILD_NUMBER >= 21277)) {
|
return "cato-client-windows-amd64.exe";
|
||||||
return "cato/cato/" + MultiplayerManager.CATO_VERSION + "/cato-windows-amd64.exe";
|
} else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||||
|
return "cato-client-windows-arm64.exe";
|
||||||
|
} else if (Architecture.SYSTEM_ARCH == Architecture.X86) {
|
||||||
|
return "cato-client-windows-i386.exe";
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
case OSX:
|
case OSX:
|
||||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
||||||
return "cato/cato/" + MultiplayerManager.CATO_VERSION + "/cato-darwin-amd64";
|
return "cato-client-darwin-amd64";
|
||||||
} else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
} else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||||
return "cato/cato/" + MultiplayerManager.CATO_VERSION + "/cato-darwin-arm64";
|
return "cato-client-darwin-arm64";
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
case LINUX:
|
case LINUX:
|
||||||
if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
if (Architecture.SYSTEM_ARCH == Architecture.X86_64) {
|
||||||
return "cato/cato/" + MultiplayerManager.CATO_VERSION + "/cato-linux-amd64";
|
return "cato-client-linux-amd64";
|
||||||
} else if (Architecture.SYSTEM_ARCH == Architecture.ARM32 || Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
} else if (Architecture.SYSTEM_ARCH == Architecture.ARM32) {
|
||||||
return "cato/cato/" + MultiplayerManager.CATO_VERSION + "/cato-linux-arm7";
|
return "cato-client-linux-arm7";
|
||||||
|
} else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) {
|
||||||
|
return "cato-client-linux-arm64";
|
||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -320,6 +289,12 @@ public final class MultiplayerManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getCatoPath() {
|
||||||
|
String name = getCatoFileName();
|
||||||
|
if (StringUtils.isBlank(name)) return "";
|
||||||
|
return "cato/cato/" + MultiplayerManager.CATO_VERSION + "/" + name;
|
||||||
|
}
|
||||||
|
|
||||||
public static class CatoSession extends ManagedProcess {
|
public static class CatoSession extends ManagedProcess {
|
||||||
private final EventManager<CatoExitEvent> onExit = new EventManager<>();
|
private final EventManager<CatoExitEvent> onExit = new EventManager<>();
|
||||||
private final EventManager<CatoIdEvent> onIdGenerated = new EventManager<>();
|
private final EventManager<CatoIdEvent> onIdGenerated = new EventManager<>();
|
||||||
@@ -331,6 +306,7 @@ public final class MultiplayerManager {
|
|||||||
private boolean peerConnected = false;
|
private boolean peerConnected = false;
|
||||||
private MultiplayerClient client;
|
private MultiplayerClient client;
|
||||||
private MultiplayerServer server;
|
private MultiplayerServer server;
|
||||||
|
private final BufferedWriter writer;
|
||||||
|
|
||||||
CatoSession(String name, State type, Process process, List<String> commands) {
|
CatoSession(String name, State type, Process process, List<String> commands) {
|
||||||
super(process, commands);
|
super(process, commands);
|
||||||
@@ -344,6 +320,8 @@ public final class MultiplayerManager {
|
|||||||
addRelatedThread(Lang.thread(this::waitFor, "CatoExitWaiter", true));
|
addRelatedThread(Lang.thread(this::waitFor, "CatoExitWaiter", true));
|
||||||
addRelatedThread(Lang.thread(new StreamPump(process.getInputStream(), this::checkCatoLog), "CatoInputStreamPump", true));
|
addRelatedThread(Lang.thread(new StreamPump(process.getInputStream(), this::checkCatoLog), "CatoInputStreamPump", true));
|
||||||
addRelatedThread(Lang.thread(new StreamPump(process.getErrorStream(), this::checkCatoLog), "CatoErrorStreamPump", true));
|
addRelatedThread(Lang.thread(new StreamPump(process.getErrorStream(), this::checkCatoLog), "CatoErrorStreamPump", true));
|
||||||
|
|
||||||
|
writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiplayerClient getClient() {
|
public MultiplayerClient getClient() {
|
||||||
@@ -390,6 +368,12 @@ public final class MultiplayerManager {
|
|||||||
onExit.fireEvent(new CatoExitEvent(this, exitCode));
|
onExit.fireEvent(new CatoExitEvent(this, exitCode));
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
onExit.fireEvent(new CatoExitEvent(this, CatoExitEvent.EXIT_CODE_INTERRUPTED));
|
onExit.fireEvent(new CatoExitEvent(this, CatoExitEvent.EXIT_CODE_INTERRUPTED));
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
writer.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to close cato stdin writer", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
destroyRelatedThreads();
|
destroyRelatedThreads();
|
||||||
}
|
}
|
||||||
@@ -419,6 +403,25 @@ public final class MultiplayerManager {
|
|||||||
return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8));
|
return Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void invokeCommand(String command) throws IOException {
|
||||||
|
LOG.info("Invoking cato: " + command);
|
||||||
|
writer.write(command);
|
||||||
|
writer.newLine();
|
||||||
|
writer.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forwardPort(String peerId, String localAddress, int localPort, String remoteAddress, int remotePort, Mode mode) throws IOException {
|
||||||
|
invokeCommand(String.format("net add %s %s:%d %s:%d %s", peerId, localAddress, localPort, remoteAddress, remotePort, mode.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void allowForwardingAddress(String address, int port) throws IOException {
|
||||||
|
invokeCommand(String.format("ufw net open %s:%d", address, port));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showAllowedAddress() throws IOException {
|
||||||
|
invokeCommand("ufw net whitelist");
|
||||||
|
}
|
||||||
|
|
||||||
public EventManager<CatoExitEvent> onExit() {
|
public EventManager<CatoExitEvent> onExit() {
|
||||||
return onExit;
|
return onExit;
|
||||||
}
|
}
|
||||||
@@ -529,7 +532,7 @@ public final class MultiplayerManager {
|
|||||||
|
|
||||||
public enum Mode {
|
public enum Mode {
|
||||||
P2P,
|
P2P,
|
||||||
RELAY;
|
BRIDGE;
|
||||||
|
|
||||||
String getName() {
|
String getName() {
|
||||||
return name().toLowerCase(Locale.ROOT);
|
return name().toLowerCase(Locale.ROOT);
|
||||||
|
|||||||
@@ -238,9 +238,9 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
|||||||
initCatoSession(session);
|
initCatoSession(session);
|
||||||
|
|
||||||
this.gamePort.set(gamePort);
|
this.gamePort.set(gamePort);
|
||||||
setMultiplayerState(MultiplayerManager.State.CONNECTING);
|
setMultiplayerState(MultiplayerManager.State.MASTER);
|
||||||
resolve.run();
|
resolve.run();
|
||||||
})
|
}, Platform::runLater)
|
||||||
.exceptionally(throwable -> {
|
.exceptionally(throwable -> {
|
||||||
reject.accept(localizeCreateErrorMessage(throwable, isStaticToken));
|
reject.accept(localizeCreateErrorMessage(throwable, isStaticToken));
|
||||||
return null;
|
return null;
|
||||||
@@ -255,6 +255,7 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
|||||||
|
|
||||||
Controllers.prompt(new PromptDialogPane.Builder(i18n("multiplayer.session.join"), (result, resolve, reject) -> {
|
Controllers.prompt(new PromptDialogPane.Builder(i18n("multiplayer.session.join"), (result, resolve, reject) -> {
|
||||||
PromptDialogPane.Builder.HintQuestion hintQuestion = (PromptDialogPane.Builder.HintQuestion) result.get(0);
|
PromptDialogPane.Builder.HintQuestion hintQuestion = (PromptDialogPane.Builder.HintQuestion) result.get(0);
|
||||||
|
boolean isStaticToken = StringUtils.isNotBlank(globalConfig().getMultiplayerToken());
|
||||||
|
|
||||||
String invitationCode = ((PromptDialogPane.Builder.StringQuestion) result.get(1)).getValue();
|
String invitationCode = ((PromptDialogPane.Builder.StringQuestion) result.get(1)).getValue();
|
||||||
MultiplayerManager.Invitation invitation;
|
MultiplayerManager.Invitation invitation;
|
||||||
@@ -280,8 +281,8 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
|||||||
invitation.getVersion(),
|
invitation.getVersion(),
|
||||||
invitation.getSessionName(),
|
invitation.getSessionName(),
|
||||||
invitation.getId(),
|
invitation.getId(),
|
||||||
globalConfig().isMultiplayerRelay() && StringUtils.isNotBlank(globalConfig().getMultiplayerToken())
|
globalConfig().isMultiplayerRelay() && (StringUtils.isNotBlank(globalConfig().getMultiplayerToken()) || StringUtils.isNotBlank(System.getProperty("hmcl.multiplayer.relay")))
|
||||||
? MultiplayerManager.Mode.RELAY
|
? MultiplayerManager.Mode.BRIDGE
|
||||||
: MultiplayerManager.Mode.P2P,
|
: MultiplayerManager.Mode.P2P,
|
||||||
invitation.getChannelPort(),
|
invitation.getChannelPort(),
|
||||||
localPort, new MultiplayerManager.JoinSessionHandler() {
|
localPort, new MultiplayerManager.JoinSessionHandler() {
|
||||||
@@ -318,7 +319,7 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
|||||||
resolve.run();
|
resolve.run();
|
||||||
}, Platform::runLater)
|
}, Platform::runLater)
|
||||||
.exceptionally(throwable -> {
|
.exceptionally(throwable -> {
|
||||||
reject.accept(localizeJoinErrorMessage(throwable));
|
reject.accept(localizeJoinErrorMessage(throwable, isStaticToken));
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
} catch (MultiplayerManager.IncompatibleCatoVersionException e) {
|
} catch (MultiplayerManager.IncompatibleCatoVersionException e) {
|
||||||
@@ -329,7 +330,7 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
|||||||
.addQuestion(new PromptDialogPane.Builder.StringQuestion(i18n("multiplayer.session.join.invitation_code"), "", new RequiredValidator())));
|
.addQuestion(new PromptDialogPane.Builder.StringQuestion(i18n("multiplayer.session.join.invitation_code"), "", new RequiredValidator())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String localizeErrorMessage(Throwable t, Function<Throwable, String> fallback) {
|
private String localizeErrorMessage(Throwable t, boolean isStaticToken, Function<Throwable, String> fallback) {
|
||||||
Throwable e = resolveException(t);
|
Throwable e = resolveException(t);
|
||||||
if (e instanceof CancellationException) {
|
if (e instanceof CancellationException) {
|
||||||
LOG.info("Connection rejected by the server");
|
LOG.info("Connection rejected by the server");
|
||||||
@@ -348,7 +349,11 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
|||||||
return i18n("multiplayer.session.join.error.connection");
|
return i18n("multiplayer.session.join.error.connection");
|
||||||
} else if (e instanceof MultiplayerManager.CatoExitTimeoutException) {
|
} else if (e instanceof MultiplayerManager.CatoExitTimeoutException) {
|
||||||
LOG.info("Cato failed to connect to main net");
|
LOG.info("Cato failed to connect to main net");
|
||||||
return i18n("multiplayer.exit.timeout");
|
if (isStaticToken) {
|
||||||
|
return i18n("multiplayer.exit.timeout.static_token");
|
||||||
|
} else {
|
||||||
|
return i18n("multiplayer.exit.timeout.dynamic_token");
|
||||||
|
}
|
||||||
} else if (e instanceof MultiplayerManager.CatoExitException) {
|
} else if (e instanceof MultiplayerManager.CatoExitException) {
|
||||||
LOG.info("Cato exited accidentally");
|
LOG.info("Cato exited accidentally");
|
||||||
if (!((MultiplayerManager.CatoExitException) e).isReady()) {
|
if (!((MultiplayerManager.CatoExitException) e).isReady()) {
|
||||||
@@ -362,7 +367,7 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String localizeCreateErrorMessage(Throwable t, boolean isStaticToken) {
|
private String localizeCreateErrorMessage(Throwable t, boolean isStaticToken) {
|
||||||
return localizeErrorMessage(t, e -> {
|
return localizeErrorMessage(t, isStaticToken, e -> {
|
||||||
LOG.log(Level.WARNING, "Failed to create session", e);
|
LOG.log(Level.WARNING, "Failed to create session", e);
|
||||||
if (isStaticToken) {
|
if (isStaticToken) {
|
||||||
return i18n("multiplayer.session.create.error.static_token") + e.getLocalizedMessage();
|
return i18n("multiplayer.session.create.error.static_token") + e.getLocalizedMessage();
|
||||||
@@ -372,8 +377,8 @@ public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorP
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private String localizeJoinErrorMessage(Throwable t) {
|
private String localizeJoinErrorMessage(Throwable t, boolean isStaticToken) {
|
||||||
return localizeErrorMessage(t, e -> {
|
return localizeErrorMessage(t, isStaticToken, e -> {
|
||||||
LOG.log(Level.WARNING, "Failed to join session", e);
|
LOG.log(Level.WARNING, "Failed to join session", e);
|
||||||
return i18n("multiplayer.session.join.error");
|
return i18n("multiplayer.session.join.error");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -647,7 +647,8 @@ multiplayer.download.success=Dependencies initialization succeeded
|
|||||||
multiplayer.download.unsupported=Current operating system or architecure is unsupported.
|
multiplayer.download.unsupported=Current operating system or architecure is unsupported.
|
||||||
multiplayer.exit.after_ready=Multiplayer session broken. cato exitcode %d
|
multiplayer.exit.after_ready=Multiplayer session broken. cato exitcode %d
|
||||||
multiplayer.exit.before_ready=Multiplayer session failed to create. cato exitcode %d
|
multiplayer.exit.before_ready=Multiplayer session failed to create. cato exitcode %d
|
||||||
multiplayer.exit.timeout=Failed to connect to multiplayer server.
|
multiplayer.exit.timeout.static_token=Failed to connect to multiplayer server. Please switch to dynamic token and try again.
|
||||||
|
multiplayer.exit.timeout.dynamic_token=Failed to connect to multiplayer server.
|
||||||
multiplayer.hint=Multiplayer functionality is experimental. Please give feedback.
|
multiplayer.hint=Multiplayer functionality is experimental. Please give feedback.
|
||||||
multiplayer.nat=Network Type Detection
|
multiplayer.nat=Network Type Detection
|
||||||
multiplayer.nat.hint=Network type detection will make it clear whether your network fulfills our requirement for multiplayer mode.
|
multiplayer.nat.hint=Network type detection will make it clear whether your network fulfills our requirement for multiplayer mode.
|
||||||
|
|||||||
@@ -647,7 +647,8 @@ multiplayer.download.success=多人聯機初始化完成
|
|||||||
multiplayer.download.unsupported=多人聯機依賴不支持當前系統或平台
|
multiplayer.download.unsupported=多人聯機依賴不支持當前系統或平台
|
||||||
multiplayer.exit.after_ready=多人聯機會話意外退出,退出碼 %d
|
multiplayer.exit.after_ready=多人聯機會話意外退出,退出碼 %d
|
||||||
multiplayer.exit.before_ready=多人聯機房間創建失敗,cato 退出碼 %d
|
multiplayer.exit.before_ready=多人聯機房間創建失敗,cato 退出碼 %d
|
||||||
multiplayer.exit.timeout=無法連接多人聯機服務,你可以在多人聯機頁面的回饋中回饋問題。
|
multiplayer.exit.timeout.static_token=無法連接多人聯機服務,請你切換動態 Token 重試。
|
||||||
|
multiplayer.exit.timeout.dynamic_token=無法連接多人聯機服務,你可以在多人聯機頁面的回饋中回饋問題。
|
||||||
multiplayer.hint=多人聯機功能處於實驗階段,如果有問題請回饋。
|
multiplayer.hint=多人聯機功能處於實驗階段,如果有問題請回饋。
|
||||||
multiplayer.nat=網路檢測
|
multiplayer.nat=網路檢測
|
||||||
multiplayer.nat.hint=執行網路檢測可以讓你更清楚你的網路狀況是否符合聯機功能的需求。不符合聯機功能運行條件的網路狀況將可能導致聯機失敗。
|
multiplayer.nat.hint=執行網路檢測可以讓你更清楚你的網路狀況是否符合聯機功能的需求。不符合聯機功能運行條件的網路狀況將可能導致聯機失敗。
|
||||||
|
|||||||
@@ -647,7 +647,8 @@ multiplayer.download.success=多人联机初始化完成
|
|||||||
multiplayer.download.unsupported=多人联机依赖不支持当前系统或平台
|
multiplayer.download.unsupported=多人联机依赖不支持当前系统或平台
|
||||||
multiplayer.exit.after_ready=多人联机会话意外退出,退出码 %d
|
multiplayer.exit.after_ready=多人联机会话意外退出,退出码 %d
|
||||||
multiplayer.exit.before_ready=多人联机房间创建失败,cato 退出码 %d
|
multiplayer.exit.before_ready=多人联机房间创建失败,cato 退出码 %d
|
||||||
multiplayer.exit.timeout=无法连接多人联机服务,你可以在多人联机页面的反馈中反馈问题。
|
multiplayer.exit.timeout.static_token=无法连接多人联机服务,请你切换动态 Token 重试。
|
||||||
|
multiplayer.exit.timeout.dynamic_token=无法连接多人联机服务,你可以在多人联机页面的反馈中反馈问题。
|
||||||
multiplayer.hint=多人联机功能处于实验阶段,如果有问题请反馈。
|
multiplayer.hint=多人联机功能处于实验阶段,如果有问题请反馈。
|
||||||
multiplayer.nat=网络检测
|
multiplayer.nat=网络检测
|
||||||
multiplayer.nat.hint=执行网络检测可以让你更清楚你的网络状况是否符合联机功能的需求。检测结果为差的网络可能导致联机失败。
|
multiplayer.nat.hint=执行网络检测可以让你更清楚你的网络状况是否符合联机功能的需求。检测结果为差的网络可能导致联机失败。
|
||||||
|
|||||||
Reference in New Issue
Block a user