diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index 90756b104..43d22d3a0 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -47,8 +47,6 @@ val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: "" val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: "" val curseForgeApiKey = System.getenv("CURSEFORGE_API_KEY") ?: "" -val enableHiPer = System.getenv("ENABLE_HIPER") ?: "false" - version = "$versionRoot.$buildNumber" dependencies { @@ -151,7 +149,6 @@ tasks.getByName("sha "Microsoft-Auth-Secret" to microsoftAuthSecret, "CurseForge-Api-Key" to curseForgeApiKey, "Build-Channel" to versionType, - "Enable-HiPer" to enableHiPer, "Class-Path" to "pack200.jar", "Add-Opens" to listOf( "java.base/java.lang", diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java index 364aba73e..06baba378 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/GlobalConfig.java @@ -70,8 +70,6 @@ public class GlobalConfig implements Cloneable, Observable { private IntegerProperty platformPromptVersion = new SimpleIntegerProperty(); - private StringProperty multiplayerToken = new SimpleStringProperty(); - private BooleanProperty multiplayerRelay = new SimpleBooleanProperty(); private IntegerProperty multiplayerAgreementVersion = new SimpleIntegerProperty(0); @@ -151,18 +149,6 @@ public class GlobalConfig implements Cloneable, Observable { this.multiplayerAgreementVersion.set(multiplayerAgreementVersion); } - public String getMultiplayerToken() { - return multiplayerToken.get(); - } - - public StringProperty multiplayerTokenProperty() { - return multiplayerToken; - } - - public void setMultiplayerToken(String multiplayerToken) { - this.multiplayerToken.set(multiplayerToken); - } - public static class Serializer implements JsonSerializer, JsonDeserializer { private static final Set knownFields = new HashSet<>(Arrays.asList( "agreementVersion", @@ -181,7 +167,6 @@ public class GlobalConfig implements Cloneable, Observable { JsonObject jsonObject = new JsonObject(); jsonObject.add("agreementVersion", context.serialize(src.getAgreementVersion())); jsonObject.add("platformPromptVersion", context.serialize(src.getPlatformPromptVersion())); - jsonObject.add("multiplayerToken", context.serialize(src.getMultiplayerToken())); jsonObject.add("multiplayerRelay", context.serialize(src.isMultiplayerRelay())); jsonObject.add("multiplayerAgreementVersion", context.serialize(src.getMultiplayerAgreementVersion())); for (Map.Entry entry : src.unknownFields.entrySet()) { @@ -200,7 +185,6 @@ public class GlobalConfig implements Cloneable, Observable { GlobalConfig config = new GlobalConfig(); config.setAgreementVersion(Optional.ofNullable(obj.get("agreementVersion")).map(JsonElement::getAsInt).orElse(0)); config.setPlatformPromptVersion(Optional.ofNullable(obj.get("platformPromptVersion")).map(JsonElement::getAsInt).orElse(0)); - config.setMultiplayerToken(Optional.ofNullable(obj.get("multiplayerToken")).map(JsonElement::getAsString).orElse(null)); config.setMultiplayerRelay(Optional.ofNullable(obj.get("multiplayerRelay")).map(JsonElement::getAsBoolean).orElse(false)); config.setMultiplayerAgreementVersion(Optional.ofNullable(obj.get("multiplayerAgreementVersion")).map(JsonElement::getAsInt).orElse(0)); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index ba598ac54..436639482 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -45,7 +45,6 @@ import org.jackhuang.hmcl.ui.download.DownloadPage; import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider; import org.jackhuang.hmcl.ui.main.LauncherSettingsPage; import org.jackhuang.hmcl.ui.main.RootPage; -import org.jackhuang.hmcl.ui.multiplayer.MultiplayerPage; import org.jackhuang.hmcl.ui.versions.GameListPage; import org.jackhuang.hmcl.ui.versions.VersionPage; import org.jackhuang.hmcl.util.FutureCallback; @@ -93,7 +92,6 @@ public final class Controllers { accountListPage.authServersProperty().bindContentBidirectional(config().getAuthlibInjectorServers()); return accountListPage; }); - private static Lazy multiplayerPage = new Lazy<>(MultiplayerPage::new); private static Lazy settingsPage = new Lazy<>(LauncherSettingsPage::new); private Controllers() { @@ -122,11 +120,6 @@ public final class Controllers { return rootPage.get(); } - // FXThread - public static MultiplayerPage getMultiplayerPage() { - return multiplayerPage.get(); - } - // FXThread public static LauncherSettingsPage getSettingsPage() { return settingsPage.get(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java index e17f8a407..02547e5de 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java @@ -92,19 +92,13 @@ public class AboutPage extends StackPane { mcmod.setSubtitle(i18n("about.thanks_to.mcmod.statement")); mcmod.setExternalLink("https://www.mcmod.cn/"); - IconedTwoLineListItem noin = new IconedTwoLineListItem(); - noin.setImage(new Image("/assets/img/noin.png", 32, 32, false, true)); - noin.setTitle(i18n("about.thanks_to.noin")); - noin.setSubtitle(i18n("about.thanks_to.noin.statement")); - noin.setExternalLink("https://mcer.cn/cato"); - IconedTwoLineListItem contributors = new IconedTwoLineListItem(); contributors.setImage(new Image("/assets/img/github.png", 32, 32, false, true)); contributors.setTitle(i18n("about.thanks_to.contributors")); contributors.setSubtitle(i18n("about.thanks_to.contributors.statement")); contributors.setExternalLink("https://github.com/huanghongxun/HMCL/graphs/contributors"); - thanks.getContent().setAll(yushijinhun, bangbang93, glavo, mcbbs, mcmod, noin, gamerteam, redLnn, contributors); + thanks.getContent().setAll(yushijinhun, bangbang93, glavo, mcbbs, mcmod, gamerteam, redLnn, contributors); } ComponentList community = new ComponentList(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index a21560672..94c282c07 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -44,7 +44,6 @@ import org.jackhuang.hmcl.ui.versions.Versions; import org.jackhuang.hmcl.upgrade.UpdateChecker; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.io.CompressingUtils; -import org.jackhuang.hmcl.util.io.JarUtils; import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.File; @@ -156,18 +155,13 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage { multiplayerItem.setLeftGraphic(wrap(SVG::lan)); multiplayerItem.setActionButtonVisible(false); multiplayerItem.setTitle(i18n("multiplayer")); - if ("true".equalsIgnoreCase(JarUtils.getManifestAttribute("Enable-HiPer", ""))) - multiplayerItem.setOnAction(e -> Controllers.navigate(Controllers.getMultiplayerPage())); - else { - JFXHyperlink link = new JFXHyperlink(i18n("multiplayer.hint.details")); - link.setOnAction(e -> FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/multiplayer-migrate")); - multiplayerItem.setOnAction(e -> - Controllers.dialog( - new MessageDialogPane.Builder(i18n("multiplayer.hint"), null, MessageDialogPane.MessageType.INFO) - .addAction(link) - .ok(null) - .build())); - } + JFXHyperlink link = new JFXHyperlink(i18n("multiplayer.hint.details")); + link.setOnAction(e -> FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/multiplayer-migrate")); + multiplayerItem.setOnAction(e -> Controllers.dialog( + new MessageDialogPane.Builder(i18n("multiplayer.hint"), null, MessageDialogPane.MessageType.INFO) + .addAction(link) + .ok(null) + .build())); // sixth item in left sidebar AdvancedListItem launcherSettingsItem = new AdvancedListItem(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/LocalServerBroadcaster.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/LocalServerBroadcaster.java deleted file mode 100644 index bdb105a39..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/LocalServerBroadcaster.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2022 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; - -import org.jackhuang.hmcl.event.Event; -import org.jackhuang.hmcl.event.EventManager; -import org.jackhuang.hmcl.util.Lang; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.*; -import java.nio.channels.UnresolvedAddressException; -import java.nio.charset.StandardCharsets; -import java.util.logging.Level; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static org.jackhuang.hmcl.util.Logging.LOG; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public class LocalServerBroadcaster implements AutoCloseable { - private final String address; - private final ThreadGroup threadGroup = new ThreadGroup("JoinSession"); - - private final EventManager onExit = new EventManager<>(); - - private boolean running = true; - - public LocalServerBroadcaster(String address) { - this.address = address; - } - - private Thread newThread(Runnable task, String name) { - Thread thread = new Thread(threadGroup, task, name); - thread.setDaemon(true); - return thread; - } - - @Override - public void close() { - running = false; - threadGroup.interrupt(); - } - - public String getAddress() { - return address; - } - - public EventManager onExit() { - return onExit; - } - - public static final Pattern ADDRESS_PATTERN = Pattern.compile("^\\s*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d{1,5})\\s*$"); - - public void start() { - Thread forwardPortThread = newThread(this::forwardPort, "ForwardPort"); - forwardPortThread.start(); - } - - private void forwardPort() { - try { - Matcher matcher = ADDRESS_PATTERN.matcher(address); - if (!matcher.find()) { - throw new MalformedURLException(); - } - try (Socket forwardingSocket = new Socket(); - ServerSocket serverSocket = new ServerSocket()) { - forwardingSocket.setSoTimeout(30000); - forwardingSocket.connect(new InetSocketAddress(matcher.group(1), Lang.parseInt(matcher.group(2), 0))); - - serverSocket.bind(null); - - Thread broadcastMOTDThread = newThread(() -> broadcastMOTD(serverSocket.getLocalPort()), "BroadcastMOTD"); - broadcastMOTDThread.start(); - - LOG.log(Level.INFO, "Listening " + serverSocket.getLocalSocketAddress()); - - while (running) { - Socket forwardedSocket = serverSocket.accept(); - LOG.log(Level.INFO, "Accepting client"); - newThread(() -> forwardTraffic(forwardingSocket, forwardedSocket), "Forward S->D").start(); - newThread(() -> forwardTraffic(forwardedSocket, forwardingSocket), "Forward D->S").start(); - } - } - } catch (IOException | UnresolvedAddressException e) { - LOG.log(Level.WARNING, "Error in forwarding port", e); - } finally { - close(); - onExit.fireEvent(new Event(this)); - } - } - - private void forwardTraffic(Socket src, Socket dest) { - try (InputStream is = src.getInputStream(); OutputStream os = dest.getOutputStream()) { - byte[] buf = new byte[1024]; - while (true) { - int len = is.read(buf, 0, buf.length); - if (len < 0) break; - LOG.log(Level.INFO, "Forwarding buffer " + len); - os.write(buf, 0, len); - } - } catch (IOException e) { - LOG.log(Level.WARNING, "Disconnected", e); - } - } - - private void broadcastMOTD(int port) { - DatagramSocket socket; - InetAddress broadcastAddress; - try { - socket = new DatagramSocket(); - broadcastAddress = InetAddress.getByName("224.0.2.60"); - } catch (IOException e) { - LOG.log(Level.WARNING, "Failed to create datagram socket", e); - return; - } - - while (running) { - try { - byte[] data = String.format("[MOTD]%s[/MOTD][AD]%d[/AD]", i18n("multiplayer.session.name.motd"), port).getBytes(StandardCharsets.UTF_8); - DatagramPacket packet = new DatagramPacket(data, 0, data.length, broadcastAddress, 4445); - socket.send(packet); - LOG.finest("Broadcast server 0.0.0.0:" + port); - } catch (IOException e) { - LOG.log(Level.WARNING, "Failed to send motd packet", e); - } - - try { - Thread.sleep(1500); - } catch (InterruptedException ignored) { - return; - } - } - - socket.close(); - } -} 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 deleted file mode 100644 index 86d9539bb..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerManager.java +++ /dev/null @@ -1,553 +0,0 @@ -/* - * 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; - -import com.google.gson.JsonParseException; -import javafx.application.Platform; -import javafx.beans.binding.Bindings; -import javafx.beans.binding.BooleanBinding; -import org.jackhuang.hmcl.Metadata; -import org.jackhuang.hmcl.event.Event; -import org.jackhuang.hmcl.event.EventManager; -import org.jackhuang.hmcl.setting.ConfigHolder; -import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.util.*; -import org.jackhuang.hmcl.util.gson.JsonUtils; -import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.io.HttpRequest; -import org.jackhuang.hmcl.util.io.NetworkUtils; -import org.jackhuang.hmcl.util.platform.Architecture; -import org.jackhuang.hmcl.util.platform.CommandBuilder; -import org.jackhuang.hmcl.util.platform.ManagedProcess; -import org.jackhuang.hmcl.util.platform.OperatingSystem; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.nio.charset.StandardCharsets; -import java.nio.file.*; -import java.nio.file.attribute.PosixFilePermission; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; -import java.util.logging.Level; - -import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; -import static org.jackhuang.hmcl.util.Lang.*; -import static org.jackhuang.hmcl.util.Logging.LOG; -import static org.jackhuang.hmcl.util.Pair.pair; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; -import static org.jackhuang.hmcl.util.io.ChecksumMismatchException.verifyChecksum; - -/** - * Cato Management. - */ -public final class MultiplayerManager { - // static final String HIPER_VERSION = "1.2.2"; - private static final String HIPER_DOWNLOAD_URL = "https://gitcode.net/to/hiper/-/raw/master/"; - private static final String HIPER_PACKAGES_URL = HIPER_DOWNLOAD_URL + "packages.sha1"; - private static final String HIPER_POINTS_URL = "https://cert.mcer.cn/point.yml"; - private static final Path HIPER_TEMP_CONFIG_PATH = Metadata.HMCL_DIRECTORY.resolve("hiper.yml"); - private static final Path HIPER_CONFIG_DIR = Metadata.HMCL_DIRECTORY.resolve("hiper-config"); - public static final Path HIPER_PATH = getHiperLocalDirectory().resolve(getHiperFileName()); - public static final int HIPER_AGREEMENT_VERSION = 3; - private static final String REMOTE_ADDRESS = "127.0.0.1"; - private static final String LOCAL_ADDRESS = "0.0.0.0"; - - private static final Map archMap = mapOf( - pair(Architecture.ARM32, "arm-7"), - pair(Architecture.ARM64, "arm64"), - pair(Architecture.X86, "386"), - pair(Architecture.X86_64, "amd64"), - pair(Architecture.LOONGARCH64, "loong64"), - pair(Architecture.MIPS, "mips"), - pair(Architecture.MIPS64, "mips64"), - pair(Architecture.MIPS64EL, "mips64le"), - pair(Architecture.PPC64LE, "ppc64le"), - pair(Architecture.RISCV64, "riscv64"), - pair(Architecture.MIPSEL, "mipsle") - ); - - private static final Map osMap = mapOf( - pair(OperatingSystem.LINUX, "linux"), - pair(OperatingSystem.WINDOWS, "windows"), - pair(OperatingSystem.OSX, "darwin") - ); - - private static final String HIPER_TARGET_NAME = String.format("%s-%s", - osMap.getOrDefault(OperatingSystem.CURRENT_OS, "windows"), - archMap.getOrDefault(Architecture.SYSTEM_ARCH, "amd64")); - - private static final String GSUDO_VERSION = "1.7.1"; - private static final String GSUDO_TARGET_ARCH = Architecture.SYSTEM_ARCH == Architecture.X86_64 ? "amd64" : "x86"; - private static final String GSUDO_FILE_NAME = "gsudo.exe"; - private static final String GSUDO_DOWNLOAD_URL = "https://gitcode.net/glavo/gsudo-release/-/raw/75c952ea3afe8792b0db4fe9bab87d41b21e5895/" + GSUDO_TARGET_ARCH + "/" + GSUDO_FILE_NAME; - private static final Path GSUDO_LOCAL_FILE = Metadata.HMCL_DIRECTORY.resolve("libraries").resolve("gsudo").resolve("gsudo").resolve(GSUDO_VERSION).resolve(GSUDO_TARGET_ARCH).resolve(GSUDO_FILE_NAME); - private static final boolean USE_GSUDO; - - static final boolean IS_ADMINISTRATOR; - - static final BooleanBinding tokenInvalid = Bindings.createBooleanBinding( - () -> { - String token = globalConfig().multiplayerTokenProperty().getValue(); - return token == null || token.isEmpty() || !StringUtils.isAlphabeticOrNumber(token); - }, - globalConfig().multiplayerTokenProperty()); - - private static final DateFormat HIPER_VALID_TIME_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - static { - boolean isAdministrator = false; - if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { - try { - Process process = Runtime.getRuntime().exec(new String[]{"net.exe", "session"}); - if (!process.waitFor(1, TimeUnit.SECONDS)) { - process.destroy(); - } else { - isAdministrator = process.exitValue() == 0; - } - } catch (Throwable ignored) { - } - USE_GSUDO = !isAdministrator && OperatingSystem.SYSTEM_BUILD_NUMBER >= 10000; - } else { - isAdministrator = "root".equals(System.getProperty("user.name")); - USE_GSUDO = false; - } - IS_ADMINISTRATOR = isAdministrator; - } - - private static CompletableFuture> HASH; - - private MultiplayerManager() { - } - - public static Path getConfigPath(String token) { - return HIPER_CONFIG_DIR.resolve(Hex.encodeHex(DigestUtils.digest("SHA-1", token)) + ".yml"); - } - - public static void clearConfiguration() { - try { - Files.deleteIfExists(HIPER_TEMP_CONFIG_PATH); - Files.deleteIfExists(getConfigPath(ConfigHolder.globalConfig().getMultiplayerToken())); - } catch (IOException e) { - LOG.log(Level.WARNING, "Failed to delete config", e); - } - } - - private static CompletableFuture> getPackagesHash() { - FXUtils.checkFxUserThread(); - if (HASH == null) { - HASH = CompletableFuture.supplyAsync(wrap(() -> { - String hashList = HttpRequest.GET(HIPER_PACKAGES_URL).getString(); - Map hashes = new HashMap<>(); - for (String line : hashList.split("\n")) { - String[] items = line.trim().split(" {2}"); - if (items.length == 2 && items[0].length() == 40) { - hashes.put(items[1], items[0]); - } else { - LOG.warning("Failed to parse Hiper packages.sha1 file, line: " + line); - } - } - if (USE_GSUDO) { - hashes.put(GSUDO_FILE_NAME, HttpRequest.GET(GSUDO_DOWNLOAD_URL + ".sha1").getString().trim()); - } - return hashes; - })); - } - return HASH; - } - - public static Task downloadHiper() { - return Task.fromCompletableFuture(getPackagesHash()).thenComposeAsync(packagesHash -> { - - BiFunction getFileDownloadTask = (String remotePath, String localFileName) -> { - String hash = packagesHash.get(remotePath); - return new FileDownloadTask( - NetworkUtils.toURL(String.format("%s%s", HIPER_DOWNLOAD_URL, remotePath)), - getHiperLocalDirectory().resolve(localFileName).toFile(), - hash == null ? null : new FileDownloadTask.IntegrityCheck("SHA-1", hash)); - }; - - List> tasks; - if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { - if (!packagesHash.containsKey(String.format("%s/hiper.exe", HIPER_TARGET_NAME))) { - throw new HiperUnsupportedPlatformException(); - } - tasks = new ArrayList<>(4); - - tasks.add(getFileDownloadTask.apply(String.format("%s/hiper.exe", HIPER_TARGET_NAME), "hiper.exe")); - tasks.add(getFileDownloadTask.apply(String.format("%s/wintun.dll", HIPER_TARGET_NAME), "wintun.dll")); - // tasks.add(getFileDownloadTask.apply("tap-windows-9.21.2.exe", "tap-windows-9.21.2.exe")); - if (USE_GSUDO) - tasks.add(new FileDownloadTask( - NetworkUtils.toURL(GSUDO_DOWNLOAD_URL), - GSUDO_LOCAL_FILE.toFile(), - new FileDownloadTask.IntegrityCheck("SHA-1", packagesHash.get(GSUDO_FILE_NAME)) - )); - } else { - if (!packagesHash.containsKey(String.format("%s/hiper", HIPER_TARGET_NAME))) { - throw new HiperUnsupportedPlatformException(); - } - tasks = Collections.singletonList(getFileDownloadTask.apply(String.format("%s/hiper", HIPER_TARGET_NAME), "hiper")); - } - return Task.allOf(tasks).thenRunAsync(() -> { - if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) { - Set perm = Files.getPosixFilePermissions(HIPER_PATH); - perm.add(PosixFilePermission.OWNER_EXECUTE); - Files.setPosixFilePermissions(HIPER_PATH, perm); - } - }); - }); - } - - public static void downloadHiperConfig(String token, Path configPath) throws IOException { - String certFileContent = HttpRequest.GET(String.format("https://cert.mcer.cn/%s.yml", token)).getString(); - if (!certFileContent.equals("")) { - FileUtils.writeText(configPath, certFileContent); - } - } - - public static CompletableFuture startHiper(String token) { - return getPackagesHash().thenComposeAsync(packagesHash -> { - CompletableFuture future = new CompletableFuture<>(); - try { - if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { - verifyChecksum(getHiperLocalDirectory().resolve("hiper.exe"), "SHA-1", packagesHash.get(String.format("%s/hiper.exe", HIPER_TARGET_NAME))); - verifyChecksum(getHiperLocalDirectory().resolve("wintun.dll"), "SHA-1", packagesHash.get(String.format("%s/wintun.dll", HIPER_TARGET_NAME))); - // verifyChecksumAndDeleteIfNotMatched(getHiperLocalDirectory().resolve("tap-windows-9.21.2.exe"), packagesHash.get("tap-windows-9.21.2.exe")); - if (USE_GSUDO) - verifyChecksum(GSUDO_LOCAL_FILE, "SHA-1", packagesHash.get(GSUDO_FILE_NAME)); - } else { - verifyChecksum(getHiperLocalDirectory().resolve("hiper"), "SHA-1", packagesHash.get(String.format("%s/hiper", HIPER_TARGET_NAME))); - } - - future.complete(null); - } catch (IOException e) { - LOG.log(Level.WARNING, "Failed to verify HiPer files", e); - Platform.runLater(() -> Controllers.taskDialog(MultiplayerManager.downloadHiper() - .whenComplete(exception -> { - if (exception == null) - future.complete(null); - else - future.completeExceptionally(exception); - }), i18n("multiplayer.download"), TaskCancellationAction.NORMAL)); - } - return future; - }).thenApplyAsync(wrap(ignored -> { - Path configPath = getConfigPath(token); - Files.createDirectories(configPath.getParent()); - - // 下载 HiPer 配置文件 - Logging.registerForbiddenToken(token, ""); - try { - downloadHiperConfig(token, configPath); - } catch (IOException e) { - LOG.log(Level.WARNING, "configuration file cloud cache token has been not available, try to use the local configuration file", e); - } - - if (Files.exists(configPath)) { - Files.copy(configPath, HIPER_TEMP_CONFIG_PATH, StandardCopyOption.REPLACE_EXISTING); - try (BufferedWriter output = Files.newBufferedWriter(HIPER_TEMP_CONFIG_PATH, StandardOpenOption.WRITE, StandardOpenOption.APPEND)) { - output.write("\n"); - output.write("logging:\n"); - output.write(" format: json\n"); - output.write(" file_path: '" + Metadata.HMCL_DIRECTORY.resolve("logs").resolve("hiper.log").toString().replace("'", "''") + "'\n"); - } - } - - String[] commands = new String[]{HIPER_PATH.toString(), "-config", HIPER_TEMP_CONFIG_PATH.toString()}; - - if (!IS_ADMINISTRATOR) { - switch (OperatingSystem.CURRENT_OS) { - case WINDOWS: - if (USE_GSUDO) - commands = new String[]{GSUDO_LOCAL_FILE.toString(), HIPER_PATH.toString(), "-config", HIPER_TEMP_CONFIG_PATH.toString()}; - break; - case LINUX: - String askpass = System.getProperty("hmcl.askpass", System.getenv("HMCL_ASKPASS")); - if ("user".equalsIgnoreCase(askpass)) - commands = new String[]{"sudo", "-A", HIPER_PATH.toString(), "-config", HIPER_TEMP_CONFIG_PATH.toString()}; - else if ("false".equalsIgnoreCase(askpass)) - commands = new String[]{"sudo", "--non-interactive", HIPER_PATH.toString(), "-config", HIPER_TEMP_CONFIG_PATH.toString()}; - else { - if (Files.exists(Paths.get("/usr/bin/pkexec"))) - commands = new String[]{"/usr/bin/pkexec", HIPER_PATH.toString(), "-config", HIPER_TEMP_CONFIG_PATH.toString()}; - else - commands = new String[]{"sudo", "--non-interactive", HIPER_PATH.toString(), "-config", HIPER_TEMP_CONFIG_PATH.toString()}; - } - break; - case OSX: - commands = new String[]{"sudo", "--non-interactive", HIPER_PATH.toString(), "-config", HIPER_TEMP_CONFIG_PATH.toString()}; - break; - } - } - - Process process = new ProcessBuilder() - .command(commands) - .start(); - - return new HiperSession(process, Arrays.asList(commands)); - })); - } - - public static String getHiperFileName() { - if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { - return "hiper.exe"; - } else { - return "hiper"; - } - } - - public static Path getHiperLocalDirectory() { - return Metadata.HMCL_DIRECTORY.resolve("libraries").resolve("hiper").resolve("hiper").resolve("binary"); - } - - public static class HiperSession extends ManagedProcess { - private final EventManager onExit = new EventManager<>(); - private final EventManager onIPAllocated = new EventManager<>(); - private final EventManager onValidUntil = new EventManager<>(); - private final BufferedWriter writer; - private int error = 0; - - HiperSession(Process process, List commands) { - super(process, commands); - - Runtime.getRuntime().addShutdownHook(new Thread(this::stop)); - - LOG.info("Started hiper with command: " + new CommandBuilder().addAll(commands)); - - addRelatedThread(Lang.thread(this::waitFor, "HiperExitWaiter", true)); - pumpInputStream(this::onLog); - pumpErrorStream(this::onLog); - - writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8)); - } - - private void onLog(String log) { - if (!log.startsWith("{")) { - LOG.warning("[HiPer] " + log); - - if (log.startsWith("failed to load config")) - error = HiperExitEvent.INVALID_CONFIGURATION; - else if (log.startsWith("sudo: ") || log.startsWith("Error getting authority") || log.startsWith("Error: An error occurred trying to start process")) - error = HiperExitEvent.NO_SUDO_PRIVILEGES; - else if (log.startsWith("Failed to write to log, can't rename log file")) { - error = HiperExitEvent.NO_SUDO_PRIVILEGES; - stop(); - } - - return; - } - - try { - Map logJson = JsonUtils.fromNonNullJson(log, Map.class); - String msg = ""; - if (logJson.containsKey("msg")) { - msg = tryCast(logJson.get("msg"), String.class).orElse(""); - if (msg.contains("Failed to get a tun/tap device")) { - error = HiperExitEvent.FAILED_GET_DEVICE; - } - if (msg.contains("Failed to load certificate from config")) { - error = HiperExitEvent.FAILED_LOAD_CONFIG; - } - if (msg.contains("Validity of client certificate")) { - Optional validUntil = tryCast(logJson.get("valid"), String.class); - if (validUntil.isPresent()) { - try { - synchronized (HIPER_VALID_TIME_FORMAT) { - Date date = HIPER_VALID_TIME_FORMAT.parse(validUntil.get()); - onValidUntil.fireEvent(new HiperShowValidUntilEvent(this, date)); - } - } catch (JsonParseException | ParseException e) { - LOG.log(Level.WARNING, "Failed to parse certification expire time string: " + validUntil.get()); - } - } - } - } - - if (logJson.containsKey("network")) { - Map network = tryCast(logJson.get("network"), Map.class).orElse(Collections.emptyMap()); - if (network.containsKey("IP") && msg.contains("Main HostMap created")) { - Optional ip = tryCast(network.get("IP"), String.class); - ip.ifPresent(s -> onIPAllocated.fireEvent(new HiperIPEvent(this, s))); - } - } - } catch (JsonParseException e) { - LOG.log(Level.WARNING, "Failed to parse hiper log: " + log, e); - } - } - - private void waitFor() { - try { - int exitCode = getProcess().waitFor(); - LOG.info("Hiper exited with exitcode " + exitCode); - if (error != 0) { - onExit.fireEvent(new HiperExitEvent(this, error)); - } else { - onExit.fireEvent(new HiperExitEvent(this, exitCode)); - } - } catch (InterruptedException e) { - onExit.fireEvent(new HiperExitEvent(this, HiperExitEvent.INTERRUPTED)); - } finally { - try { - if (writer != null) - writer.close(); - } catch (IOException e) { - LOG.log(Level.WARNING, "Failed to close Hiper stdin writer", e); - } - } - destroyRelatedThreads(); - } - - @Override - public void stop() { - try { - writer.write("quit\n"); - writer.flush(); - } catch (IOException e) { - LOG.log(Level.WARNING, "Failed to quit HiPer", e); - } - try { - getProcess().waitFor(1, TimeUnit.SECONDS); - } catch (InterruptedException ignored) { - } - super.stop(); - } - - public EventManager onExit() { - return onExit; - } - - public EventManager onIPAllocated() { - return onIPAllocated; - } - - public EventManager onValidUntil() { - return onValidUntil; - } - - } - - public static class HiperExitEvent extends Event { - private final int exitCode; - - public HiperExitEvent(Object source, int exitCode) { - super(source); - this.exitCode = exitCode; - } - - public int getExitCode() { - return exitCode; - } - - public static final int INTERRUPTED = -1; - public static final int INVALID_CONFIGURATION = -2; - public static final int CERTIFICATE_EXPIRED = -3; - public static final int FAILED_GET_DEVICE = -4; - public static final int FAILED_LOAD_CONFIG = -5; - public static final int NO_SUDO_PRIVILEGES = -6; - } - - public static class HiperIPEvent extends Event { - private final String ip; - - public HiperIPEvent(Object source, String ip) { - super(source); - this.ip = ip; - } - - public String getIP() { - return ip; - } - } - - public static class HiperShowValidUntilEvent extends Event { - private final Date validAt; - - public HiperShowValidUntilEvent(Object source, Date validAt) { - super(source); - this.validAt = validAt; - } - - public Date getValidUntil() { - return validAt; - } - } - - public static class HiperExitException extends RuntimeException { - private final int exitCode; - private final boolean ready; - - public HiperExitException(int exitCode, boolean ready) { - this.exitCode = exitCode; - this.ready = ready; - } - - public int getExitCode() { - return exitCode; - } - - public boolean isReady() { - return ready; - } - } - - public static class HiperExitTimeoutException extends RuntimeException { - } - - public static class HiperSessionExpiredException extends HiperInvalidConfigurationException { - } - - public static class HiperInvalidConfigurationException extends RuntimeException { - } - - public static class JoinRequestTimeoutException extends RuntimeException { - } - - public static class PeerConnectionTimeoutException extends RuntimeException { - } - - public static class ConnectionErrorException extends RuntimeException { - } - - public static class KickedException extends RuntimeException { - private final String reason; - - public KickedException(String reason) { - this.reason = reason; - } - - public String getReason() { - return reason; - } - } - - public static class HiperInvalidTokenException extends RuntimeException { - } - - public static class HiperUnsupportedPlatformException extends RuntimeException { - } - -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerOfflineAccount.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerOfflineAccount.java deleted file mode 100644 index b02569961..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerOfflineAccount.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2022 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; - -import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorArtifactProvider; -import org.jackhuang.hmcl.auth.offline.OfflineAccount; -import org.jackhuang.hmcl.auth.offline.Skin; - -import java.util.UUID; - -public class MultiplayerOfflineAccount extends OfflineAccount { - - public MultiplayerOfflineAccount(AuthlibInjectorArtifactProvider downloader, String username, UUID uuid, Skin skin) { - super(downloader, username, uuid, skin); - } - - @Override - protected boolean loadAuthlibInjector(Skin skin) { - return true; - } -} 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 deleted file mode 100644 index 3cc29f8ce..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPage.java +++ /dev/null @@ -1,367 +0,0 @@ -/* - * 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; - -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXDialogLayout; -import javafx.beans.property.*; -import javafx.scene.control.Label; -import javafx.scene.control.Skin; -import org.jackhuang.hmcl.auth.Account; -import org.jackhuang.hmcl.auth.offline.OfflineAccount; -import org.jackhuang.hmcl.event.Event; -import org.jackhuang.hmcl.setting.DownloadProviders; -import org.jackhuang.hmcl.setting.Profile; -import org.jackhuang.hmcl.setting.Profiles; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.construct.*; -import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; -import org.jackhuang.hmcl.ui.decorator.DecoratorPage; -import org.jackhuang.hmcl.ui.versions.Versions; -import org.jackhuang.hmcl.util.HMCLService; -import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.TaskCancellationAction; -import org.jackhuang.hmcl.util.io.ChecksumMismatchException; -import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.platform.CommandBuilder; -import org.jackhuang.hmcl.util.platform.OperatingSystem; -import org.jackhuang.hmcl.util.platform.SystemUtils; - -import java.io.File; -import java.util.Date; -import java.util.concurrent.CancellationException; -import java.util.function.Consumer; -import java.util.logging.Level; - -import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; -import static org.jackhuang.hmcl.ui.FXUtils.runInFX; -import static org.jackhuang.hmcl.util.Lang.resolveException; -import static org.jackhuang.hmcl.util.Logging.LOG; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public class MultiplayerPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware { - private final ReadOnlyObjectWrapper state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("multiplayer"))); - - private final ReadOnlyObjectWrapper session = new ReadOnlyObjectWrapper<>(); - private final IntegerProperty port = new SimpleIntegerProperty(); - private final StringProperty address = new SimpleStringProperty(); - private final ReadOnlyObjectWrapper expireTime = new ReadOnlyObjectWrapper<>(); - - private Consumer onExit; - private Consumer onIPAllocated; - private Consumer onValidUntil; - - private final ReadOnlyObjectWrapper broadcaster = new ReadOnlyObjectWrapper<>(); - private Consumer onBroadcasterExit = null; - - public MultiplayerPage() { - } - - @Override - public void onPageShown() { - checkAgreement(this::downloadHiPerIfNecessary); - } - - @Override - protected Skin createDefaultSkin() { - return new MultiplayerPageSkin(this); - } - - public int getPort() { - return port.get(); - } - - public IntegerProperty portProperty() { - return port; - } - - public void setPort(int port) { - this.port.set(port); - } - - public String getAddress() { - return address.get(); - } - - public StringProperty addressProperty() { - return address; - } - - public void setAddress(String address) { - this.address.set(address); - } - - public LocalServerBroadcaster getBroadcaster() { - return broadcaster.get(); - } - - public ReadOnlyObjectWrapper broadcasterProperty() { - return broadcaster; - } - - public void setBroadcaster(LocalServerBroadcaster broadcaster) { - this.broadcaster.set(broadcaster); - } - - public Date getExpireTime() { - return expireTime.get(); - } - - public ReadOnlyObjectWrapper expireTimeProperty() { - return expireTime; - } - - public void setExpireTime(Date expireTime) { - this.expireTime.set(expireTime); - } - - public MultiplayerManager.HiperSession getSession() { - return session.get(); - } - - public ReadOnlyObjectProperty sessionProperty() { - return session.getReadOnlyProperty(); - } - - void launchGame() { - Profile profile = Profiles.getSelectedProfile(); - Versions.launch(profile, profile.getSelectedVersion(), (launcherHelper) -> { - launcherHelper.setKeep(); - Account account = launcherHelper.getAccount(); - if (account instanceof OfflineAccount && !(account instanceof MultiplayerOfflineAccount)) { - OfflineAccount offlineAccount = (OfflineAccount) account; - launcherHelper.setAccount(new MultiplayerOfflineAccount( - offlineAccount.getDownloader(), - offlineAccount.getUsername(), - offlineAccount.getUUID(), - offlineAccount.getSkin() - )); - } - }); - } - - private void checkAgreement(Runnable runnable) { - if (globalConfig().getMultiplayerAgreementVersion() < MultiplayerManager.HIPER_AGREEMENT_VERSION) { - JFXDialogLayout agreementPane = new JFXDialogLayout(); - agreementPane.setHeading(new Label(i18n("launcher.agreement"))); - agreementPane.setBody(new Label(i18n("multiplayer.agreement.prompt"))); - JFXHyperlink agreementLink = new JFXHyperlink(i18n("launcher.agreement")); - agreementLink.setOnAction(e -> HMCLService.openRedirectLink("multiplayer-agreement")); - JFXButton yesButton = new JFXButton(i18n("launcher.agreement.accept")); - yesButton.getStyleClass().add("dialog-accept"); - yesButton.setOnAction(e -> { - globalConfig().setMultiplayerAgreementVersion(MultiplayerManager.HIPER_AGREEMENT_VERSION); - runnable.run(); - agreementPane.fireEvent(new DialogCloseEvent()); - }); - JFXButton noButton = new JFXButton(i18n("launcher.agreement.decline")); - noButton.getStyleClass().add("dialog-cancel"); - noButton.setOnAction(e -> { - agreementPane.fireEvent(new DialogCloseEvent()); - fireEvent(new PageCloseEvent()); - }); - agreementPane.setActions(agreementLink, yesButton, noButton); - Controllers.dialog(agreementPane); - } else { - runnable.run(); - } - } - - private void downloadHiPerIfNecessary() { - if (!MultiplayerManager.HIPER_PATH.toFile().exists()) { - setDisabled(true); - Controllers.taskDialog(MultiplayerManager.downloadHiper() - .whenComplete(Schedulers.javafx(), exception -> { - setDisabled(false); - if (exception != null) { - if (exception instanceof CancellationException) { - Controllers.showToast(i18n("message.cancelled")); - } else if (exception instanceof MultiplayerManager.HiperUnsupportedPlatformException) { - Controllers.dialog(i18n("multiplayer.download.unsupported"), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR); - fireEvent(new PageCloseEvent()); - } else { - Controllers.dialog(DownloadProviders.localizeErrorMessage(exception), i18n("install.failed.downloading"), MessageDialogPane.MessageType.ERROR); - fireEvent(new PageCloseEvent()); - } - } else { - Controllers.showToast(i18n("multiplayer.download.success")); - } - }), i18n("multiplayer.download"), TaskCancellationAction.NORMAL); - } else { - setDisabled(false); - } - } - - private String localizeErrorMessage(Throwable t) { - Throwable e = resolveException(t); - if (e instanceof CancellationException) { - LOG.info("Connection rejected by the server"); - return i18n("message.cancelled"); - } else if (e instanceof MultiplayerManager.HiperInvalidConfigurationException) { - LOG.warning("HiPer invalid configuration"); - return i18n("multiplayer.token.malformed"); - } else if (e instanceof ChecksumMismatchException) { - LOG.log(Level.WARNING, "Failed to verify HiPer files", e); - return i18n("multiplayer.error.file_not_found"); - } else if (e instanceof MultiplayerManager.HiperExitException) { - int exitCode = ((MultiplayerManager.HiperExitException) e).getExitCode(); - LOG.warning("HiPer exited unexpectedly with exit code " + exitCode); - return i18n("multiplayer.exit", exitCode); - } else if (e instanceof MultiplayerManager.HiperInvalidTokenException) { - LOG.warning("invalid token"); - return i18n("multiplayer.token.invalid"); - } else { - LOG.log(Level.WARNING, "Unknown HiPer exception", e); - return e.getLocalizedMessage() + "\n" + StringUtils.getStackTrace(e); - } - } - - public void start() { - MultiplayerManager.startHiper(globalConfig().getMultiplayerToken()) - .thenAcceptAsync(session -> { - this.session.set(session); - onExit = session.onExit().registerWeak(this::onExit); - onIPAllocated = session.onIPAllocated().registerWeak(this::onIPAllocated); - onValidUntil = session.onValidUntil().registerWeak(this::onValidUntil); - }, Schedulers.javafx()) - .exceptionally(throwable -> { - runInFX(() -> Controllers.dialog(localizeErrorMessage(throwable), null, MessageDialogPane.MessageType.ERROR)); - return null; - }); - } - - public void stop() { - if (getSession() != null) { - getSession().stop(); - } - if (getBroadcaster() != null) { - getBroadcaster().close(); - } - clearSession(); - } - - public void broadcast(String url) { - LocalServerBroadcaster broadcaster = new LocalServerBroadcaster(url); - this.onBroadcasterExit = broadcaster.onExit().registerWeak(this::onBroadcasterExit); - broadcaster.start(); - this.broadcaster.set(broadcaster); - } - - public void stopBroadcasting() { - if (getBroadcaster() != null) { - getBroadcaster().close(); - setBroadcaster(null); - } - } - - private void onBroadcasterExit(Event event) { - runInFX(() -> { - if (this.broadcaster.get() == event.getSource()) { - this.broadcaster.set(null); - } - }); - } - - private void clearSession() { - this.session.set(null); - this.expireTime.set(null); - this.onExit = null; - this.onIPAllocated = null; - this.onValidUntil = null; - this.broadcaster.set(null); - this.onBroadcasterExit = null; - } - - private void onIPAllocated(MultiplayerManager.HiperIPEvent event) { - runInFX(() -> this.address.set(event.getIP())); - } - - private void onValidUntil(MultiplayerManager.HiperShowValidUntilEvent event) { - runInFX(() -> this.expireTime.set(event.getValidUntil())); - } - - private void onExit(MultiplayerManager.HiperExitEvent event) { - runInFX(() -> { - switch (event.getExitCode()) { - case 0: - break; - case MultiplayerManager.HiperExitEvent.CERTIFICATE_EXPIRED: - MultiplayerManager.clearConfiguration(); - Controllers.dialog(i18n("multiplayer.token.expired")); - break; - case MultiplayerManager.HiperExitEvent.INVALID_CONFIGURATION: - MultiplayerManager.clearConfiguration(); - Controllers.dialog(i18n("multiplayer.token.malformed")); - break; - case MultiplayerManager.HiperExitEvent.NO_SUDO_PRIVILEGES: - if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) { - Controllers.confirm(i18n("multiplayer.error.failed_sudo.windows"), null, MessageDialogPane.MessageType.WARNING, () -> { - FXUtils.openLink("https://docs.hmcl.net/multiplayer/admin.html"); - }, null); - } else if (OperatingSystem.CURRENT_OS == OperatingSystem.LINUX) { - Controllers.dialog(i18n("multiplayer.error.failed_sudo.linux", MultiplayerManager.HIPER_PATH.toString()), null, MessageDialogPane.MessageType.WARNING); - } else if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) { - Controllers.confirm(i18n("multiplayer.error.failed_sudo.mac"), null, MessageDialogPane.MessageType.INFO, () -> { - try { - String text = "%hmcl-hiper ALL=(ALL:ALL) NOPASSWD: " + MultiplayerManager.HIPER_PATH.toString().replaceAll("[ @!(),:=\\\\]", "\\\\$0") + "\n"; - - File sudoersTmp = File.createTempFile("sudoer", ".tmp"); - sudoersTmp.deleteOnExit(); - FileUtils.writeText(sudoersTmp, text); - - SystemUtils.callExternalProcess( - "osascript", "-e", String.format("do shell script \"%s\" with administrator privileges", String.join(";", - "dscl . create /Groups/hmcl-hiper PrimaryGroupID 758", - "dscl . merge /Groups/hmcl-hiper GroupMembership " + CommandBuilder.toShellStringLiteral(System.getProperty("user.name")) + "", - "mkdir -p /private/etc/sudoers.d", - "mv -f " + CommandBuilder.toShellStringLiteral(sudoersTmp.toString()) + " /private/etc/sudoers.d/hmcl-hiper", - "chown root /private/etc/sudoers.d/hmcl-hiper", - "chmod 0440 /private/etc/sudoers.d/hmcl-hiper" - ).replaceAll("[\\\\\"]", "\\\\$0")) - ); - } catch (Throwable e) { - LOG.log(Level.WARNING, "Failed to modify sudoers", e); - } - }, null); - } - break; - case MultiplayerManager.HiperExitEvent.INTERRUPTED: - // do nothing - break; - case MultiplayerManager.HiperExitEvent.FAILED_GET_DEVICE: - Controllers.dialog(i18n("multiplayer.error.failed_get_device")); - break; - case MultiplayerManager.HiperExitEvent.FAILED_LOAD_CONFIG: - Controllers.dialog(i18n("multiplayer.error.failed_load_config")); - break; - default: - Controllers.dialog(i18n("multiplayer.exit", event.getExitCode())); - break; - } - - clearSession(); - }); - } - - @Override - public ReadOnlyObjectProperty stateProperty() { - return state; - } -} 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 deleted file mode 100644 index 3110bceba..000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/multiplayer/MultiplayerPageSkin.java +++ /dev/null @@ -1,461 +0,0 @@ -/* - * 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; - -import com.jfoenix.controls.JFXButton; -import com.jfoenix.controls.JFXPasswordField; -import com.jfoenix.controls.JFXTextField; -import javafx.application.Platform; -import javafx.beans.InvalidationListener; -import javafx.beans.WeakInvalidationListener; -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.layout.*; -import javafx.stage.FileChooser; -import javafx.util.StringConverter; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.ui.FXUtils; -import org.jackhuang.hmcl.ui.SVG; -import org.jackhuang.hmcl.ui.construct.*; -import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; -import org.jackhuang.hmcl.ui.decorator.DecoratorAnimatedPage; -import org.jackhuang.hmcl.util.HMCLService; -import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.StringUtils; -import org.jackhuang.hmcl.util.i18n.Locales; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.concurrent.CompletableFuture; -import java.util.logging.Level; - -import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig; -import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap; -import static org.jackhuang.hmcl.util.Logging.LOG; -import static org.jackhuang.hmcl.util.i18n.I18n.i18n; - -public class MultiplayerPageSkin extends DecoratorAnimatedPage.DecoratorAnimatedPageSkin { - - private ObservableList clients; - - /** - * Constructor for all SkinBase instances. - * - * @param control The control for which this Skin should attach to. - */ - protected MultiplayerPageSkin(MultiplayerPage control) { - super(control); - - { - AdvancedListBox sideBar = new AdvancedListBox() - .addNavigationDrawerItem(item -> { - item.setTitle(i18n("version.launch")); - item.setLeftGraphic(wrap(SVG::rocketLaunchOutline)); - item.setOnAction(e -> { - control.launchGame(); - }); - }) - .startCategory(i18n("help")) - .addNavigationDrawerItem(item -> { - item.setTitle(i18n("help")); - item.setLeftGraphic(wrap(SVG::helpCircleOutline)); - item.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/multiplayer")); - }) -// .addNavigationDrawerItem(item -> { -// item.setTitle(i18n("multiplayer.help.1")); -// item.setLeftGraphic(wrap(SVG::helpCircleOutline)); -// item.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/multiplayer/admin.html")); -// }) - .addNavigationDrawerItem(item -> { - item.setTitle(i18n("multiplayer.help.2")); - item.setLeftGraphic(wrap(SVG::helpCircleOutline)); - item.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/multiplayer/help.html")); - }) - .addNavigationDrawerItem(item -> { - item.setTitle(i18n("multiplayer.help.3")); - item.setLeftGraphic(wrap(SVG::helpCircleOutline)); - item.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/multiplayer/help.html#%E5%88%9B%E5%BB%BA%E6%96%B9")); - }) - .addNavigationDrawerItem(item -> { - item.setTitle(i18n("multiplayer.help.4")); - item.setLeftGraphic(wrap(SVG::helpCircleOutline)); - item.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/multiplayer/help.html#%E5%8F%82%E4%B8%8E%E8%80%85")); - }) - .addNavigationDrawerItem(item -> { - item.setTitle(i18n("multiplayer.help.text")); - item.setLeftGraphic(wrap(SVG::rocketLaunchOutline)); - item.setOnAction(e -> FXUtils.openLink("https://docs.hmcl.net/multiplayer/text.html")); - }) - .addNavigationDrawerItem(report -> { - report.setTitle(i18n("feedback")); - report.setLeftGraphic(wrap(SVG::messageAlertOutline)); - report.setOnAction(e -> HMCLService.openRedirectLink("multiplayer-feedback")); - }); - FXUtils.setLimitWidth(sideBar, 200); - setLeft(sideBar); - } - - { - VBox content = new VBox(16); - content.setPadding(new Insets(10)); - content.setFillWidth(true); - ScrollPane scrollPane = new ScrollPane(content); - scrollPane.setFitToWidth(true); - setCenter(scrollPane); - - VBox mainPane = new VBox(16); - { - ComponentList offPane = new ComponentList(); - { - HintPane hintPane = new HintPane(MessageType.WARNING); - hintPane.setText(i18n("multiplayer.off.hint")); - - BorderPane tokenPane = new BorderPane(); - { - Label tokenTitle = new Label(i18n("multiplayer.token")); - BorderPane.setAlignment(tokenTitle, Pos.CENTER_LEFT); - tokenPane.setLeft(tokenTitle); - // Token acts like password, we hide it here preventing users from accidentally leaking their token when taking screenshots. - JFXPasswordField tokenField = new JFXPasswordField(); - BorderPane.setAlignment(tokenField, Pos.CENTER_LEFT); - BorderPane.setMargin(tokenField, new Insets(0, 8, 0, 8)); - tokenPane.setCenter(tokenField); - tokenField.textProperty().bindBidirectional(globalConfig().multiplayerTokenProperty()); - tokenField.setPromptText(i18n("multiplayer.token.prompt")); - - Validator validator = new Validator("multiplayer.token.format_invalid", StringUtils::isAlphabeticOrNumber); - InvalidationListener listener = any -> tokenField.validate(); - validator.getProperties().put(validator, listener); - tokenField.textProperty().addListener(new WeakInvalidationListener(listener)); - tokenField.getValidators().add(validator); - - JFXHyperlink applyLink = new JFXHyperlink(i18n("multiplayer.token.apply")); - BorderPane.setAlignment(applyLink, Pos.CENTER_RIGHT); - applyLink.setOnAction(e -> HMCLService.openRedirectLink("multiplayer-static-token")); - tokenPane.setRight(applyLink); - } - - HBox startPane = new HBox(); - { - JFXButton startButton = new JFXButton(i18n("multiplayer.off.start")); - startButton.getStyleClass().add("jfx-button-raised"); - startButton.setButtonType(JFXButton.ButtonType.RAISED); - startButton.setOnMouseClicked(e -> control.start()); - startButton.disableProperty().bind(MultiplayerManager.tokenInvalid); - - startPane.getChildren().setAll(startButton); - startPane.setAlignment(Pos.CENTER_RIGHT); - } - - if (!MultiplayerManager.IS_ADMINISTRATOR) - offPane.getContent().add(hintPane); - offPane.getContent().addAll(tokenPane, startPane); - } - - ComponentList onPane = new ComponentList(); - { - BorderPane expirationPane = new BorderPane(); - expirationPane.setLeft(new Label(i18n("multiplayer.session.expiration"))); - Label expirationLabel = new Label(); - expirationLabel.textProperty().bind(Bindings.createStringBinding(() -> - control.getExpireTime() == null ? "" : Locales.SIMPLE_DATE_FORMAT.get().format(control.getExpireTime()), - control.expireTimeProperty())); - expirationPane.setRight(expirationLabel); - - GridPane masterPane = new GridPane(); - masterPane.setVgap(8); - masterPane.setHgap(16); - ColumnConstraints titleColumn = new ColumnConstraints(); - ColumnConstraints valueColumn = new ColumnConstraints(); - ColumnConstraints rightColumn = new ColumnConstraints(); - masterPane.getColumnConstraints().setAll(titleColumn, valueColumn, rightColumn); - valueColumn.setFillWidth(true); - valueColumn.setHgrow(Priority.ALWAYS); - { - BorderPane titlePane = new BorderPane(); - GridPane.setColumnSpan(titlePane, 3); - Label title = new Label(i18n("multiplayer.master")); - titlePane.setLeft(title); - - JFXHyperlink tutorial = new JFXHyperlink(i18n("multiplayer.master.video_tutorial")); - titlePane.setRight(tutorial); - tutorial.setOnAction(e -> HMCLService.openRedirectLink("multiplayer-tutorial-master")); - masterPane.addRow(0, titlePane); - - HintPane hintPane = new HintPane(MessageType.INFO); - GridPane.setColumnSpan(hintPane, 3); - hintPane.setText(i18n("multiplayer.master.hint")); - masterPane.addRow(1, hintPane); - - Label portTitle = new Label(i18n("multiplayer.master.port")); - BorderPane.setAlignment(portTitle, Pos.CENTER_LEFT); - - JFXTextField portTextField = new JFXTextField(); - GridPane.setColumnSpan(portTextField, 2); - FXUtils.setValidateWhileTextChanged(portTextField, true); - portTextField.getValidators().add(new Validator(i18n("multiplayer.master.port.validate"), (text) -> { - Integer value = Lang.toIntOrNull(text); - return value != null && 0 <= value && value <= 65535; - })); - portTextField.textProperty().bindBidirectional(control.portProperty(), new StringConverter() { - @Override - public String toString(Number object) { - return Integer.toString(object.intValue()); - } - - @Override - public Number fromString(String string) { - return Lang.parseInt(string, 0); - } - }); - masterPane.addRow(2, portTitle, portTextField); - - Label serverAddressTitle = new Label(i18n("multiplayer.master.server_address")); - BorderPane.setAlignment(serverAddressTitle, Pos.CENTER_LEFT); - Label serverAddressLabel = new Label(); - BorderPane.setAlignment(serverAddressLabel, Pos.CENTER_LEFT); - serverAddressLabel.textProperty().bind(Bindings.createStringBinding(() -> { - return (control.getAddress() == null ? "" : control.getAddress()) + ":" + control.getPort(); - }, control.addressProperty(), control.portProperty())); - JFXButton copyButton = new JFXButton(i18n("multiplayer.master.server_address.copy")); - copyButton.setOnAction(e -> FXUtils.copyText(serverAddressLabel.getText())); - masterPane.addRow(3, serverAddressTitle, serverAddressLabel, copyButton); - } - - VBox slavePane = new VBox(8); - { - BorderPane titlePane = new BorderPane(); - Label title = new Label(i18n("multiplayer.slave")); - titlePane.setLeft(title); - - JFXHyperlink tutorial = new JFXHyperlink(i18n("multiplayer.slave.video_tutorial")); - tutorial.setOnAction(e -> HMCLService.openRedirectLink("multiplayer-tutorial-slave")); - titlePane.setRight(tutorial); - - HintPane hintPane = new HintPane(MessageType.INFO); - GridPane.setColumnSpan(hintPane, 3); - hintPane.setText(i18n("multiplayer.slave.hint")); - slavePane.getChildren().add(hintPane); - - HintPane hintPane2 = new HintPane(MessageType.WARNING); - GridPane.setColumnSpan(hintPane2, 3); - hintPane2.setText(i18n("multiplayer.slave.hint2")); - slavePane.getChildren().add(hintPane2); - - GridPane notBroadcastingPane = new GridPane(); - { - notBroadcastingPane.setVgap(8); - notBroadcastingPane.setHgap(16); - notBroadcastingPane.getColumnConstraints().setAll(titleColumn, valueColumn, rightColumn); - - Label addressTitle = new Label(i18n("multiplayer.slave.server_address")); - - JFXTextField addressField = new JFXTextField(); - FXUtils.setValidateWhileTextChanged(addressField, true); - addressField.getValidators().add(new ServerAddressValidator()); - - JFXButton startButton = new JFXButton(i18n("multiplayer.slave.server_address.start")); - startButton.setOnAction(e -> control.broadcast(addressField.getText())); - notBroadcastingPane.addRow(0, addressTitle, addressField, startButton); - } - - GridPane broadcastingPane = new GridPane(); - { - broadcastingPane.setVgap(8); - broadcastingPane.setHgap(16); - broadcastingPane.getColumnConstraints().setAll(titleColumn, valueColumn, rightColumn); - - Label addressTitle = new Label(i18n("multiplayer.slave.server_address")); - Label addressLabel = new Label(); - addressLabel.textProperty().bind(Bindings.createStringBinding(() -> - control.getBroadcaster() != null ? control.getBroadcaster().getAddress() : "", - control.broadcasterProperty())); - - JFXButton stopButton = new JFXButton(i18n("multiplayer.slave.server_address.stop")); - stopButton.setOnAction(e -> control.stopBroadcasting()); - broadcastingPane.addRow(0, addressTitle, addressLabel, stopButton); - } - - FXUtils.onChangeAndOperate(control.broadcasterProperty(), broadcaster -> { - if (broadcaster == null) { - slavePane.getChildren().setAll(titlePane, hintPane, hintPane2, notBroadcastingPane); - } else { - slavePane.getChildren().setAll(titlePane, hintPane, hintPane2, broadcastingPane); - } - }); - } - - FXUtils.onChangeAndOperate(control.expireTimeProperty(), t -> { - if (t == null) { - onPane.getContent().setAll(masterPane, slavePane); - } else { - onPane.getContent().setAll(expirationPane, masterPane, slavePane); - } - }); - } - - FXUtils.onChangeAndOperate(getSkinnable().sessionProperty(), session -> { - if (session == null) { - mainPane.getChildren().setAll(offPane); - } else { - mainPane.getChildren().setAll(onPane); - } - }); - } - - ComponentList persistencePane = new ComponentList(); - { - HintPane hintPane = new HintPane(MessageType.WARNING); - hintPane.setText(i18n("multiplayer.persistence.hint")); - - BorderPane importPane = new BorderPane(); - { - Label left = new Label(i18n("multiplayer.persistence.import")); - BorderPane.setAlignment(left, Pos.CENTER_LEFT); - importPane.setLeft(left); - - JFXButton importButton = new JFXButton(i18n("multiplayer.persistence.import.button")); - importButton.setOnMouseClicked(e -> { - Path targetPath = MultiplayerManager.getConfigPath(globalConfig().getMultiplayerToken()); - if (Files.exists(targetPath)) { - LOG.warning("License file " + targetPath + " already exists"); - Controllers.dialog(i18n("multiplayer.persistence.import.file_already_exists"), null, MessageType.ERROR); - return; - } - - FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle(i18n("multiplayer.persistence.import.title")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("multiplayer.persistence.license_file"), "*.yml")); - - File file = fileChooser.showOpenDialog(Controllers.getStage()); - if (file == null) - return; - - CompletableFuture future = new CompletableFuture<>(); - if (file.getName().matches("[a-z0-9]{40}.yml") && !targetPath.getFileName().toString().equals(file.getName())) { - Controllers.confirm(i18n("multiplayer.persistence.import.token_not_match"), null, MessageType.QUESTION, - () -> future.complete(true), - () -> future.complete(false)) ; - } else { - future.complete(true); - } - future.thenAcceptAsync(Lang.wrapConsumer(c -> { - if (c) Files.copy(file.toPath(), targetPath); - })).exceptionally(exception -> { - LOG.log(Level.WARNING, "Failed to import license file", exception); - Platform.runLater(() -> Controllers.dialog(i18n("multiplayer.persistence.import.failed"), null, MessageType.ERROR)); - return null; - }); - }); - importButton.disableProperty().bind(MultiplayerManager.tokenInvalid); - importButton.getStyleClass().add("jfx-button-border"); - importPane.setRight(importButton); - } - - BorderPane exportPane = new BorderPane(); - { - Label left = new Label(i18n("multiplayer.persistence.export")); - BorderPane.setAlignment(left, Pos.CENTER_LEFT); - exportPane.setLeft(left); - - JFXButton exportButton = new JFXButton(i18n("multiplayer.persistence.export.button")); - exportButton.setOnMouseClicked(e -> { - String token = globalConfig().getMultiplayerToken(); - Path configPath = MultiplayerManager.getConfigPath(token); - - FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle(i18n("multiplayer.persistence.export.title")); - fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("multiplayer.persistence.license_file"), "*.yml")); - fileChooser.setInitialFileName(configPath.getFileName().toString()); - - File file = fileChooser.showSaveDialog(Controllers.getStage()); - if (file == null) - return; - - CompletableFuture.runAsync(Lang.wrap(() -> MultiplayerManager.downloadHiperConfig(token, configPath)), Schedulers.io()) - .handleAsync((ignored, exception) -> { - if (exception != null) { - LOG.log(Level.INFO, "Unable to download hiper config file", e); - } - - if (!Files.isRegularFile(configPath)) { - LOG.warning("License file " + configPath + " not exists"); - Platform.runLater(() -> Controllers.dialog(i18n("multiplayer.persistence.export.file_not_exists"), null, MessageType.ERROR)); - return null; - } - - try { - Files.copy(configPath, file.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException ioException) { - LOG.log(Level.WARNING, "Failed to export license file", ioException); - Platform.runLater(() -> Controllers.dialog(i18n("multiplayer.persistence.export.failed"), null, MessageType.ERROR)); - } - - return null; - }); - - }); - exportButton.disableProperty().bind(MultiplayerManager.tokenInvalid); - exportButton.getStyleClass().add("jfx-button-border"); - exportPane.setRight(exportButton); - } - - persistencePane.getContent().setAll(hintPane, importPane, exportPane); - } - - - ComponentList thanksPane = new ComponentList(); - { - HBox pane = new HBox(); - pane.setAlignment(Pos.CENTER_LEFT); - - JFXHyperlink aboutLink = new JFXHyperlink(i18n("about")); - aboutLink.setOnAction(e -> HMCLService.openRedirectLink("multiplayer-about")); - - HBox placeholder = new HBox(); - HBox.setHgrow(placeholder, Priority.ALWAYS); - - pane.getChildren().setAll( - new Label("Based on HiPer"), - aboutLink, - placeholder, - FXUtils.segmentToTextFlow(i18n("multiplayer.powered_by"), Controllers::onHyperlinkAction)); - - thanksPane.getContent().addAll(pane); - } - - content.getChildren().setAll( - mainPane, - ComponentList.createComponentListTitle(i18n("multiplayer.persistence")), - persistencePane, - ComponentList.createComponentListTitle(i18n("about")), - thanksPane - ); - } - } - -}