From fb7ff7b891dcdf0377ae5f4e239de64c14297b56 Mon Sep 17 00:00:00 2001 From: huanghongxun Date: Tue, 21 Sep 2021 16:39:15 +0800 Subject: [PATCH] feat(multiplayer): check cato version before establishing connection. --- .../ui/multiplayer/MultiplayerClient.java | 25 ++++++++ .../ui/multiplayer/MultiplayerManager.java | 57 ++++++++++++++++--- .../hmcl/ui/multiplayer/MultiplayerPage.java | 57 +++++++++++++------ .../ui/multiplayer/MultiplayerPageSkin.java | 7 +++ .../ui/multiplayer/MultiplayerServer.java | 10 ++-- .../resources/assets/lang/I18N.properties | 5 +- .../resources/assets/lang/I18N_zh.properties | 5 +- .../assets/lang/I18N_zh_CN.properties | 7 ++- .../hmcl/game/CrashReportAnalyzer.java | 21 ++++++- 9 files changed, 158 insertions(+), 36 deletions(-) create mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClient.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClient.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClient.java new file mode 100644 index 000000000..c5b9ee01e --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerClient.java @@ -0,0 +1,25 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * 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 . + */ +package org.jackhuang.hmcl.ui.multiplayer; + +public class MultiplayerClient { + + public MultiplayerClient() { + + } +} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java index f3a76d2c5..d0b3b2464 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java @@ -51,7 +51,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG; */ public final class MultiplayerManager { private static final String CATO_DOWNLOAD_URL = "https://files.huangyuhui.net/maven/"; - private static final String CATO_VERSION = "2021-09-20"; + private static final String CATO_VERSION = "2021-09-21"; private static final Artifact CATO_ARTIFACT = new Artifact("cato", "cato", CATO_VERSION, OperatingSystem.CURRENT_OS.getCheckedName() + "-" + Architecture.CURRENT.name().toLowerCase(Locale.ROOT), OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? "exe" : null); @@ -76,7 +76,11 @@ public final class MultiplayerManager { return CATO_ARTIFACT.getPath(Metadata.HMCL_DIRECTORY.resolve("libraries")); } - public static CatoSession joinSession(String sessionName, String peer, int remotePort, int localPort) throws IOException { + public static CatoSession joinSession(String version, String sessionName, String peer, int remotePort, int localPort) throws IOException, IncompatibleCatoVersionException { + if (!CATO_VERSION.equals(version)) { + throw new IncompatibleCatoVersionException(version, CATO_VERSION); + } + Path exe = getCatoExecutable(); if (!Files.isRegularFile(exe)) { throw new IllegalStateException("Cato file not found"); @@ -96,6 +100,10 @@ public final class MultiplayerManager { if (!Files.isRegularFile(exe)) { throw new IllegalStateException("Cato file not found"); } +// +// MultiplayerServer server = new MultiplayerServer(port); +// server.start(); + String[] commands = new String[]{exe.toString(), "--token", "new", "--allows", String.format("127.0.0.1:%d", port)}; Process process = new ProcessBuilder() .command(commands) @@ -181,11 +189,11 @@ public final class MultiplayerManager { return id; } - public String generateInvitationCode(int port) { + public String generateInvitationCode(int gamePort, int serverPort) { if (id == null) { throw new IllegalStateException("id not generated"); } - String json = JsonUtils.GSON.toJson(new Invitation(id, name, port)); + String json = JsonUtils.GSON.toJson(new Invitation(CATO_VERSION, id, name, gamePort, serverPort)); return new String(Base64.getEncoder().encode(json.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8); } @@ -203,6 +211,7 @@ public final class MultiplayerManager { private static final Pattern TEMP_TOKEN_PATTERN = Pattern.compile("id\\(mix(?\\w+)\\)"); private static final Pattern PEER_CONNECTED_PATTERN = Pattern.compile("Peer connected"); + private static final Pattern LOG_PATTERN = Pattern.compile("(\\[\\d+])\\s+(\\w+)\\s+(\\w+-{0,1}\\w+):\\s(.*)"); } public static class CatoExitEvent extends Event { @@ -242,14 +251,22 @@ public final class MultiplayerManager { } public static class Invitation { + private final String version; private final String id; private final String sessionName; - private final int port; + private final int gamePort; + private final int channelPort; - public Invitation(String id, String sessionName, int port) { + public Invitation(String version, String id, String sessionName, int gamePort, int channelPort) { + this.version = version; this.id = id; this.sessionName = sessionName; - this.port = port; + this.gamePort = gamePort; + this.channelPort = channelPort; + } + + public String getVersion() { + return version; } public String getId() { @@ -260,8 +277,30 @@ public final class MultiplayerManager { return sessionName; } - public int getPort() { - return port; + public int getGamePort() { + return gamePort; + } + + public int getChannelPort() { + return channelPort; + } + } + + public static class IncompatibleCatoVersionException extends Exception { + private final String expected; + private final String actual; + + public IncompatibleCatoVersionException(String expected, String actual) { + this.expected = expected; + this.actual = actual; + } + + public String getExpected() { + return expected; + } + + public String getActual() { + return actual; } } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java index d6b09f2b5..9f26d896b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java @@ -151,7 +151,7 @@ public class MultiplayerPage extends Control implements DecoratorPage { throw new IllegalStateException("CatoSession not ready"); } - FXUtils.copyText(getSession().generateInvitationCode(port.get())); + FXUtils.copyText(getSession().generateInvitationCode(port.get(), 0)); } public void createRoom() { @@ -203,7 +203,10 @@ public class MultiplayerPage extends Control implements DecoratorPage { } try { - initCatoSession(MultiplayerManager.joinSession(invitation.getSessionName(), invitation.getId(), invitation.getPort(), localPort)); + initCatoSession(MultiplayerManager.joinSession(invitation.getVersion(), invitation.getSessionName(), invitation.getId(), invitation.getGamePort(), localPort)); + } catch (MultiplayerManager.IncompatibleCatoVersionException e) { + reject.accept(i18n("multiplayer.session.join.invitation_code.version")); + return; } catch (Exception e) { reject.accept(i18n("multiplayer.session.error")); return; @@ -223,10 +226,7 @@ public class MultiplayerPage extends Control implements DecoratorPage { } Controllers.confirm(i18n("multiplayer.session.close.warning"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING, - () -> { - getSession().stop(); - clearCatoSession(); - }, null); + this::stopCatoSession, null); } public void quitRoom() { @@ -234,8 +234,16 @@ public class MultiplayerPage extends Control implements DecoratorPage { throw new IllegalStateException("CatoSession not ready"); } - getSession().stop(); - clearCatoSession(); + Controllers.confirm(i18n("multiplayer.session.quit.warning"), i18n("message.warning"), MessageDialogPane.MessageType.WARNING, + this::stopCatoSession, null); + } + + public void cancelRoom() { + if (getSession() == null || getSession().isReady() || getMultiplayerState() != MultiplayerManager.State.CONNECTING) { + throw new IllegalStateException("CatoSession not existing or already ready"); + } + + stopCatoSession(); } private void initCatoSession(MultiplayerManager.CatoSession session) { @@ -248,6 +256,11 @@ public class MultiplayerPage extends Control implements DecoratorPage { }); } + private void stopCatoSession() { + getSession().stop(); + clearCatoSession(); + } + private void clearCatoSession() { this.session.set(null); this.token.set(null); @@ -257,15 +270,27 @@ public class MultiplayerPage extends Control implements DecoratorPage { private void onCatoExit(MultiplayerManager.CatoExitEvent event) { runInFX(() -> { - if (event.getExitCode() == MultiplayerManager.CatoExitEvent.EXIT_CODE_SESSION_EXPIRED) { - Controllers.dialog(i18n("multiplayer.session.expired")); - } else if (event.getExitCode() != 0) { - if (!((MultiplayerManager.CatoSession) event.getSource()).isReady()) { - Controllers.dialog(i18n("multiplayer.exit.before_ready", event.getExitCode())); - } else { - Controllers.dialog(i18n("multiplayer.exit.after_ready", event.getExitCode())); - } + boolean ready = ((MultiplayerManager.CatoSession) event.getSource()).isReady(); + switch (event.getExitCode()) { + case 0: + break; + case MultiplayerManager.CatoExitEvent.EXIT_CODE_SESSION_EXPIRED: + Controllers.dialog(i18n("multiplayer.session.expired")); + break; + case 1: + if (!ready) { + Controllers.dialog(i18n("multiplayer.exit.timeout")); + } + break; + default: + if (!((MultiplayerManager.CatoSession) event.getSource()).isReady()) { + Controllers.dialog(i18n("multiplayer.exit.before_ready", event.getExitCode())); + } else { + Controllers.dialog(i18n("multiplayer.exit.after_ready", event.getExitCode())); + } + break; } + clearCatoSession(); }); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java index a2d9d5ce1..dd724d824 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java @@ -64,6 +64,11 @@ public class MultiplayerPageSkin extends SkinBase { copyLinkItem.setLeftGraphic(wrap(SVG::accountArrowRightOutline)); copyLinkItem.setOnAction(e -> control.copyInvitationCode()); + AdvancedListItem cancelItem = new AdvancedListItem(); + cancelItem.setTitle(i18n("button.cancel")); + cancelItem.setLeftGraphic(wrap(SVG::closeCircle)); + cancelItem.setOnAction(e -> control.cancelRoom()); + AdvancedListItem quitItem = new AdvancedListItem(); quitItem.setTitle(i18n("multiplayer.session.quit")); quitItem.setLeftGraphic(wrap(SVG::closeCircle)); @@ -77,6 +82,8 @@ public class MultiplayerPageSkin extends SkinBase { FXUtils.onChangeAndOperate(getSkinnable().multiplayerStateProperty(), state -> { if (state == MultiplayerManager.State.DISCONNECTED) { roomPane.getChildren().setAll(createRoomItem, joinRoomItem); + } else if (state == MultiplayerManager.State.CONNECTING) { + roomPane.getChildren().setAll(cancelItem); } else if (state == MultiplayerManager.State.MASTER) { roomPane.getChildren().setAll(copyLinkItem, closeRoomItem); } else if (state == MultiplayerManager.State.SLAVE) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerServer.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerServer.java index df5ad1c00..1499f6b09 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerServer.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerServer.java @@ -88,16 +88,16 @@ public class MultiplayerServer { } public static class JoinRequest extends Request { - private final String clientLauncherVersion; + private final String clientVersion; private final String username; - public JoinRequest(String clientLauncherVersion, String username) { - this.clientLauncherVersion = clientLauncherVersion; + public JoinRequest(String clientVersion, String username) { + this.clientVersion = clientVersion; this.username = username; } - public String getClientLauncherVersion() { - return clientLauncherVersion; + public String getClientVersion() { + return clientVersion; } public String getUsername() { diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 107439b92..75e85d3e3 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -568,8 +568,9 @@ multiplayer=Multiplayer multiplayer.download=Downloading dependencies for multiplayer multiplayer.download.success=Dependencies initialization succeeded multiplayer.download.failed=Failed to initialize multiplayer, some files cannot be downloaded -multiplayer.exit.before_ready=Multiplayer session failed to create. 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.timeout=Failed to connect to multiplayer server. multiplayer.hint=Multiplayer functionality is experimental. Please give feedback. multiplayer.nat=Network Type Detection multiplayer.nat.hint=Network type detection will make it clear whether your network fulfills our requirement for multiplayer mode. @@ -605,9 +606,11 @@ multiplayer.session.join=Join Room multiplayer.session.join.hint=You must obtain the invitation code from the gamer who has already created a multiplayer room. multiplayer.session.join.invitation_code=Invitation code multiplayer.session.join.invitation_code.error=Incorrect invitation code. Please obtain invitation code from the player who creates the multiplayer room. +multiplayer.session.join.invitation_code.version=Versions of multiplayer functionalities are not the same among you. multiplayer.session.join.port.error=Cannot find available local network port for listening. Please ensure that HMCL has the permission to listen on a port. multiplayer.session.members=Room Members multiplayer.session.quit=Quit Room +multiplayer.session.quit.warning=After quiting room, you will lost the connection with the server. Continue? multiplayer.session.username=Username multiplayer.state.connecting=Connecting multiplayer.state.disconnected=Not created/entered a multiplayer session diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 64c84e9d9..c726141ae 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -568,8 +568,9 @@ multiplayer=多人聯機 multiplayer.download=正在下載相依元件 multiplayer.download.success=多人聯機初始化完成 multiplayer.download.failed=初始化失敗,部分文件未能完成下載 -multiplayer.exit.before_ready=多人聯機房間創建失敗,cato 退出碼 %d multiplayer.exit.after_ready=多人聯機會話意外退出,退出碼 %d +multiplayer.exit.before_ready=多人聯機房間創建失敗,cato 退出碼 %d +multiplayer.exit.timeout=無法連接多人聯機服務 multiplayer.hint=多人聯機功能處於實驗階段,如果有問題請回饋。 multiplayer.nat=網路檢測 multiplayer.nat.hint=執行網路檢測可以讓你更清楚你的網路狀況是否符合聯機功能的需求。不符合聯機功能運行條件的網路狀況將可能導致聯機失敗。 @@ -604,9 +605,11 @@ multiplayer.session.join=加入房間 multiplayer.session.join.hint=你需要向已經創建好房間的玩家索要邀請碼以便加入多人聯機房間 multiplayer.session.join.invitation_code=邀請碼 multiplayer.session.join.invitation_code.error=邀請碼不正確,請向開服玩家獲取邀請碼 +multiplayer.session.join.invitation_code.version=多人聯機功能版本號不一致,請保證連接多人聯機功能版本號一致。 multiplayer.session.join.port.error=無法找到可用的本地網路埠,請確保 HMCL 擁有綁定本地埠的權限。 multiplayer.session.members=房間成員 multiplayer.session.quit=退出房間 +multiplayer.session.quit.warning=退出房間後,你將會與伺服器斷開連接,是否繼續? multiplayer.session.username=使用者名稱 multiplayer.state.connecting=連接中 multiplayer.state.disconnected=未創建/加入房間 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 1317b9808..6385b8974 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -568,11 +568,12 @@ multiplayer=多人联机 multiplayer.download=正在下载依赖 multiplayer.download.success=多人联机初始化完成 multiplayer.download.failed=初始化失败,部分文件未能完成下载 -multiplayer.exit.before_ready=多人联机房间创建失败,cato 退出码 %d multiplayer.exit.after_ready=多人联机会话意外退出,退出码 %d +multiplayer.exit.before_ready=多人联机房间创建失败,cato 退出码 %d +multiplayer.exit.timeout=无法连接多人联机服务 multiplayer.hint=多人联机功能处于实验阶段,如果有问题请反馈。 multiplayer.nat=网络检测 -multiplayer.nat.hint=执行网络检测可以让你更清楚你的网络状况是否符合联机功能的需求。不符合联机功能运行条件的网络状况将可能导致联机失败。 +multiplayer.nat.hint=执行网络检测可以让你更清楚你的网络状况是否符合联机功能的需求。检测结果为差的网络可能导致联机失败。 multiplayer.nat.latency=延迟 multiplayer.nat.not_yet_tested=尚未检测 multiplayer.nat.packet_loss_ratio=丢包率 @@ -604,9 +605,11 @@ multiplayer.session.join=加入房间 multiplayer.session.join.hint=你需要向已经创建好房间的玩家索要邀请码以便加入多人联机房间 multiplayer.session.join.invitation_code=邀请码 multiplayer.session.join.invitation_code.error=邀请码不正确,请向开服玩家获取邀请码 +multiplayer.session.join.invitation_code.version=多人联机功能版本号不一致,请保证连接多人联机功能版本号一致。 multiplayer.session.join.port.error=无法找到可用的本地网络端口,请确保 HMCL 拥有绑定本地端口的权限。 multiplayer.session.members=房间成员 multiplayer.session.quit=退出房间 +multiplayer.session.quit.warning=退出房间后,你将会与服务器断开连接,是否继续? multiplayer.session.username=用户名 multiplayer.state.connecting=连接中 multiplayer.state.disconnected=未创建/加入房间 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java index 7c4a72596..b24540afd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java @@ -1,3 +1,20 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2021 huangyuhui and contributors + * + * 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 . + */ package org.jackhuang.hmcl.game; import org.jackhuang.hmcl.util.io.FileUtils; @@ -162,11 +179,11 @@ public final class CrashReportAnalyzer { return result; } - public static final int getJavaVersionFromMajorVersion(int majorVersion) { + public static int getJavaVersionFromMajorVersion(int majorVersion) { if (majorVersion >= 46) { return majorVersion - 44; } else { return -1; } } -} \ No newline at end of file +}