diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/TerracottaManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/TerracottaManager.java index c05b89025..480a3e12a 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/TerracottaManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/TerracottaManager.java @@ -138,16 +138,21 @@ public final class TerracottaManager { ReadOnlyDoubleWrapper progress = new ReadOnlyDoubleWrapper(0); TerracottaState.Preparing preparing = new TerracottaState.Preparing(progress.getReadOnlyProperty()); - Objects.requireNonNull(TerracottaMetadata.PROVIDER).install(progress).thenRunAsync(() -> { - TerracottaState.Launching launching = new TerracottaState.Launching(); - if (compareAndSet(preparing, launching)) { - launch(launching); - } - }).whenComplete(exception -> { - compareAndSet(preparing, TerracottaState.Fatal.INSTANCE); - }).start(); + try { + Objects.requireNonNull(TerracottaMetadata.PROVIDER).install(progress).thenRunAsync(() -> { + TerracottaState.Launching launching = new TerracottaState.Launching(); + if (compareAndSet(preparing, launching)) { + launch(launching); + } + }).whenComplete(exception -> { + compareAndSet(preparing, TerracottaState.Fatal.INSTANCE); + }).start(); - return setState(preparing); + return setState(preparing); + } catch (Exception e) { + setState(TerracottaState.Fatal.INSTANCE); + return null; + } } private static void launch(TerracottaState.Launching state) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/TerracottaMetadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/TerracottaMetadata.java index 6e9cdd8c9..2565f9aca 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/TerracottaMetadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/TerracottaMetadata.java @@ -1,9 +1,11 @@ package org.jackhuang.hmcl.ui.terracotta.core; -import org.jackhuang.hmcl.ui.terracotta.core.provider.ExecutableProvider; +import org.jackhuang.hmcl.ui.terracotta.core.provider.GeneralProvider; import org.jackhuang.hmcl.ui.terracotta.core.provider.ITerracottaProvider; +import org.jackhuang.hmcl.ui.terracotta.core.provider.MacOSProvider; import java.net.URI; +import java.util.List; public final class TerracottaMetadata { private TerracottaMetadata() { @@ -11,15 +13,28 @@ public final class TerracottaMetadata { public static final String VERSION = "0.3.8-rc.1"; - public static final URI WINDOWS_X86_64 = URI.create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-windows-x86_64.exe", VERSION)); - public static final URI WINDOWS_ARM64 = URI.create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-windows-arm64.exe", VERSION)); - public static final URI LINUX_X86_64 = URI.create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-linux-x86_64", VERSION)); - public static final URI LINUX_ARM64 = URI.create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-linux-arm64", VERSION)); + public static final List WINDOWS_X86_64 = create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-windows-x86_64.exe", VERSION)); + public static final List WINDOWS_ARM64 = create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-windows-arm64.exe", VERSION)); + + public static final List LINUX_X86_64 = create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-linux-x86_64", VERSION)); + public static final List LINUX_ARM64 = create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-linux-arm64", VERSION)); + + public static final List MACOS_INSTALLER_X86_64 = create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-macos-x86_64.pkg", VERSION)); + public static final List MACOS_INSTALLER_ARM64 = create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-macos-arm64.pkg", VERSION)); + public static final List MACOS_BIN_X86_64 = create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-macos-x86_64", VERSION)); + public static final List MACOS_BIN_ARM64 = create(String.format("https://github.com/burningtnt/Terracotta/releases/download/V%1$s/terracotta-%1$s-macos-arm64", VERSION)); + + private static List create(String s) { + return List.of(URI.create("https://ghfast.top/" + s), URI.create("https://cdn.crashmc.com/" + s), URI.create(s)); + } public static final ITerracottaProvider PROVIDER = locateProvider(); + private static ITerracottaProvider locateProvider() { - if (ExecutableProvider.TARGET != null) { - return new ExecutableProvider(); + if (GeneralProvider.TARGET != null) { + return new GeneralProvider(); + } else if (MacOSProvider.INSTALLER != null) { + return new MacOSProvider(); } else { return null; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/ExecutableProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/GeneralProvider.java similarity index 76% rename from HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/ExecutableProvider.java rename to HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/GeneralProvider.java index 579c5283c..dc0d80a9c 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/ExecutableProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/GeneralProvider.java @@ -11,22 +11,23 @@ import org.jackhuang.hmcl.util.platform.Platform; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.nio.file.attribute.PosixFilePermission; import java.util.List; import java.util.Map; import java.util.Set; -public final class ExecutableProvider implements ITerracottaProvider { - public static final URI TARGET = Map.of( +public final class GeneralProvider implements ITerracottaProvider { + public static final List TARGET = Map.of( Platform.WINDOWS_X86_64, TerracottaMetadata.WINDOWS_X86_64, Platform.WINDOWS_ARM64, TerracottaMetadata.WINDOWS_ARM64, Platform.LINUX_X86_64, TerracottaMetadata.LINUX_X86_64, Platform.LINUX_ARM64, TerracottaMetadata.LINUX_ARM64 ).get(Platform.SYSTEM_PLATFORM); - private static final Path PATH = Metadata.DEPENDENCIES_DIRECTORY.resolve(String.format( - "terracota/%s/%s", TerracottaMetadata.VERSION, Path.of(TARGET.getPath()).getFileName() - )).toAbsolutePath(); + private static final Path PATH = TARGET != null ? Metadata.DEPENDENCIES_DIRECTORY.resolve(String.format( + "terracota/%s/%s", TerracottaMetadata.VERSION, Path.of(TARGET.get(0).getPath()).getFileName() + )).toAbsolutePath() : null; @Override public boolean exist() { @@ -35,8 +36,12 @@ public final class ExecutableProvider implements ITerracottaProvider { @Override public Task install(DoubleProperty progress) { - Task task = new FileDownloadTask(TARGET, PATH); + Path tmp = PATH.resolveSibling(PATH.getFileName() + ".tmp"); + + Task task = new FileDownloadTask(TARGET, tmp); progress.bind(task.progressProperty()); + task = task.thenRunAsync(() -> Files.move(tmp, PATH, StandardCopyOption.REPLACE_EXISTING)); + if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) { task = task.thenRunAsync(() -> Files.setPosixFilePermissions(PATH, Set.of( PosixFilePermission.OWNER_READ, diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/ITerracottaProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/ITerracottaProvider.java index 8ed8320a7..5121beb92 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/ITerracottaProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/ITerracottaProvider.java @@ -3,13 +3,14 @@ package org.jackhuang.hmcl.ui.terracotta.core.provider; import javafx.beans.property.DoubleProperty; import org.jackhuang.hmcl.task.Task; +import java.io.IOException; import java.nio.file.Path; import java.util.List; public interface ITerracottaProvider { boolean exist(); - Task install(DoubleProperty progress); + Task install(DoubleProperty progress) throws IOException; List launch(Path path); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/MacOSProvider.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/MacOSProvider.java new file mode 100644 index 000000000..7ae5bdf9e --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/terracotta/core/provider/MacOSProvider.java @@ -0,0 +1,93 @@ +package org.jackhuang.hmcl.ui.terracotta.core.provider; + +import javafx.beans.property.DoubleProperty; +import org.jackhuang.hmcl.Metadata; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.task.Task; +import org.jackhuang.hmcl.ui.terracotta.core.TerracottaMetadata; +import org.jackhuang.hmcl.util.platform.Architecture; +import org.jackhuang.hmcl.util.platform.ManagedProcess; +import org.jackhuang.hmcl.util.platform.SystemUtils; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.util.List; +import java.util.Set; + +import static org.jackhuang.hmcl.util.i18n.I18n.i18n; + +public final class MacOSProvider implements ITerracottaProvider { + public static final List INSTALLER, BINARY; + + static { + if (Architecture.SYSTEM_ARCH == Architecture.X86_64) { + INSTALLER = TerracottaMetadata.MACOS_INSTALLER_X86_64; + BINARY = TerracottaMetadata.MACOS_BIN_X86_64; + } else if (Architecture.SYSTEM_ARCH == Architecture.ARM64) { + INSTALLER = TerracottaMetadata.MACOS_INSTALLER_ARM64; + BINARY = TerracottaMetadata.MACOS_BIN_ARM64; + } else { + INSTALLER = null; + BINARY = null; + } + } + + private static final Path PATH = BINARY != null ? Metadata.DEPENDENCIES_DIRECTORY.resolve(String.format( + "terracota/%s/%s", TerracottaMetadata.VERSION, Path.of(BINARY.get(0).getPath()).getFileName() + )).toAbsolutePath() : null; + + @Override + public boolean exist() { + return Files.exists(Path.of("/Applications/terracotta.app")) && Files.exists(PATH); + } + + @Override + public Task install(DoubleProperty progress) throws IOException { + Path installer = Files.createTempFile("hmcl-terracotta-installer-", ".pkg").toAbsolutePath(); + Task installerTask = new FileDownloadTask(INSTALLER, installer); + + Path binary = PATH.resolveSibling(PATH.getFileName() + ".tmp"); + Task binaryTask = new FileDownloadTask(BINARY, binary); + + progress.bind(installerTask.progressProperty().add(binaryTask.progressProperty()).multiply(0.3)); + + installerTask = installerTask.thenComposeAsync(() -> { + ManagedProcess process = new ManagedProcess(new ProcessBuilder( + "osascript", + "-e", + String.format( + "do shell script \"%s\" with prompt \"%s\" with administrator privileges", + String.format("installer -pkg %s -target /Applications", installer), + i18n("terracotta.sudo_installing") + ) + )); + process.pumpInputStream(SystemUtils::onLogLine); + process.pumpErrorStream(SystemUtils::onLogLine); + + return Task.fromCompletableFuture(process.getProcess().onExit()); + }); + binaryTask = binaryTask.thenRunAsync(() -> { + Files.move(binary, PATH, StandardCopyOption.REPLACE_EXISTING); + Files.setPosixFilePermissions(PATH, Set.of( + PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OWNER_EXECUTE, + PosixFilePermission.GROUP_READ, + PosixFilePermission.GROUP_EXECUTE, + PosixFilePermission.OTHERS_READ, + PosixFilePermission.OTHERS_EXECUTE + )); + }); + + return Task.allOf(installerTask, binaryTask); + } + + @Override + public List launch(Path path) { + return List.of(PATH.toString(), "--hmcl", path.toString()); + } +} 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 d7c156ceb..62cc46cdb 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -1195,6 +1195,7 @@ terracotta=多人联机 terracotta.status=状态 terracotta.back=退出 terracotta.network_warning=多人联机基于 p2p,最终联机体验和您的网络情况有较大关系。 +terracotta.sudo_installing=HMCL 需要验证您的密码才能安装多人联机核心。 terracotta.status.uninitialized=未下载多人联机核心 terracotta.status.uninitialized.title=下载多人联机核心(约 8MB) terracotta.status.uninitialized.desc=您承诺,多人联机全过程中,您将严格遵守您所在国家或地区的全部法律法规 @@ -1227,7 +1228,7 @@ terracotta.status.exception.desc.ping_host_rst=房间连接断开:房间已关 terracotta.status.exception.desc.guest_et_crash=加入房间失败:EasyTier 已崩溃,请向开发者反馈该问题 terracotta.status.exception.desc.host_et_crash=创建房间失败:EasyTier 已崩溃,请向开发者反馈该问题 terracotta.status.exception.desc.ping_server_rst=房间已关闭:您已退出游戏存档,房间已自动关闭 -terracotta.status.fatal=严重错误,您的设备可能无法正常使用多人联机功能 +terracotta.status.fatal=严重错误,您的设备无法正常使用多人联机功能 unofficial.hint=你正在使用非官方构建的 HMCL。我们无法保证其安全性,请注意甄别。