Compare commits

...
16 Commits
Author SHA1 Message Date
GlavoandBurning_TNT 861816c0ad [release/3.7] Bump Terracotta to 0.3.14 (#4840)
https://github.com/HMCL-dev/HMCL/pull/4838

Co-authored-by: Burning_TNT <pangyl08@163.com>
2025-11-22 17:16:49 +08:00
Glavo d28d98fc54 [release/3.7] 使用 EdgeOne 分发 HMCL 元数据 (#4841)
https://github.com/HMCL-dev/HMCL/pull/4830
2025-11-22 17:14:24 +08:00
GlavoandmineDiamond 81c2418b54 [release/3.7] 添加对 1.21.11-pre1/2_unobfuscated 的支持 (#4842)
https://github.com/HMCL-dev/HMCL/pull/4829

Co-authored-by: mineDiamond <mine_yaoym@outlook.com>
2025-11-22 17:03:31 +08:00
Glavo f8dd5bfea4 [release/3.7] 更新陶瓦联机用户须知 (#4824)
https://github.com/HMCL-dev/HMCL/pull/4816
2025-11-18 23:09:01 +08:00
Glavo ffef3ac7df [release/3.7] 使用低版本 Java 启动且带有 --apply-to 参数时提醒用户手动下载最新版本 HMCL (#4819)
https://github.com/HMCL-dev/HMCL/pull/4793
2025-11-18 20:23:41 +08:00
Glavo 686ec172af [release/3.7] 添加对 unobfuscated 版本的支持 (#4820)
https://github.com/HMCL-dev/HMCL/pull/4767
https://github.com/HMCL-dev/HMCL/pull/4782
2025-11-18 20:22:46 +08:00
Glavoand辞庐 b2e6d5665e [release/3.7] 修复 Windows 下全局设置中本地库路径路径分隔符显示错误的问题 (#4818)
https://github.com/HMCL-dev/HMCL/pull/4808

Co-authored-by: 辞庐 <109708109+CiiLu@users.noreply.github.com>
2025-11-18 16:56:07 +08:00
Glavo 01f0a38f1c [release/3.7] 修复初次打开部分页面时页面闪烁的问题 (#4817)
https://github.com/HMCL-dev/HMCL/pull/4795
2025-11-18 16:55:30 +08:00
6375558591 [release/3.7] 优化陶瓦联机 (#4751)
https://github.com/HMCL-dev/HMCL/pull/4723
https://github.com/HMCL-dev/HMCL/pull/4750
https://github.com/HMCL-dev/HMCL/pull/4735

---------

Co-authored-by: Burning_TNT <pangyl08@163.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-03 20:14:05 +08:00
GlavoandWulian233 12b525fdc1 [release/3.7] 更新 Chunkbase 种子版本 (#4753)
https://github.com/HMCL-dev/HMCL/pull/4742

Co-authored-by: Wulian233 <1055917385@qq.com>
2025-11-02 22:14:36 +08:00
Glavo ca69dd8032 [release/3.7] 修复 Platform.FREEBSD_X86_64 架构错误的问题 (#4752)
https://github.com/HMCL-dev/HMCL/pull/4736
2025-11-02 22:14:06 +08:00
Glavo 95cbc6f9b0 [release/3.7] Update PLATFORM.md (#4732)
https://github.com/HMCL-dev/HMCL/pull/4717
2025-11-01 16:45:37 +08:00
Glavo d0145d53d7 [release/3.7] Fix #4720: 修复 JFXListViewSkin 创建大量平台线程的问题 (#4731)
https://github.com/HMCL-dev/HMCL/pull/4721
2025-10-28 20:26:22 +08:00
Glavo ee0ae6e4ec [release/3.7] 将官方群组入口重新移动到主界面 (#4730)
https://github.com/HMCL-dev/HMCL/pull/4727
2025-10-28 20:18:34 +08:00
GlavoandBurning_TNT 210ab077cb [release/3.7] Bump Terracotta to 0.3.12 (#4729)
https://github.com/HMCL-dev/HMCL/pull/4718
https://github.com/HMCL-dev/HMCL/pull/4724

---------

Co-authored-by: Burning_TNT <pangyl08@163.com>
2025-10-28 20:16:10 +08:00
GlavoandBurning_TNT 341c60b0de [release/3.7] 优化陶瓦联机 (#4708)
https://github.com/HMCL-dev/HMCL/pull/4682
https://github.com/HMCL-dev/HMCL/pull/4686
https://github.com/HMCL-dev/HMCL/pull/4687
https://github.com/HMCL-dev/HMCL/pull/4688
https://github.com/HMCL-dev/HMCL/pull/4700
https://github.com/HMCL-dev/HMCL/pull/4705

---------

Co-authored-by: Burning_TNT <pangyl08@163.com>
2025-10-21 15:06:10 +08:00
46 changed files with 1264 additions and 301 deletions

No files matched your search

@@ -0,0 +1,91 @@
// Copy from https://github.com/sshahine/JFoenix/blob/d427fd801a338f934307ba41ce604eb5c79f0b20/jfoenix/src/main/java/com/jfoenix/skins/JFXListViewSkin.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.jfoenix.skins;
import com.jfoenix.adapters.skins.ListViewSkin;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.effects.JFXDepthManager;
import javafx.scene.control.ListCell;
import javafx.scene.control.skin.VirtualFlow;
import javafx.scene.layout.Region;
// https://github.com/HMCL-dev/HMCL/issues/4720
public class JFXListViewSkin<T> extends ListViewSkin<T> {
private final VirtualFlow<ListCell<T>> flow;
@SuppressWarnings("unchecked")
public JFXListViewSkin(final JFXListView<T> listView) {
super(listView);
flow = (VirtualFlow<ListCell<T>>) getChildren().get(0);
JFXDepthManager.setDepth(flow, listView.depthProperty().get());
listView.depthProperty().addListener((o, oldVal, newVal) -> JFXDepthManager.setDepth(flow, newVal));
}
@Override
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
return 200;
}
@Override
protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) {
final int itemsCount = getSkinnable().getItems().size();
if (getSkinnable().maxHeightProperty().isBound() || itemsCount <= 0) {
return super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
}
final double fixedCellSize = getSkinnable().getFixedCellSize();
double computedHeight = fixedCellSize != Region.USE_COMPUTED_SIZE ?
fixedCellSize * itemsCount + snapVerticalInsets() : estimateHeight();
double height = super.computePrefHeight(width, topInset, rightInset, bottomInset, leftInset);
if (height > computedHeight) {
height = computedHeight;
}
if (getSkinnable().getMaxHeight() > 0 && computedHeight > getSkinnable().getMaxHeight()) {
return getSkinnable().getMaxHeight();
}
return height;
}
private double estimateHeight() {
// compute the border/padding for the list
double borderWidth = snapVerticalInsets();
// compute the gap between list cells
JFXListView<T> listview = (JFXListView<T>) getSkinnable();
double gap = listview.isExpanded() ? ((JFXListView<T>) getSkinnable()).getVerticalGap() * (getSkinnable().getItems()
.size()) : 0;
// compute the height of each list cell
double cellsHeight = 0;
for (int i = 0; i < flow.getCellCount(); i++) {
ListCell<T> cell = flow.getCell(i);
cellsHeight += cell.getHeight();
}
return cellsHeight + gap + borderWidth;
}
private double snapVerticalInsets() {
return getSkinnable().snappedBottomInset() + getSkinnable().snappedTopInset();
}
}
@@ -45,6 +45,7 @@ import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
@@ -61,22 +62,20 @@ public final class TerracottaManager {
private static final InvocationDispatcher<TerracottaState> STATE_D = InvocationDispatcher.runOn(Platform::runLater, STATE::set);
static {
Task.runAsync(() -> {
if (TerracottaMetadata.PROVIDER == null) {
setState(new TerracottaState.Fatal(TerracottaState.Fatal.Type.OS));
LOG.warning("Terracotta hasn't support your OS: " + org.jackhuang.hmcl.util.platform.Platform.SYSTEM_PLATFORM);
} else {
Schedulers.io().execute(() -> {
try {
if (TerracottaMetadata.PROVIDER == null)
throw new IOException("Unsupported platform: " + org.jackhuang.hmcl.util.platform.Platform.CURRENT_PLATFORM);
switch (TerracottaMetadata.PROVIDER.status()) {
case NOT_EXIST -> setState(new TerracottaState.Uninitialized(false));
case LEGACY_VERSION -> setState(new TerracottaState.Uninitialized(true));
case READY -> launch(setState(new TerracottaState.Launching()));
}
}
}).whenComplete(exception -> {
if (exception != null) {
} catch (Exception e) {
compareAndSet(TerracottaState.Bootstrap.INSTANCE, new TerracottaState.Fatal(TerracottaState.Fatal.Type.UNKNOWN));
}
}).start();
});
}
public static ReadOnlyObjectProperty<TerracottaState> stateProperty() {
@@ -143,12 +142,7 @@ public final class TerracottaManager {
return null;
}
TerracottaState.Preparing preparing;
if (state instanceof TerracottaState.Preparing it) {
preparing = it;
} else {
preparing = new TerracottaState.Preparing(new ReadOnlyDoubleWrapper(-1));
}
TerracottaState.Preparing preparing = new TerracottaState.Preparing(new ReadOnlyDoubleWrapper(-1));
Task.supplyAsync(Schedulers.io(), () -> {
return file != null ? TarFileTree.open(file) : null;
@@ -173,6 +167,7 @@ public final class TerracottaManager {
if (compareAndSet(preparing, launching)) {
launch(launching);
}
} else if (exception instanceof CancellationException) {
} else if (exception instanceof ITerracottaProvider.ArchiveFileMissingException) {
LOG.warning("Cannot install terracotta from local package.", exception);
compareAndSet(preparing, new TerracottaState.Fatal(TerracottaState.Fatal.Type.INSTALL));
@@ -183,7 +178,7 @@ public final class TerracottaManager {
}
}).start();
return setState(preparing);
return compareAndSet(state, preparing) ? preparing : null;
}
private static ITerracottaProvider getProvider() {
@@ -23,7 +23,9 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.terracotta.provider.GeneralProvider;
import org.jackhuang.hmcl.terracotta.provider.ITerracottaProvider;
import org.jackhuang.hmcl.terracotta.provider.MacOSProvider;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
import org.jackhuang.hmcl.util.i18n.LocalizedText;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
@@ -42,7 +44,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -61,20 +62,25 @@ public final class TerracottaMetadata {
@SerializedName("classifiers") Map<String, String> classifiers,
@SerializedName("downloads") List<String> downloads,
@SerializedName("downloads_CN") List<String> downloadsCN,
@SerializedName("links") List<Link> links
) {
private TerracottaNative of(String classifier) {
List<URI> links = new ArrayList<>(this.downloads.size());
for (String download : this.downloads) {
private @Nullable TerracottaNative of(String classifier) {
String hash = this.classifiers.get(classifier);
if (hash == null)
return null;
if (!hash.startsWith("sha256:"))
throw new IllegalArgumentException(String.format("Invalid hash value %s for classifier %s.", hash, classifier));
hash = hash.substring("sha256:".length());
List<URI> links = new ArrayList<>(this.downloads.size() + this.downloadsCN.size());
for (String download : LocaleUtils.IS_CHINA_MAINLAND
? Lang.merge(this.downloadsCN, this.downloads)
: Lang.merge(this.downloads, this.downloadsCN)) {
links.add(URI.create(download.replace("${version}", this.latest).replace("${classifier}", classifier)));
}
String hash = Objects.requireNonNull(this.classifiers.get(classifier), String.format("Classifier %s doesn't exist.", classifier));
if (!hash.startsWith("sha256:")) {
throw new IllegalArgumentException(String.format("Invalid hash value %s for classifier %s.", hash, classifier));
}
hash = hash.substring("sha256:".length());
return new TerracottaNative(
Collections.unmodifiableList(links),
Metadata.DEPENDENCIES_DIRECTORY.resolve(
@@ -138,36 +144,31 @@ public final class TerracottaMetadata {
@Nullable
private static ProviderContext locateProvider(Config config) {
String architecture = switch (Architecture.SYSTEM_ARCH) {
case X86_64 -> "x86_64";
case ARM64 -> "arm64";
default -> null;
};
if (architecture == null) {
return null;
}
String arch = Architecture.SYSTEM_ARCH.getCheckedName();
return switch (OperatingSystem.CURRENT_OS) {
case WINDOWS -> {
if (OperatingSystem.SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_8_1)) {
yield new ProviderContext(
new GeneralProvider(config.of(String.format("windows-%s.exe", architecture))),
"windows", architecture
);
}
yield null;
if (!OperatingSystem.SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_10))
yield null;
TerracottaNative target = config.of("windows-%s.exe".formatted(arch));
yield target != null
? new ProviderContext(new GeneralProvider(target), "windows", arch)
: null;
}
case LINUX -> {
TerracottaNative target = config.of("linux-%s".formatted(arch));
yield target != null
? new ProviderContext(new GeneralProvider(target), "linux", arch)
: null;
}
case MACOS -> {
TerracottaNative installer = config.of("macos-%s.pkg".formatted(arch));
TerracottaNative binary = config.of("macos-%s".formatted(arch));
yield installer != null && binary != null
? new ProviderContext(new MacOSProvider(installer, binary), "macos", arch)
: null;
}
case LINUX -> new ProviderContext(
new GeneralProvider(config.of(String.format("linux-%s", architecture))),
"linux", architecture
);
case MACOS -> new ProviderContext(
new MacOSProvider(
config.of(String.format("macos-%s.pkg", architecture)),
config.of(String.format("macos-%s", architecture))
),
"macos", architecture
);
default -> null;
};
}
@@ -298,7 +298,7 @@ public abstract sealed class TerracottaState {
}
public boolean isRecoverable() {
return this.type != Type.OS && this.type != Type.UNKNOWN;
return this.type != Type.UNKNOWN;
}
}
}
@@ -17,8 +17,10 @@
*/
package org.jackhuang.hmcl.terracotta.provider;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.terracotta.TerracottaNative;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.ManagedProcess;
import org.jackhuang.hmcl.util.platform.SystemUtils;
import org.jackhuang.hmcl.util.tree.TarFileTree;
@@ -27,11 +29,13 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
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;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class MacOSProvider implements ITerracottaProvider {
public final TerracottaNative installer, binary;
@@ -62,19 +66,39 @@ public final class MacOSProvider implements ITerracottaProvider {
return Task.allOf(
installerTask.thenComposeAsync(() -> {
Path osascript = SystemUtils.which("osascript");
if (osascript == null) {
throw new IllegalStateException("Cannot locate 'osascript' system executable on MacOS for installing Terracotta.");
}
Path pkg = Files.createTempDirectory(Metadata.HMCL_GLOBAL_DIRECTORY, "terracotta-pkg")
.toRealPath()
.resolve(FileUtils.getName(installer.getPath()));
Files.copy(installer.getPath(), pkg, StandardCopyOption.REPLACE_EXISTING);
ManagedProcess process = new ManagedProcess(new ProcessBuilder(
"osascript",
"-e",
String.format(
"do shell script \"installer -pkg %s -target /\" with prompt \"%s\" with administrator privileges",
installer.getPath(),
i18n("terracotta.sudo_installing")
)
));
osascript.toString(), "-e", String.format(
"do shell script \"installer -pkg '%s' -target /\" with prompt \"%s\" with administrator privileges",
pkg, i18n("terracotta.sudo_installing")
)));
process.pumpInputStream(SystemUtils::onLogLine);
process.pumpErrorStream(SystemUtils::onLogLine);
return Task.fromCompletableFuture(process.getProcess().onExit());
return Task.fromCompletableFuture(process.getProcess().onExit()).thenRunAsync(() -> {
try {
FileUtils.cleanDirectory(pkg.getParent());
} catch (IOException e) {
LOG.warning("Cannot remove temporary Terracotta package file.", e);
}
if (process.getExitCode() != 0) {
throw new IllegalStateException(String.format(
"Cannot install Terracotta %s: system installer exited with code %d",
pkg,
process.getExitCode()
));
}
});
}),
binaryTask.thenRunAsync(() -> Files.setPosixFilePermissions(binary.getPath(), Set.of(
PosixFilePermission.OWNER_READ,
@@ -64,6 +64,7 @@ import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jetbrains.annotations.Nullable;
import java.nio.file.Path;
import java.util.List;
@@ -433,26 +434,34 @@ public final class Controllers {
dialog(new MessageDialogPane.Builder(text, title, type).actionOrCancel(actionButton, cancel).build());
}
public static void confirmActionDanger(String text, String title, Runnable resolve, Runnable cancel) {
JFXButton btnYes = new JFXButton(i18n("button.ok"));
btnYes.getStyleClass().add("dialog-error");
btnYes.setOnAction(e -> resolve.run());
btnYes.setDisable(true);
public static void confirmWithCountdown(String text, String title, int seconds, MessageType messageType,
@Nullable Runnable ok, @Nullable Runnable cancel) {
if (seconds <= 0)
throw new IllegalArgumentException("Seconds must be greater than 0");
int countdown = 10;
KeyFrame[] keyFrames = new KeyFrame[countdown + 1];
for (int i = 0; i < countdown; i++) {
JFXButton btnOk = new JFXButton(i18n("button.ok"));
btnOk.getStyleClass().add(messageType == MessageType.WARNING || messageType == MessageType.ERROR
? "dialog-error"
: "dialog-accept");
if (ok != null)
btnOk.setOnAction(e -> ok.run());
btnOk.setDisable(true);
KeyFrame[] keyFrames = new KeyFrame[seconds + 1];
for (int i = 0; i < seconds; i++) {
keyFrames[i] = new KeyFrame(Duration.seconds(i),
new KeyValue(btnYes.textProperty(), i18n("button.ok.countdown", countdown - i)));
new KeyValue(btnOk.textProperty(), i18n("button.ok.countdown", seconds - i)));
}
keyFrames[countdown] = new KeyFrame(Duration.seconds(countdown),
new KeyValue(btnYes.textProperty(), i18n("button.ok")),
new KeyValue(btnYes.disableProperty(), false));
keyFrames[seconds] = new KeyFrame(Duration.seconds(seconds),
new KeyValue(btnOk.textProperty(), i18n("button.ok")),
new KeyValue(btnOk.disableProperty(), false));
Timeline timeline = new Timeline(keyFrames);
confirmAction(text, title, MessageType.WARNING, btnYes, () -> {
confirmAction(text, title, messageType, btnOk, () -> {
timeline.stop();
cancel.run();
if (cancel != null)
cancel.run();
});
timeline.play();
}
@@ -1411,12 +1411,16 @@ public final class FXUtils {
}
public static void copyText(String text) {
copyText(text, i18n("message.copied"));
}
public static void copyText(String text, @Nullable String toastMessage) {
ClipboardContent content = new ClipboardContent();
content.putString(text);
Clipboard.getSystemClipboard().setContent(content);
if (!Controllers.isStopped()) {
Controllers.showToast(i18n("message.copied"));
if (toastMessage != null && !Controllers.isStopped()) {
Controllers.showToast(toastMessage);
}
}
@@ -69,6 +69,7 @@ public enum SVG {
FOR_YOU("M12 12Q14.025 12 16.225 11.5875T20 10.5V20.5Q18.5 21.175 16.35 21.5875T12 22Q9.8 22 7.65 21.5875T4 20.5V10.5Q5.575 11.175 7.775 11.5875T12 12ZM18 19V13.25Q16.75 13.6 15.1125 13.8T12 14Q10.525 14 8.8875 13.8T6 13.25V19Q7.25 19.45 8.875 19.725T12 20Q13.5 20 15.125 19.725T18 19ZM12 2Q13.65 2 14.825 3.175T16 6Q16 7.65 14.825 8.825T12 10Q10.35 10 9.175 8.825T8 6Q8 4.35 9.175 3.175T12 2ZM12 8Q12.825 8 13.4125 7.4125T14 6Q14 5.175 13.4125 4.5875T12 4Q11.175 4 10.5875 4.5875T10 6Q10 6.825 10.5875 7.4125T12 8ZM12 6ZM12 16.625Z"),
GAMEPAD("M12 7.65ZM16.35 12ZM7.65 12ZM12 16.35ZM12 10.5 9 7.5V2H15V7.5L12 10.5ZM16.5 15 13.5 12 16.5 9H22V15H16.5ZM2 15V9H7.5L10.5 12 7.5 15H2ZM9 22V16.5L12 13.5 15 16.5V22H9ZM12 7.65 13 6.65V4H11V6.65L12 7.65ZM4 13H6.65L7.65 12 6.65 11H4V13ZM11 20H13V17.35L12 16.35 11 17.35V20ZM17.35 13H20V11H17.35L16.35 12 17.35 13Z"),
GLOBE_BOOK("M3.075 13Q3.05 12.75 3.0375 12.5T3.025 12Q3.025 10.125 3.725 8.4875T5.65 5.6375Q6.875 4.425 8.5 3.7125T12 3Q13.875 3 15.5125 3.7125T18.3625 5.6375Q19.575 6.85 20.2875 8.4875T21 12Q21 12.25 20.9875 12.5T20.95 13H18.925Q18.975 12.75 18.9875 12.5T19 12Q19 11.75 18.9875 11.5T18.925 11H15.975Q16 11.25 16 11.5V12.5Q16 12.75 15.975 13H14V12.175Q14 11.875 13.9875 11.575T13.95 11H10.075Q10.05 11.275 10.0375 11.575T10.025 12.175V13H8.05Q8.025 12.75 8.025 12.5V11.5Q8.025 11.25 8.05 11H5.1Q5.05 11.25 5.0375 11.5T5.025 12Q5.025 12.25 5.0375 12.5T5.1 13H3.075ZM5.7 9H8.275Q8.475 7.925 8.775 7.0625T9.425 5.5Q8.225 5.95 7.25 6.8625T5.7 9ZM10.35 9H13.65Q13.4 7.925 13.025 6.9T12 5Q11.35 5.875 10.9625 6.9T10.35 9ZM15.75 9H18.325Q17.75 7.775 16.7625 6.8625T14.575 5.5Q14.925 6.25 15.2375 7.0875T15.75 9ZM11 21V20Q11 18.75 10.125 17.875T8 17H2V15H8Q9.2 15 10.2375 15.525T12 17Q12.725 16.05 13.7625 15.525T16 15H22V17H16Q14.75 17 13.875 17.875T13 20V21H11Z"),
GRAPH2("M5 22q-1.25 0-2.125-.875T2 19q0-.975.5625-1.75T4 16.175V14q0-1.25.875-2.125T7 11h4V7.825q-.875-.3-1.4375-1.075T9 5q0-1.25.875-2.125T12 2t2.125.875T15 5q0 .975-.5625 1.75T13 7.825V11h4q1.25 0 2.125.875T20 14v2.175q.875.3 1.4375 1.075T22 19q0 1.25-.875 2.125T19 22t-2.125-.875T16 19q0-.975.5625-1.75T18 16.175V14q0-.425-.2875-.7125T17 13H13v3.175q.875.3 1.4375 1.075T15 19q0 1.25-.875 2.125T12 22t-2.125-.875T9 19q0-.975.5625-1.75T11 16.175V13H7q-.425 0-.7125.2875T6 14v2.175q.875.3 1.4375 1.075T8 19q0 1.25-.875 2.125T5 22Zm0-2q.425 0 .7125-.2875T6 19q0-.425-.2875-.7125T5 18q-.425 0-.7125.2875T4 19q0 .425.2875.7125T5 20Zm7 0q.425 0 .7125-.2875T13 19q0-.425-.2875-.7125T12 18q-.425 0-.7125.2875T11 19q0 .425.2875.7125T12 20Zm7 0q.425 0 .7125-.2875T20 19q0-.425-.2875-.7125T19 18q-.425 0-.7125.2875T18 19q0 .425.2875.7125T19 20ZM12 6q.425 0 .7125-.2875T13 5q0-.425-.2875-.7125T12 4q-.425 0-.7125.2875T11 5q0 .425.2875.7125T12 6Z"),
HELP("M11.95 18Q12.475 18 12.8375 17.6375T13.2 16.75Q13.2 16.225 12.8375 15.8625T11.95 15.5Q11.425 15.5 11.0625 15.8625T10.7 16.75Q10.7 17.275 11.0625 17.6375T11.95 18ZM11.05 14.15H12.9Q12.9 13.325 13.0875 12.85T14.15 11.55Q14.8 10.9 15.175 10.3125T15.55 8.9Q15.55 7.5 14.525 6.75T12.1 6Q10.675 6 9.7875 6.75T8.55 8.55L10.2 9.2Q10.325 8.75 10.7625 8.225T12.1 7.7Q12.9 7.7 13.3 8.1375T13.7 9.1Q13.7 9.6 13.4 10.0375T12.65 10.85Q11.55 11.825 11.3 12.325T11.05 14.15ZM12 22Q9.925 22 8.1 21.2125T4.925 19.075Q3.575 17.725 2.7875 15.9T2 12Q2 9.925 2.7875 8.1T4.925 4.925Q6.275 3.575 8.1 2.7875T12 2Q14.075 2 15.9 2.7875T19.075 4.925Q20.425 6.275 21.2125 8.1T22 12Q22 14.075 21.2125 15.9T19.075 19.075Q17.725 20.425 15.9 21.2125T12 22ZM12 20Q15.35 20 17.675 17.675T20 12Q20 8.65 17.675 6.325T12 4Q8.65 4 6.325 6.325T4 12Q4 15.35 6.325 17.675T12 20ZM12 12Z"),
HOME("M6 19H9V13H15V19H18V10L12 5.5 6 10V19ZM4 21V9L12 3 20 9V21H13V15H11V21H4ZM12 12.25Z"),
HOST("M4 21Q3.175 21 2.5875 20.4125T2 19V5Q2 4.175 2.5875 3.5875T4 3H9Q9.825 3 10.4125 3.5875T11 5V19Q11 19.825 10.4125 20.4125T9 21H4ZM15 21Q14.175 21 13.5875 20.4125T13 19V5Q13 4.175 13.5875 3.5875T15 3H20Q20.825 3 21.4125 3.5875T22 5V19Q22 19.825 21.4125 20.4125T20 21H15ZM4 19H9V5H4V19ZM15 19H20V5H15V19ZM5 15H8V13H5V15ZM16 15H19V13H16V15ZM5 12H8V10H5V12ZM16 12H19V10H16V12ZM5 9H8V7H5V9ZM16 9H19V7H16V9ZM4 19H9 4ZM15 19H20 15Z"),
@@ -19,7 +19,13 @@ package org.jackhuang.hmcl.ui.account;
import com.jfoenix.controls.JFXButton;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
@@ -45,51 +51,23 @@ import org.jackhuang.hmcl.util.i18n.LocaleUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import org.jackhuang.hmcl.util.platform.NativeUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.windows.Kernel32;
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Locale;
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class AccountListPage extends DecoratorAnimatedPage implements DecoratorPage {
static final BooleanProperty RESTRICTED = new SimpleBooleanProperty(true);
private static boolean isExemptedRegion() {
if ("Asia/Shanghai".equals(ZoneId.systemDefault().getId()))
return true;
// Check if the time zone is UTC+8
if (ZonedDateTime.now().getOffset().getTotalSeconds() == Duration.ofHours(8).toSeconds()) {
if ("CN".equals(LocaleUtils.SYSTEM_DEFAULT.getCountry()))
return true;
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && NativeUtils.USE_JNA) {
Kernel32 kernel32 = Kernel32.INSTANCE;
// https://learn.microsoft.com/windows/win32/intl/table-of-geographical-locations
if (kernel32 != null && kernel32.GetUserGeoID(WinConstants.GEOCLASS_NATION) == 45) // China
return true;
}
}
return false;
}
static {
String property = System.getProperty("hmcl.offline.auth.restricted", "auto");
if ("false".equals(property)
|| "auto".equals(property) && isExemptedRegion()
|| "auto".equals(property) && LocaleUtils.IS_CHINA_MAINLAND
|| globalConfig().isEnableOfflineAccount())
RESTRICTED.set(false);
else
@@ -264,10 +264,12 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
};
if (factory instanceof OfflineAccountFactory && username != null && (!USERNAME_CHECKER_PATTERN.matcher(username).matches() || username.length() > 16)) {
Controllers.confirmActionDanger(i18n("account.methods.offline.name.invalid"), i18n("message.warning"), doCreate, () -> {
body.setDisable(false);
spinner.hideSpinner();
});
Controllers.confirmWithCountdown(i18n("account.methods.offline.name.invalid"), i18n("message.warning"), 10,
MessageDialogPane.MessageType.WARNING,
doCreate, () -> {
body.setDisable(false);
spinner.hideSpinner();
});
} else {
doCreate.run();
}
@@ -64,6 +64,8 @@ public final class DecoratorAnimationProducer implements AnimationProducer {
new KeyValue(center.translateXProperty(), 30, Interpolator.EASE_BOTH)));
}
keyFrames.add(new KeyFrame(Duration.ZERO,
new KeyValue(next.opacityProperty(), 0, Interpolator.EASE_BOTH)));
keyFrames.add(new KeyFrame(halfDuration,
new KeyValue(next.opacityProperty(), 0, Interpolator.EASE_BOTH)));
keyFrames.add(new KeyFrame(handler.getDuration(),
@@ -250,7 +250,7 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
twoLineListItem.addTag(i18n("version.game.release"));
imageView.setImage(VersionIconType.GRASS.getIcon());
}
case PENDING, SNAPSHOT -> {
case SNAPSHOT, PENDING, UNOBFUSCATED -> {
if (versionType == RemoteVersion.Type.SNAPSHOT
&& GameVersionNumber.asGameVersion(remoteVersion.getGameVersion()).isAprilFools()) {
twoLineListItem.addTag(i18n("version.game.april_fools"));
@@ -455,7 +455,8 @@ public final class VersionsPage extends Control implements WizardPage, Refreshab
return switch (filter) {
case RELEASE -> versionType == RemoteVersion.Type.RELEASE;
case SNAPSHOTS -> versionType == RemoteVersion.Type.SNAPSHOT
|| versionType == RemoteVersion.Type.PENDING;
|| versionType == RemoteVersion.Type.PENDING
|| versionType == RemoteVersion.Type.UNOBFUSCATED;
case APRIL_FOOLS -> versionType == RemoteVersion.Type.SNAPSHOT
&& GameVersionNumber.asGameVersion(it.getGameVersion()).isAprilFools();
case OLD -> versionType == RemoteVersion.Type.OLD;
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.terracotta.TerracottaMetadata;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -49,6 +50,7 @@ import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.TaskCancellationAction;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.platform.*;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.nio.file.Files;
@@ -183,10 +185,28 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
// sixth item in left sidebar
AdvancedListItem terracottaItem = new AdvancedListItem();
terracottaItem.setLeftGraphic(wrap(SVG.HOST));
terracottaItem.setLeftGraphic(wrap(SVG.GRAPH2));
terracottaItem.setActionButtonVisible(false);
terracottaItem.setTitle(i18n("terracotta"));
terracottaItem.setOnAction(e -> Controllers.navigate(Controllers.getTerracottaPage()));
terracottaItem.setOnAction(e -> {
if (TerracottaMetadata.PROVIDER != null) {
Controllers.navigate(Controllers.getTerracottaPage());
} else {
String message;
if (Architecture.SYSTEM_ARCH.getBits() == Bits.BIT_32)
message = i18n("terracotta.unsupported.arch.32bit");
else if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
&& !OperatingSystem.SYSTEM_VERSION.isAtLeast(OSVersion.WINDOWS_10))
message = i18n("terracotta.unsupported.os.windows.old");
else if (Platform.SYSTEM_PLATFORM.equals(OperatingSystem.LINUX, Architecture.LOONGARCH64_OW))
message = i18n("terracotta.unsupported.arch.loongarch64_ow");
else
message = i18n("terracotta.unsupported");
Controllers.dialog(message, null, MessageDialogPane.MessageType.WARNING);
}
});
// the left sidebar
AdvancedListBox sideBar = new AdvancedListBox()
@@ -198,7 +218,8 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
.add(downloadItem)
.startCategory(i18n("settings.launcher.general").toUpperCase(Locale.ROOT))
.add(launcherSettingsItem)
.add(terracottaItem);
.add(terracottaItem)
.addNavigationDrawerItem(i18n("chat"), SVG.CHAT, () -> FXUtils.openLink(Metadata.GROUPS_URL));
// the root page, with the sidebar in left, navigator in center.
setLeft(sideBar);
@@ -34,11 +34,8 @@ import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.Clipboard;
import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
@@ -86,7 +83,6 @@ import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class TerracottaControllerPage extends StackPane {
@@ -138,13 +134,13 @@ public class TerracottaControllerPage extends StackPane {
TerracottaState state = UI_STATE.get(), next;
if (state instanceof TerracottaState.Uninitialized || state instanceof TerracottaState.Preparing preparing && preparing.hasInstallFence()) {
if (state instanceof TerracottaState.Uninitialized uninitialized && !uninitialized.hasLegacy()) {
Controllers.confirmActionDanger(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), () -> {
TerracottaState.Preparing s = TerracottaManager.install(path);
if (s != null) {
UI_STATE.set(s);
}
}, () -> {
});
Controllers.confirmWithCountdown(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), 5,
MessageDialogPane.MessageType.INFO, () -> {
TerracottaState.Preparing s = TerracottaManager.install(path);
if (s != null) {
UI_STATE.set(s);
}
}, null);
return;
}
@@ -185,31 +181,20 @@ public class TerracottaControllerPage extends StackPane {
download.setSubtitle(i18n("terracotta.status.uninitialized.desc"));
download.setRightIcon(SVG.ARROW_FORWARD);
FXUtils.onClicked(download, () -> {
if (globalConfig().getTerracottaAgreementVersion() >= 1) {
TerracottaState.Preparing s = TerracottaManager.install(null);
if (s != null) {
UI_STATE.set(s);
}
TerracottaState.Preparing s = TerracottaManager.install(null);
if (s != null) {
UI_STATE.set(s);
}
if (uninitialized.hasLegacy() && I18n.isUseChinese()) {
Object feedback = config().getShownTips().get(FEEDBACK_TIP);
if (!(feedback instanceof Number number) || number.intValue() < 1) {
Controllers.confirm(i18n("terracotta.feedback.desc"), i18n("terracotta.feedback.title"), () -> {
FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK);
config().getShownTips().put(FEEDBACK_TIP, 1);
}, () -> {
});
}
if (uninitialized.hasLegacy() && I18n.isUseChinese()) {
Object feedback = config().getShownTips().get(FEEDBACK_TIP);
if (!(feedback instanceof Number number) || number.intValue() < 1) {
Controllers.confirm(i18n("terracotta.feedback.desc"), i18n("terracotta.feedback.title"), () -> {
FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK);
config().getShownTips().put(FEEDBACK_TIP, 1);
}, () -> {
});
}
} else {
Controllers.confirmActionDanger(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), () -> {
globalConfig().setTerracottaAgreementVersion(1);
TerracottaState.Preparing s = TerracottaManager.install(null);
if (s != null) {
UI_STATE.set(s);
}
}, () -> {
});
}
});
@@ -342,6 +327,7 @@ public class TerracottaControllerPage extends StackPane {
return;
} else {
String cs = hostOK.getCode();
copyCode(cs);
statusProperty.set(i18n("terracotta.status.host_ok"));
progressProperty.set(1);
@@ -350,36 +336,24 @@ public class TerracottaControllerPage extends StackPane {
code.setAlignment(Pos.CENTER);
{
Label desc = new Label(i18n("terracotta.status.host_ok.code"));
{
ClipboardContent cp = new ClipboardContent();
cp.putString(cs);
Clipboard.getSystemClipboard().setContent(cp);
}
desc.setMouseTransparent(true);
// FIXME: The implementation to display Room Code is ambiguous. Consider using a clearer JavaFX Element in the future.
TextField label = new TextField(cs);
label.setEditable(false);
label.setFocusTraversable(false);
Label label = new Label(cs);
label.setMouseTransparent(true);
label.setStyle("-fx-font-size: 24");
label.setAlignment(Pos.CENTER);
label.setStyle("-fx-background-color: transparent; -fx-border-color: transparent;");
VBox.setMargin(label, new Insets(10, 0, 10, 0));
label.setScaleX(1.8);
label.setScaleY(1.8);
holder.add(FXUtils.onWeakChange(label.selectedTextProperty(), string -> {
if (string != null && !string.isEmpty() && !cs.equals(string)) {
label.selectAll();
}
}));
code.getChildren().setAll(desc, label);
}
FXUtils.onClicked(code, () -> FXUtils.copyText(cs));
code.setCursor(Cursor.HAND);
FXUtils.onClicked(code, () -> copyCode(cs));
LineButton copy = LineButton.of();
copy.setLeftIcon(SVG.CONTENT_COPY);
copy.setTitle(i18n("terracotta.status.host_ok.code.copy"));
copy.setSubtitle(i18n("terracotta.status.host_ok.code.desc"));
FXUtils.onClicked(copy, () -> FXUtils.copyText(cs));
FXUtils.onClicked(copy, () -> copyCode(cs));
LineButton back = LineButton.of();
back.setLeftIcon(SVG.ARROW_BACK);
@@ -601,6 +575,10 @@ public class TerracottaControllerPage extends StackPane {
return locals;
}
private void copyCode(String code) {
FXUtils.copyText(code, i18n("terracotta.status.host_ok.code.copy.toast"));
}
private static final class LineButton extends RipplerContainer {
private final WeakListenerHolder holder = new WeakListenerHolder();
@@ -19,30 +19,42 @@ package org.jackhuang.hmcl.ui.terracotta;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import org.jackhuang.hmcl.Metadata;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Insets;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.terracotta.TerracottaMetadata;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionPane;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
import org.jackhuang.hmcl.ui.construct.PageAware;
import org.jackhuang.hmcl.ui.construct.TabHeader;
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.main.MainPage;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import java.util.Locale;
import static org.jackhuang.hmcl.ui.versions.VersionPage.wrap;
import static org.jackhuang.hmcl.setting.ConfigHolder.globalConfig;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPage, PageAware {
private static final int TERRACOTTA_AGREEMENT_VERSION = 2;
private final ReadOnlyObjectWrapper<State> state = new ReadOnlyObjectWrapper<>(State.fromTitle(i18n("terracotta.terracotta")));
private final TabHeader tab;
private final TabHeader.Tab<TerracottaControllerPage> statusPage = new TabHeader.Tab<>("statusPage");
private final TransitionPane transitionPane = new TransitionPane();
@SuppressWarnings("unused")
private ChangeListener<String> instanceChangeListenerHolder;
public TerracottaPage() {
statusPage.setNodeSupplier(TerracottaControllerPage::new);
tab = new TabHeader(statusPage);
@@ -53,32 +65,34 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
transitionPane.setContent(newValue.getNode(), ContainerAnimations.FADE);
});
AdvancedListItem feedback = new AdvancedListItem();
feedback.setLeftGraphic(wrap(SVG.FEEDBACK));
feedback.setActionButtonVisible(false);
feedback.setTitle(i18n("terracotta.feedback.title"));
feedback.setOnAction(e -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK));
AdvancedListItem chatItem = new AdvancedListItem();
chatItem.setLeftGraphic(wrap(SVG.CHAT));
chatItem.setActionButtonVisible(false);
chatItem.setTitle(i18n("chat"));
chatItem.setOnAction(e -> FXUtils.openLink(Metadata.GROUPS_URL));
AdvancedListItem easytierItem = new AdvancedListItem();
easytierItem.setLeftGraphic(wrap(SVG.HOST));
easytierItem.setActionButtonVisible(false);
easytierItem.setTitle(i18n("terracotta.easytier"));
easytierItem.setOnAction(e -> FXUtils.openLink("https://easytier.cn/"));
BorderPane left = new BorderPane();
FXUtils.setLimitWidth(left, 200);
VBox.setVgrow(left, Priority.ALWAYS);
setLeft(left);
AdvancedListBox sideBar = new AdvancedListBox()
.addNavigationDrawerTab(tab, statusPage, i18n("terracotta.status"), SVG.TUNE)
.startCategory(i18n("help").toUpperCase(Locale.ROOT))
.add(feedback)
.add(chatItem)
.add(easytierItem);
FXUtils.setLimitWidth(sideBar, 200);
setLeft(sideBar);
.addNavigationDrawerTab(tab, statusPage, i18n("terracotta.status"), SVG.TUNE);
left.setTop(sideBar);
AdvancedListBox toolbar = new AdvancedListBox()
.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, () -> {
Profile profile = Profiles.getSelectedProfile();
Versions.launch(profile, profile.getSelectedVersion(), LauncherHelper::setKeep);
}, item -> {
instanceChangeListenerHolder = FXUtils.onWeakChangeAndOperate(Profiles.selectedVersionProperty(),
instanceName -> item.setSubtitle(StringUtils.isNotBlank(instanceName) ? instanceName : i18n("version.empty"))
);
MainPage mainPage = Controllers.getRootPage().getMainPage();
FXUtils.onScroll(item, mainPage.getVersions(), list -> {
String currentId = mainPage.getCurrentGame();
return Lang.indexWhere(list, instance -> instance.getId().equals(currentId));
}, it -> mainPage.getProfile().setSelectedVersion(it.getId()));
})
.addNavigationDrawerItem(i18n("terracotta.feedback.title"), SVG.FEEDBACK, () -> FXUtils.openLink(TerracottaMetadata.FEEDBACK_LINK))
.addNavigationDrawerItem(i18n("terracotta.easytier"), SVG.HOST, () -> FXUtils.openLink("https://easytier.cn/"));
BorderPane.setMargin(toolbar, new Insets(0, 0, 12, 0));
left.setBottom(toolbar);
setCenter(transitionPane);
}
@@ -86,6 +100,12 @@ public class TerracottaPage extends DecoratorAnimatedPage implements DecoratorPa
@Override
public void onPageShown() {
tab.onPageShown();
if (globalConfig().getTerracottaAgreementVersion() < TERRACOTTA_AGREEMENT_VERSION) {
Controllers.confirmWithCountdown(i18n("terracotta.confirm.desc"), i18n("terracotta.confirm.title"), 5, MessageDialogPane.MessageType.INFO, () -> {
globalConfig().setTerracottaAgreementVersion(TERRACOTTA_AGREEMENT_VERSION);
}, () -> fireEvent(new PageCloseEvent()));
}
}
@Override
@@ -11,16 +11,18 @@ import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.game.NativesDirectoryType;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.game.Renderer;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.Platform;
import org.jetbrains.annotations.Nullable;
import java.nio.file.FileSystems;
import java.util.Arrays;
import java.util.Locale;
@@ -250,8 +252,8 @@ public final class AdvancedVersionSettingPage extends StackPane implements Decor
if (versionSetting.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
String nativesDirName = "natives-" + Platform.SYSTEM_PLATFORM;
if (versionId == null) {
return String.format("%s/%s/%s",
profile.getRepository().getBaseDirectory().resolve("versions").toAbsolutePath().normalize(),
return String.join(FileSystems.getDefault().getSeparator(),
FileUtils.getAbsolutePath(profile.getRepository().getBaseDirectory().resolve("versions")),
i18n("settings.advanced.natives_directory.default.version_id"),
nativesDirName
);
@@ -30,9 +30,9 @@ public final class ChunkBaseApp {
private static final GameVersionNumber MIN_GAME_VERSION = GameVersionNumber.asGameVersion("1.7");
private static final String[] SEED_MAP_GAME_VERSIONS = {
"1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21", "1.20", "1.19.3",
"1.19", "1.18", "1.17", "1.16", "1.15", "1.14", "1.13", "1.12",
"1.11", "1.10", "1.9", "1.8", "1.7"
"1.21.9", "1.21.6", "1.21.5", "1.21.4", "1.21.2", "1.21", "1.20",
"1.19.3", "1.19", "1.18", "1.17", "1.16", "1.15", "1.14", "1.13",
"1.12", "1.11", "1.10", "1.9", "1.8", "1.7"
};
public static final String[] STRONGHOLD_FINDER_GAME_VERSIONS = {
@@ -236,6 +236,7 @@ public final class NativePatcher {
minVersion = "1.6";
} else if (platform.equals(Platform.LINUX_RISCV64)) {
minVersion = "1.8";
maxVersion = "1.21.5";
} else if (platform.equals(Platform.LINUX_LOONGARCH64)) {
minVersion = "1.6";
} else if (platform.equals(Platform.LINUX_LOONGARCH64_OW)) {
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.util.i18n;
import org.jackhuang.hmcl.download.game.GameRemoteVersion;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.translator.Translator_lzh;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
@@ -29,7 +30,7 @@ public final class MinecraftWiki {
private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("^[0-9]{2}w[0-9]{2}.+$");
public static String getWikiLink(SupportedLocale locale, GameRemoteVersion version) {
String wikiVersion = version.getSelfVersion();
String wikiVersion = StringUtils.removeSuffix(version.getSelfVersion(), "_unobfuscated");
var gameVersion = GameVersionNumber.asGameVersion(wikiVersion);
if (locale.getLocale().getLanguage().equals("lzh")) {
+1 -1
View File
@@ -228,7 +228,7 @@
.class-title {
-fx-font-size: 12px;
-fx-padding: 16 16 8 16;
-fx-padding: 8 16 8 16;
}
.advanced-list-box-item {
@@ -1437,8 +1437,8 @@ terracotta.status.uninitialized.update.title=Update Multiplayer Core (~ 8MiB)
terracotta.status.uninitialized.desc=You legally promise to strictly abide by all laws and regulations of your country or region during the multiplayer process.
terracotta.confirm.title=User Notice
terracotta.confirm.desc=Terracotta is a third-party open source free software. Please provide feedback on any issues you encounter through the relevant channels during use.\n\
Terracotta is based on P2P, so that the final experience depends greatly on your network condition.\n\
You promise to strictly abide by all laws and regulations in your country or region when playing online.
Terracotta uses P2P technology. After a successful connection, users in the room will connect directly to each other, and no third-party server will relay your traffic. The final multiplayer experience largely depends on the network conditions of the participants.\n\
During the entire multiplayer process, you must strictly comply with all laws and regulations of your country and region.
terracotta.status.preparing=Multiplayer Core: Downloading (DO NOT exit HMCL)
terracotta.status.launching=Multiplayer Core: Initializing
terracotta.status.unknown=Multiplayer Core: Initializing
@@ -1460,6 +1460,7 @@ terracotta.status.host_starting.back=This will stop creating the room.
terracotta.status.host_ok=Room created
terracotta.status.host_ok.code=Invitation code (Copied)
terracotta.status.host_ok.code.copy=Copy invitation code
terracotta.status.host_ok.code.copy.toast=Invitation code has been copied to clipboard
terracotta.status.host_ok.code.desc=Please remind your friends to select Guest mode in HMCL - Multiplayer or PCL CE and enter this invitation code.
terracotta.status.host_ok.back=This will also close the room, other guests will leave and cannot rejoin.
terracotta.status.guest_starting=Joining room
@@ -1476,7 +1477,6 @@ terracotta.status.exception.desc.host_et_crash=Failed to create room: EasyTier c
terracotta.status.exception.desc.ping_server_rst=Room closed: You exited the game world, room closed automatically
terracotta.status.exception.desc.scaffolding_invalid_response=Invalid Protocol:Host has sent invalid response, please report this issue to developers
terracotta.status.fatal.retry=Retry
terracotta.status.fatal.os=Sorry, HMCL cannot enable Terracotta | Multiplayer on your operating system or architecture. Please use a more modern operating system.
terracotta.status.fatal.network=Failed to download Multiplayer Core. Please check your network connection and try again.
terracotta.status.fatal.install=Fatal Error: Unable to install Multiplayer Core.
terracotta.status.fatal.terracotta=Fatal Error: Unable to connect to Multiplayer Core.
@@ -1486,6 +1486,10 @@ terracotta.player_anonymous=Anonymous Player
terracotta.player_kind.host=Host
terracotta.player_kind.local=Yourself
terracotta.player_kind.guest=Guest
terracotta.unsupported=Multiplayer is not yet supported on the current platform.
terracotta.unsupported.os.windows.old=Multiplayer requires Windows 10 or later. Please update your system.
terracotta.unsupported.arch.32bit=Multiplayer is not supported on 32-bit systems. Please upgrade to a 64-bit system.
terracotta.unsupported.arch.loongarch64_ow=Multiplayer is not supported on Linux LoongArch64 Old World distributions. Please update to a New World distribution (such as AOSC OS).
unofficial.hint=You are using an unofficial build of HMCL. We cannot guarantee its security.
@@ -1225,8 +1225,8 @@ terracotta.status.uninitialized.update.title=更新聯機核心 (約 8MiB)
terracotta.status.uninitialized.desc=您承諾,在多人聯機全過程中,您將嚴格遵守您所在國家或地區的全部法律法規
terracotta.confirm.title=使用者須知
terracotta.confirm.desc=陶瓦聯機是第三方開源自由軟體,您在使用過程中所遇到的問題請通過相關管道進行回饋。\n\
多人連線基於 p2p,最終線上體驗和您的網路情況有較大關係。\n\
您承諾,在多人連線全過程中,您將嚴格遵守您所在國家或地區的全部法律法規。
陶瓦聯機使用 P2P 技術,聯機成功後房間內用戶之間將直接連接,不會使用第三方伺服器對您的流量進行轉發。最終聯機體驗和參與聯機者的網路情況有較大關係。\n\
在多人聯機全過程中,您必須嚴格遵守您所在國家與地區的全部法律法規。
terracotta.status.preparing=正在下載聯機核心 (請勿退出啟動器)
terracotta.status.launching=正在初始化聯機核心
terracotta.status.unknown=正在初始化聯機核心
@@ -1248,6 +1248,7 @@ terracotta.status.host_starting.back=這將會取消建立房間。
terracotta.status.host_ok=已建立房間
terracotta.status.host_ok.code=邀請碼 (已自動複製到剪貼簿)
terracotta.status.host_ok.code.copy=複製邀請碼
terracotta.status.host_ok.code.copy.toast=已將邀請碼複製到剪貼簿
terracotta.status.host_ok.code.desc=請提醒您的朋友在 HMCL 或 PCL CE 多人遊戲功能中選擇房客模式,並輸入該邀請碼。
terracotta.status.host_ok.back=這將同時徹底關閉房間,其他房客將退出並不再能重新加入該房間。
terracotta.status.guest_starting=正在加入房間
@@ -1264,7 +1265,6 @@ terracotta.status.exception.desc.host_et_crash=建立房間失敗:EasyTier 已
terracotta.status.exception.desc.ping_server_rst=房間已關閉:您已退出遊戲存檔,房間已自動關閉
terracotta.status.exception.desc.scaffolding_invalid_response=協議錯誤:房主發送了錯誤的回應資料,請向開發者回報該問題
terracotta.status.fatal.retry=重試
terracotta.status.fatal.os=抱歉,HMCL 不能在您的作業系統或架構上啟用多人連線。請使用更主流的作業系統
terracotta.status.fatal.network=未能下載線上核心。請檢查網路連接,然後再試一次
terracotta.status.fatal.install=嚴重錯誤:無法安裝線上核心
terracotta.status.fatal.terracotta=嚴重錯誤:無法與線上核心通訊
@@ -1274,6 +1274,10 @@ terracotta.player_anonymous=匿名玩家
terracotta.player_kind.host=房主
terracotta.player_kind.local=你
terracotta.player_kind.guest=房客
terracotta.unsupported=多人聯機功能尚未支援目前平台。
terracotta.unsupported.os.windows.old=多人聯機功能需要 Windows 10 或更高版本。請更新系統。
terracotta.unsupported.arch.32bit=多人聯機功能不支援 32 位元系統。請更新至 64 位元系統。
terracotta.unsupported.arch.loongarch64_ow=多人聯機功能不支援 Linux LoongArch64 舊世界發行版,請更新至新世界發行版 (如 AOSC OC)。
unofficial.hint=你正在使用第三方提供的 HMCL。我們無法保證其安全性,請注意甄別。
@@ -1235,8 +1235,8 @@ terracotta.status.uninitialized.update.title=更新联机核心 (约 8MiB)
terracotta.status.uninitialized.desc=您承诺,在多人联机全过程中,您将严格遵守您所在国家或地区的全部法律法规
terracotta.confirm.title=用户须知
terracotta.confirm.desc=陶瓦联机是第三方开源自由软件,您在使用过程中所遇到的问题请通过相关渠道进行反馈。\n\
多人联机基于 p2p,最终联机体验和您的网络情况有较大关系。\n\
在多人联机全过程中,您将严格遵守您所在国家或地区的全部法律法规。
陶瓦联机使用 P2P 技术,联机成功后房间内用户之间将直接连接,不会使用第三方服务器对您的流量进行转发。最终联机体验和参与联机者的网络情况有较大关系。\n\
在多人联机全过程中,您必须严格遵守您所在国家与地区的全部法律法规。
terracotta.status.preparing=正在下载联机核心 (请勿退出启动器)
terracotta.status.launching=正在初始化联机核心
terracotta.status.unknown=正在初始化联机核心
@@ -1258,6 +1258,7 @@ terracotta.status.host_starting.back=这将会取消创建房间。
terracotta.status.host_ok=已启动房间
terracotta.status.host_ok.code=邀请码 (已自动复制到剪贴板)
terracotta.status.host_ok.code.copy=复制邀请码
terracotta.status.host_ok.code.copy.toast=已将邀请码复制到剪贴板
terracotta.status.host_ok.code.desc=请提醒您的朋友在 HMCL 或 PCL CE 多人联机功能中选择房客模式,并输入该邀请码。
terracotta.status.host_ok.back=这将同时彻底关闭房间,其他房客将退出并不再能重新加入该房间。
terracotta.status.guest_starting=正在加入房间
@@ -1274,7 +1275,6 @@ terracotta.status.exception.desc.host_et_crash=创建房间失败:EasyTier 已
terracotta.status.exception.desc.ping_server_rst=房间已关闭:您已退出游戏存档,房间已自动关闭
terracotta.status.exception.desc.scaffolding_invalid_response=协议错误:房主发送了错误的响应数据,请向开发者反馈该问题
terracotta.status.fatal.retry=重试
terracotta.status.fatal.os=抱歉,HMCL 不能在您的操作系统或架构上启用多人联机。请使用更主流的操作系统
terracotta.status.fatal.network=未能下载联机核心。请检查网络连接,然后再试一次
terracotta.status.fatal.install=严重错误:无法安装联机核心
terracotta.status.fatal.terracotta=严重错误:无法与联机核心通讯
@@ -1284,6 +1284,10 @@ terracotta.player_anonymous=匿名玩家
terracotta.player_kind.host=房主
terracotta.player_kind.local=你
terracotta.player_kind.guest=房客
terracotta.unsupported=多人联机功能尚不支持当前平台。
terracotta.unsupported.os.windows.old=多人联机功能需要 Windows 10 或更高版本。请更新系统。
terracotta.unsupported.arch.32bit=多人联机功能不支持 32 位系统。请更新至 64 位系统。
terracotta.unsupported.arch.loongarch64_ow=多人联机功能不支持 Linux LoongArch64 旧世界发行版,请更新至新世界发行版 (如 AOSC OC)。
unofficial.hint=你正在使用非官方构建的 HMCL。我们无法保证其安全性,请注意甄别。
+19 -16
View File
@@ -1,26 +1,29 @@
{
"version_legacy": "0\\.3\\.[89]-rc\\.([0-9]|10)",
"version_legacy": "0\\.3\\.([89]-rc\\.([0-9]|10)|(1[0-3]))",
"version_recent": [
"0.3.9-rc.10"
"0.3.13"
],
"version_latest": "0.3.10",
"version_latest": "0.3.14",
"classifiers": {
"linux-arm64": "sha256:001b6b1e220e98dffd84198a3e16855644cad27900562c78d610b0bf50138d17",
"linux-x86_64": "sha256:b133ddb796ff52879fb0c38e68ef7ea968d3eaec77f27ea274c701888e795782",
"macos-arm64": "sha256:4bb2eb432ea0ee3e3dca6b1019d8864aa14bedfe20b3d804865e216e36e856df",
"macos-arm64.pkg": "sha256:e4bf3d2014e417565e3ab0df579f4280e0af42bc28516f640067a52dca77098f",
"macos-x86_64": "sha256:17a2cc46d884951a88da6f3f0725e38ffca6fdfd1da021807121c29b944bd830",
"macos-x86_64.pkg": "sha256:a4f08a706605262485c1889dfae00554fba753141f6d6ada76e29bfc3e0338aa",
"windows-arm64.exe": "sha256:245e0faf2ee1cdc5e430b5c18fbab4c4e5e3f333f0de51a621ae772db3a16924",
"windows-x86_64.exe": "sha256:ab99ddb729860c939b2085d8852fd62007b86d97568b687cf2a7147718b64eea"
"freebsd-x86_64": "sha256:ff8c50b701e55fde9701a1f7fbe9d7317b24087c28ca6337ba9d695c53ad499a",
"linux-arm64": "sha256:6912a14255cdeedee512244c686dd7f80dcc45d8dfbe129ea4ea3f1b22d3de60",
"linux-x86_64": "sha256:9b13f51dc41c5112f2ab163df5c0dccdff3d22deb793a6698c013e9c09badd00",
"linux-loongarch64": "sha256:729ca24ef643547b6a3917520ec3335b92e226ad1884c85d6bb6c2cfa917cb03",
"linux-riscv64": "sha256:afbd8cffeacc47431f18c3ae659f7a308e1d3f2efab0ab9c5ece5176d068bf30",
"macos-arm64": "sha256:9ed9c7a35b01dce440135c91e4a5e617d84f6c8ba13a117d13a2796dc69584f5",
"macos-arm64.pkg": "sha256:49dbebd608c4036c260f09d4005dedaacb26fc1582b10479ed51476baa48f4a9",
"macos-x86_64": "sha256:40a44703237b0a194b0fb18075e3b2361ace8f4319b60269cb56c5548d2afb3a",
"macos-x86_64.pkg": "sha256:95a9de782b9c3a352ae6ec702c2fc4ca34ba807e8e4a768e61902a0c78391332",
"windows-arm64.exe": "sha256:8428c6bc6228213c5f645ab3dd0f3b684da515be103ee53d5bdc4adf59fc8a5f",
"windows-x86_64.exe": "sha256:942b91888264e1c96a10b7c94bd134f4b10d3e352e3c731def0ff6a3069c8221"
},
"downloads": [
"https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}",
"https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}"
],
"downloads_CN": [
"https://gitee.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}",
"https://alist.8mi.tech/d/mirror/HMCL-Terracotta/Auto/v${version}/terracotta-${version}-${classifier}",
"https://ghfast.top/https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}",
"https://cdn.crashmc.com/https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}",
"https://cp.zkitefly.eu.org/https://github.com/burningtnt/Terracotta/releases/download/v${version}/terracotta-${version}-${classifier}"
"https://cnb.cool/HMCL-Terracotta/Terracotta/-/releases/download/v${version}/terracotta-${version}-${classifier}",
"https://alist.8mi.tech/d/mirror/HMCL-Terracotta/Auto/v${version}/terracotta-${version}-${classifier}"
],
"links": [
{
@@ -0,0 +1,99 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl;
import java.io.File;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
/// @author Glavo
public final class DesktopUtils {
private static final String[] linuxBrowsers = {
"xdg-open",
"google-chrome",
"firefox",
"microsoft-edge",
"opera",
"konqueror",
"mozilla"
};
public static Path which(String command) {
String path = System.getenv("PATH");
if (path == null)
return null;
try {
for (String item : path.split(File.pathSeparator)) {
try {
Path program = Paths.get(item, command);
if (Files.isExecutable(program))
return program.toRealPath();
} catch (Throwable ignored) {
}
}
} catch (Throwable ignored) {
}
return null;
}
public static void openLink(String link) {
String osName = System.getProperty("os.name").toLowerCase(Locale.ROOT);
Throwable e1 = null;
try {
if (osName.startsWith("windows")) {
Runtime.getRuntime().exec(new String[]{"rundll32.exe", "url.dll,FileProtocolHandler", link});
return;
} else if (osName.startsWith("mac") || osName.startsWith("darwin")) {
Runtime.getRuntime().exec(new String[]{"open", link});
return;
} else {
for (String browser : linuxBrowsers) {
Path path = which(browser);
if (path != null) {
try {
Runtime.getRuntime().exec(new String[]{path.toString(), link});
return;
} catch (Throwable ignored) {
}
}
}
System.err.println("No known browser found");
}
} catch (Throwable e) {
e1 = e;
}
try {
java.awt.Desktop.getDesktop().browse(new URI(link));
} catch (Throwable e2) {
if (e1 != null)
e2.addSuppressed(e1);
e2.printStackTrace(System.err);
}
}
private DesktopUtils() {
}
}
@@ -19,11 +19,15 @@ package org.jackhuang.hmcl;
import org.jackhuang.hmcl.util.SwingUtils;
import javax.swing.*;
import java.util.ResourceBundle;
/**
* @author Glavo
*/
public final class Main {
private static final int MINIMUM_JAVA_VERSION = 17;
private static final String DOWNLOAD_PAGE = "https://hmcl.huangyuhui.net/download/";
private Main() {
}
@@ -73,14 +77,36 @@ public final class Main {
}
}
static void showErrorAndExit(String[] args) {
SwingUtils.initLookAndFeel();
ResourceBundle resourceBundle = BootProperties.getResourceBundle();
String errorTitle = resourceBundle.getString("boot.message.error");
if (args.length > 0 && args[0].equals("--apply-to")) {
String errorMessage = resourceBundle.getString("boot.manual_update");
System.err.println(errorMessage);
int result = JOptionPane.showOptionDialog(null, errorMessage, errorTitle, JOptionPane.YES_NO_OPTION,
JOptionPane.ERROR_MESSAGE, null, null, null);
if (result == JOptionPane.YES_OPTION) {
System.out.println("Open " + DOWNLOAD_PAGE);
DesktopUtils.openLink(DOWNLOAD_PAGE);
}
} else {
String errorMessage = resourceBundle.getString("boot.unsupported_java_version");
System.err.println(errorMessage);
SwingUtils.showErrorDialog(errorMessage, errorTitle);
}
System.exit(1);
}
public static void main(String[] args) throws Throwable {
if (getJavaFeatureVersion(System.getProperty("java.version")) >= MINIMUM_JAVA_VERSION) {
EntryPoint.main(args);
} else {
String errorMessage = BootProperties.getResourceBundle().getString("boot.unsupported_java_version");
System.err.println(errorMessage);
SwingUtils.showErrorDialog(errorMessage);
System.exit(1);
showErrorAndExit(args);
}
}
}
@@ -40,14 +40,26 @@ public final class SwingUtils {
}
public static void showInfoDialog(Object message) {
JOptionPane.showMessageDialog(null, message, "Info", JOptionPane.INFORMATION_MESSAGE);
showInfoDialog(message, "Info");
}
public static void showInfoDialog(Object message, String title) {
JOptionPane.showMessageDialog(null, message, title, JOptionPane.INFORMATION_MESSAGE);
}
public static void showWarningDialog(Object message) {
JOptionPane.showMessageDialog(null, message, "Warning", JOptionPane.WARNING_MESSAGE);
showWarningDialog(message, "Warning");
}
public static void showWarningDialog(Object message, String title) {
JOptionPane.showMessageDialog(null, message, title, JOptionPane.WARNING_MESSAGE);
}
public static void showErrorDialog(Object message) {
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
showErrorDialog(message, "Error");
}
public static void showErrorDialog(Object message, String title) {
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
}
}
@@ -17,3 +17,6 @@
#
boot.unsupported_java_version=HMCL requires Java 17 or later to run, but still supports launching games with Java 6~16. Please install the latest version of Java and try opening HMCL again.\nYou can keep your old version of Java. HMCL can detect and manage multiple Java installations, and will automatically select the appropriate Java version for your game.
boot.manual_update=HMCL cannot complete automatic updates in the current environment. Please download the latest version of HMCL manually.\nWould you like to go to the download page?
boot.message.error=Error
@@ -17,3 +17,6 @@
#
boot.unsupported_java_version=HMCL 需要 Java 17 或更高版本才能运行,但依然支持使用 Java 6~16 启动游戏。请安装最新版本的 Java 再尝试启动 HMCL。\n你可以继续保留旧版本 Java。HMCL 能够识别与管理多个 Java,并会自动根据游戏版本为你选择合适的 Java。\n你可以访问 https://docs.hmcl.net/help.html 页面寻求帮助。
boot.manual_update=HMCL 在当前环境无法完成自动更新,请手动下载最新版本的 HMCL。\n是否前往下载页面?
boot.message.error=错误
@@ -17,3 +17,6 @@
#
boot.unsupported_java_version=HMCL 需要 Java 17 或更高版本才能執行,但依然支援使用 Java 6~16 啟動遊戲。請安裝最新版本的 Java 再嘗試開啟 HMCL。\n你可以繼續保留舊版本 Java。HMCL 能夠識別與管理多個 Java,並會自動根據遊戲版本為你選取合適的 Java。
boot.manual_update=HMCL 在當前環境無法完成自動更新,請手動下載最新版本的 HMCL。\n是否前往下載頁面?
boot.message.error=錯誤
@@ -82,8 +82,8 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
pair("https://authlib-injector.yushi.moe", apiRoot + "/mirrors/authlib-injector"),
pair("https://repo1.maven.org/maven2", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public"),
pair("https://repo.maven.apache.org/maven2", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public"),
pair("https://hmcl-dev.github.io/metadata/cleanroom", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/cleanroom"),
pair("https://hmcl-dev.github.io/metadata/fmllibs", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/fmllibs"),
pair("https://hmcl.glavo.site/metadata/cleanroom", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/cleanroom"),
pair("https://hmcl.glavo.site/metadata/fmllibs", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/fmllibs"),
pair("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto")
// // https://github.com/mcmod-info-mirror/mcim-rust-api
// pair("https://api.modrinth.com", "https://mod.mcimirror.top/modrinth"),
@@ -128,6 +128,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
RELEASE,
SNAPSHOT,
OLD,
PENDING
PENDING,
UNOBFUSCATED,
}
}
@@ -27,8 +27,8 @@ import java.util.Collections;
public final class CleanroomVersionList extends VersionList<CleanroomRemoteVersion> {
private final DownloadProvider downloadProvider;
private static final String LOADER_LIST_URL = "https://hmcl-dev.github.io/metadata/cleanroom/index.json";
private static final String INSTALLER_URL = "https://hmcl-dev.github.io/metadata/cleanroom/files/cleanroom-%s-installer.jar";
private static final String LOADER_LIST_URL = "https://hmcl.glavo.site/metadata/cleanroom/index.json";
private static final String INSTALLER_URL = "https://hmcl.glavo.site/metadata/cleanroom/files/cleanroom-%s-installer.jar";
public CleanroomVersionList(DownloadProvider downloadProvider) {
this.downloadProvider = downloadProvider;
@@ -96,5 +96,5 @@ public final class ForgeVersionList extends VersionList<ForgeRemoteVersion> {
});
}
public static final URI FORGE_LIST = URI.create("https://hmcl-dev.github.io/metadata/forge/");
public static final URI FORGE_LIST = URI.create("https://hmcl.glavo.site/metadata/forge/");
}
@@ -149,7 +149,7 @@ public final class GameLibrariesTask extends Task<Void> {
Path file = libDir.resolve(fmlLib.name);
if (shouldDownloadFMLLib(fmlLib, file)) {
List<URI> uris = dependencyManager.getDownloadProvider()
.injectURLWithCandidates(fmlLib.getDownloadURI());
.injectURLWithCandidates(fmlLib.downloadUrl());
dependencies.add(new FileDownloadTask(uris, file)
.withCounter("hmcl.install.libraries"));
}
@@ -193,9 +193,12 @@ public final class GameLibrariesTask extends Task<Void> {
if (forgeVersion.startsWith("7.8.1.")) {
return List.of(
new FMLLib("argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"),
new FMLLib("guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"),
new FMLLib("asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"),
new FMLLib("bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"),
new FMLLib("guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a",
"https://repo1.maven.org/maven2/com/google/guava/guava/14.0-rc3/guava-14.0-rc3.jar"),
new FMLLib("asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58",
"https://repo1.maven.org/maven2/org/ow2/asm/asm-all/4.1/asm-all-4.1.jar"),
new FMLLib("bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65",
"https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.48/bcprov-jdk15on-1.48.jar"),
new FMLLib("deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9"),
new FMLLib("scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85")
);
@@ -204,9 +207,9 @@ public final class GameLibrariesTask extends Task<Void> {
return null;
}
private record FMLLib(String name, String sha1) {
public String getDownloadURI() {
return "https://hmcl-dev.github.io/metadata/fmllibs/" + name;
private record FMLLib(String name, String sha1, String downloadUrl) {
FMLLib(String name, String sha1) {
this(name, sha1, "https://hmcl.glavo.site/metadata/fmllibs/" + name);
}
}
}
@@ -73,6 +73,7 @@ public final class GameRemoteVersion extends RemoteVersion {
case SNAPSHOT -> Type.SNAPSHOT;
case UNKNOWN -> Type.UNCATEGORIZED;
case PENDING -> Type.PENDING;
case UNOBFUSCATED -> Type.UNOBFUSCATED;
default -> Type.OLD;
};
}
@@ -28,6 +28,7 @@ public enum ReleaseType {
OLD_BETA("old-beta"),
OLD_ALPHA("old-alpha"),
PENDING("pending"),
UNOBFUSCATED("unobfuscated"),
UNKNOWN("unknown");
private final String id;
@@ -20,6 +20,10 @@ package org.jackhuang.hmcl.util.i18n;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.platform.NativeUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.windows.Kernel32;
import org.jackhuang.hmcl.util.platform.windows.WinConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
@@ -27,6 +31,9 @@ import org.jetbrains.annotations.Unmodifiable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -385,6 +392,29 @@ public final class LocaleUtils {
return "zh".equals(getRootLanguage(locale));
}
public static final boolean IS_CHINA_MAINLAND = isChinaMainland();
private static boolean isChinaMainland() {
if ("Asia/Shanghai".equals(ZoneId.systemDefault().getId()))
return true;
// Check if the time zone is UTC+8
if (ZonedDateTime.now().getOffset().getTotalSeconds() == Duration.ofHours(8).toSeconds()) {
if ("CN".equals(LocaleUtils.SYSTEM_DEFAULT.getCountry()))
return true;
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && NativeUtils.USE_JNA) {
Kernel32 kernel32 = Kernel32.INSTANCE;
// https://learn.microsoft.com/windows/win32/intl/table-of-geographical-locations
if (kernel32 != null && kernel32.GetUserGeoID(WinConstants.GEOCLASS_NATION) == 45) // China
return true;
}
}
return false;
}
private LocaleUtils() {
}
}
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.platform;
public record Platform(OperatingSystem os, Architecture arch) {
@@ -19,7 +36,7 @@ public record Platform(OperatingSystem os, Architecture arch) {
public static final Platform MACOS_X86_64 = new Platform(OperatingSystem.MACOS, Architecture.X86_64);
public static final Platform MACOS_ARM64 = new Platform(OperatingSystem.MACOS, Architecture.ARM64);
public static final Platform FREEBSD_X86_64 = new Platform(OperatingSystem.FREEBSD, Architecture.X86);
public static final Platform FREEBSD_X86_64 = new Platform(OperatingSystem.FREEBSD, Architecture.X86_64);
public static final Platform CURRENT_PLATFORM = Platform.getPlatform(OperatingSystem.CURRENT_OS, Architecture.CURRENT_ARCH);
public static final Platform SYSTEM_PLATFORM = Platform.getPlatform(OperatingSystem.CURRENT_OS, Architecture.SYSTEM_ARCH);
@@ -100,7 +100,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
}
public boolean isAprilFools() {
if (this instanceof Special)
if (this instanceof Special && !value.endsWith("_unobfuscated"))
return true;
if (this instanceof Snapshot snapshot) {
@@ -1,5 +1,33 @@
{
"versions": [
{
"id": "1.21.11-pre2_unobfuscated",
"type": "unobfuscated",
"url": "https://piston-meta.mojang.com/v1/packages/2955ce0af0512fdfe53ff0740b017344acf6f397/1.21.11-pre2_unobfuscated.json",
"time": "2025-11-21T12:07:21+00:00",
"releaseTime": "2025-11-21T12:07:21+00:00"
},
{
"id": "1.21.11-pre1_unobfuscated",
"type": "unobfuscated",
"url": "https://piston-meta.mojang.com/v1/packages/9c267f8dda2728bae55201a753cdd07b584709f1/1.21.11-pre1_unobfuscated.json",
"time": "2025-11-19T08:30:46+00:00",
"releaseTime": "2025-11-19T08:30:46+00:00"
},
{
"id": "25w46a_unobfuscated",
"type": "unobfuscated",
"url": "https://piston-meta.mojang.com/v1/packages/314ade2afeada364047798e163ef8e82427c69e1/25w46a_unobfuscated.json",
"time": "2025-11-11T13:20:54+00:00",
"releaseTime": "2025-11-11T13:20:54+00:00"
},
{
"id": "25w45a_unobfuscated",
"type": "unobfuscated",
"url": "https://piston-meta.mojang.com/v1/packages/7a3c149f148b6aa5ac3af48c4f701adea7e5b615/25w45a_unobfuscated.json",
"time": "2025-11-04T14:07:08+00:00",
"releaseTime": "2025-11-04T14:07:08+00:00"
},
{
"id": "1.19_deep_dark_experimental_snapshot-1",
"type": "pending",
@@ -848,4 +848,16 @@
1.21.9-rc1
1.21.9
1.21.10-rc1
1.21.10
1.21.10
25w41a
25w42a
25w43a
25w44a
25w45a
25w45a_unobfuscated
25w46a
25w46a_unobfuscated
1.21.11-pre1
1.21.11-pre1_unobfuscated
1.21.11-pre2
1.21.11-pre2_unobfuscated
@@ -121,6 +121,7 @@ public final class GameVersionNumberTest {
assertFalse(isAprilFools("25w21a"));
assertFalse(isAprilFools("13w12~"));
assertFalse(isAprilFools("15w14b"));
assertFalse(isAprilFools("25w45a_unobfuscated"));
}
@Test
@@ -272,6 +273,8 @@ public final class GameVersionNumberTest {
"24w13a",
"24w14potato",
"24w14a",
"25w45a",
"25w45a_unobfuscated",
"Unknown",
"100.0"
);
+200 -13
View File
@@ -4,20 +4,131 @@
**English** | 中文 ([简体](PLATFORM_zh.md), [繁體](PLATFORM_zh_Hant.md))
<!-- #END LANGUAGE_SWITCHER -->
## Launcher Compatibility
<!-- #BEGIN BLOCK -->
<!-- #PROPERTY NAME=PLATFORM_TABLE -->
| | Windows | Linux | macOS | FreeBSD |
|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.8) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.8) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.8) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.8) | / | / |
| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
<!-- #PROPERTY NAME=LAUNCHER_COMPATIBILITY -->
<table>
<thead>
<tr>
<th></th>
<th>Windows</th>
<th>Linux</th>
<th>macOS</th>
<th>FreeBSD</th>
</tr>
</thead>
<tbody>
<tr>
<td>x86-64</td>
<td>
✅️ Fully supported (Windows 7 ~ Windows 11)
<br>
✅️ Fully supported (Windows Server 2008 R2 ~ 2025)
<br>
🕰️ <a href="https://github.com/HMCL-dev/HMCL/releases?q=3.6">HMCL 3.6</a> (Windows Vista)
<br>
🕰️ <a href="https://github.com/HMCL-dev/HMCL/releases?q=3.6">HMCL 3.6</a> (Windows Server 2003 ~ 2008)
</td>
<td>✅️ Fully supported</td>
<td>✅️ Fully supported</td>
<td>✅ Fully supported</td>
</tr>
<tr>
<td>x86</td>
<td>
🕰️ Limited support (Windows 7 ~ Windows 10)
<br>
🕰️ <a href="https://github.com/HMCL-dev/HMCL/releases?q=3.6">HMCL 3.6</a> (Windows XP/Vista)
</td>
<td>🕰️ Limited support</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>ARM64</td>
<td>✅️ Fully supported</td>
<td>✅️ Fully supported</td>
<td>✅️ Fully supported</td>
<td>/</td>
</tr>
<tr>
<td>ARM32</td>
<td>/️</td>
<td>🕰️ Limited support</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>MIPS64el</td>
<td>/</td>
<td>🕰️ Limited support</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>RISC-V 64</td>
<td>/</td>
<td>✅️ Fully supported</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>LoongArch64</td>
<td>/</td>
<td>
✅️ Fully supported (New World)
<br>
🕰️ Limited support (Old World)
</td>
<td>/</td>
<td>/</td>
</tr>
</tbody>
</table>
<!-- #END BLOCK -->
Legend:
* ✅️ Fully supported
Platforms that are fully supported. HMCL will provide support for these platforms as much as possible.
* 🕰️ Limited support
These platforms are usually legacy platforms.
HMCL can work on these platforms, but some features may not be available.
We may drop some features for these platforms to reduce maintenance costs.
* 🕰️ HMCL 3.6 (Limited support)
The main branch of HMCL no longer supports these platforms.
We continue to provide security patches and bug fixes for these platforms through the HMCL 3.6 LTS branch,
but no feature updates will be available.
* / (Not supported)
HMCL does not support these platforms yet. We may support them in the future.
## Game Compatibility
<!-- #BEGIN BLOCK -->
<!-- #PROPERTY NAME=GAME_COMPATIBILITY -->
| | Windows | Linux | macOS | FreeBSD |
|-----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:----------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.10) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.10) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (use Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.5) | / | / |
| LoongArch64 (New World) | / | 👌 (Minecraft 1.6~1.21.10) | / | / |
| LoongArch64 (Old World) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
<!-- #END BLOCK -->
Legend:
@@ -41,3 +152,79 @@ Legend:
We have no plans to support these platforms at this time, mainly because we do not have the equipment to test them.
If you can help us adapt, please file a support request via GitHub Issue.
## Terracotta Compatibility
<!-- #BEGIN BLOCK -->
<!-- #PROPERTY NAME=TERRACOTTA_COMPATIBILITY -->
<table>
<thead>
<tr>
<th></th>
<th>Windows</th>
<th>Linux</th>
<th>macOS</th>
<th>FreeBSD</th>
</tr>
</thead>
<tbody>
<tr>
<td>x86-64</td>
<td>
✅️ (Windows 10 ~ Windows 11)
<br>
✅️ (Windows Server 2016 ~ 2025)
</td>
<td>✅️</td>
<td>✅️</td>
<td>✅️</td>
</tr>
<tr>
<td>x86</td>
<td>/</td>
<td>/</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>ARM64</td>
<td>✅️</td>
<td>✅️</td>
<td>✅️</td>
<td>/</td>
</tr>
<tr>
<td>ARM32</td>
<td>/️</td>
<td>/</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>MIPS64el</td>
<td>/</td>
<td>/</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>RISC-V 64</td>
<td>/</td>
<td>❔</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>LoongArch64</td>
<td>/</td>
<td>
✅️ (New World)
<br>
❌ (Old World)
</td>
<td>/</td>
<td>/</td>
</tr>
</tbody>
</table>
<!-- #END BLOCK -->
+208 -14
View File
@@ -4,22 +4,138 @@
[English](PLATFORM.md) | **中文** (**简体**, [繁體](PLATFORM_zh_Hant.md))
<!-- #END LANGUAGE_SWITCHER -->
## 启动器兼容性
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=PLATFORM_TABLE -->
<!-- #PROPERTY REPLACE="\\(Old World\\)" "(旧世界)" -->
<!-- #PROPERTY NAME=LAUNCHER_COMPATIBILITY -->
<!-- #PROPERTY REPLACE="Fully supported" "完整支持" -->
<!-- #PROPERTY REPLACE="Limited support" "有限支持" -->
<!-- #PROPERTY REPLACE="Old World" "旧世界" -->
<!-- #PROPERTY REPLACE="New World" "新世界" -->
<table>
<thead>
<tr>
<th></th>
<th>Windows</th>
<th>Linux</th>
<th>macOS</th>
<th>FreeBSD</th>
</tr>
</thead>
<tbody>
<tr>
<td>x86-64</td>
<td>
✅️ 完整支持 (Windows 7 ~ Windows 11)
<br>
✅️ 完整支持 (Windows Server 2008 R2 ~ 2025)
<br>
🕰️ <a href="https://github.com/HMCL-dev/HMCL/releases?q=3.6">HMCL 3.6</a> (Windows Vista)
<br>
🕰️ <a href="https://github.com/HMCL-dev/HMCL/releases?q=3.6">HMCL 3.6</a> (Windows Server 2003 ~ 2008)
</td>
<td>✅️ 完整支持</td>
<td>✅️ 完整支持</td>
<td>✅ 完整支持</td>
</tr>
<tr>
<td>x86</td>
<td>
🕰️ 有限支持 (Windows 7 ~ Windows 10)
<br>
🕰️ <a href="https://github.com/HMCL-dev/HMCL/releases?q=3.6">HMCL 3.6</a> (Windows XP/Vista)
</td>
<td>🕰️ 有限支持</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>ARM64</td>
<td>✅️ 完整支持</td>
<td>✅️ 完整支持</td>
<td>✅️ 完整支持</td>
<td>/</td>
</tr>
<tr>
<td>ARM32</td>
<td>/️</td>
<td>🕰️ 有限支持</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>MIPS64el</td>
<td>/</td>
<td>🕰️ 有限支持</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>RISC-V 64</td>
<td>/</td>
<td>✅️ 完整支持</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>LoongArch64</td>
<td>/</td>
<td>
✅️ 完整支持 (新世界)
<br>
🕰️ 有限支持 (旧世界)
</td>
<td>/</td>
<td>/</td>
</tr>
</tbody>
</table>
<!-- #END COPY -->
图例:
* ✅️ 完整支持
受到完整支持的平台。HMCL 会尽可能为此平台提供支持。
* 🕰️ 有限支持
这些平台通常是老旧的遗留平台。
HMCL 可以在这些平台上工作,但部分功能可能无法使用。
我们可能会为了降低维护成本而放弃为此平台提供部分功能。
* 🕰️ HMCL 3.6 (有限支持)
HMCL 主分支不再支持此平台。
我们通过 HMCL 3.6 LTS 分支继续为该平台提供安全补丁和错误修复,
但此平台上将无法接受到功能更新。
* / (不受支持)
HMCL 尚不支持此平台。我们可能会在未来支持此平台。
## 游戏兼容性
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=GAME_COMPATIBILITY -->
<!-- #PROPERTY REPLACE="Old World" "旧世界" -->
<!-- #PROPERTY REPLACE="New World" "新世界" -->
<!-- #PROPERTY REPLACE="\\(use Rosetta 2\\)" "(使用 Rosetta 2)" -->
| | Windows | Linux | macOS | FreeBSD |
|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.8) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.8) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.8) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.8) | / | / |
| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
| | Windows | Linux | macOS | FreeBSD |
|-----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:----------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.10) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.10) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.5) | / | / |
| LoongArch64 (新世界) | / | 👌 (Minecraft 1.6~1.21.10) | / | / |
| LoongArch64 (旧世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
<!-- #END COPY -->
图例:
@@ -43,3 +159,81 @@
我们目前还没有打算支持这些平台,主要是因为我们没有测试这些平台的设备。
如果你能帮助我们进行测试,请通过提交 Issue 提出支持请求。
## 陶瓦联机兼容性
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=TERRACOTTA_COMPATIBILITY -->
<!-- #PROPERTY REPLACE="Old World" "旧世界" -->
<!-- #PROPERTY REPLACE="New World" "新世界" -->
<table>
<thead>
<tr>
<th></th>
<th>Windows</th>
<th>Linux</th>
<th>macOS</th>
<th>FreeBSD</th>
</tr>
</thead>
<tbody>
<tr>
<td>x86-64</td>
<td>
✅️ (Windows 10 ~ Windows 11)
<br>
✅️ (Windows Server 2016 ~ 2025)
</td>
<td>✅️</td>
<td>✅️</td>
<td>✅️</td>
</tr>
<tr>
<td>x86</td>
<td>/</td>
<td>/</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>ARM64</td>
<td>✅️</td>
<td>✅️</td>
<td>✅️</td>
<td>/</td>
</tr>
<tr>
<td>ARM32</td>
<td>/️</td>
<td>/</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>MIPS64el</td>
<td>/</td>
<td>/</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>RISC-V 64</td>
<td>/</td>
<td>❔</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>LoongArch64</td>
<td>/</td>
<td>
✅️ (新世界)
<br>
❌ (旧世界)
</td>
<td>/</td>
<td>/</td>
</tr>
</tbody>
</table>
<!-- #END COPY -->
+208 -14
View File
@@ -4,22 +4,138 @@
[English](PLATFORM.md) | **中文** ([简体](PLATFORM_zh.md), **繁體**)
<!-- #END LANGUAGE_SWITCHER -->
## 啟動器相容性
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=PLATFORM_TABLE -->
<!-- #PROPERTY REPLACE="\\(Old World\\)" "(舊世界)" -->
<!-- #PROPERTY NAME=LAUNCHER_COMPATIBILITY -->
<!-- #PROPERTY REPLACE="Fully supported" "完整支援" -->
<!-- #PROPERTY REPLACE="Limited support" "有限支援" -->
<!-- #PROPERTY REPLACE="New World" "新世界" -->
<!-- #PROPERTY REPLACE="Old World" "舊世界" -->
<table>
<thead>
<tr>
<th></th>
<th>Windows</th>
<th>Linux</th>
<th>macOS</th>
<th>FreeBSD</th>
</tr>
</thead>
<tbody>
<tr>
<td>x86-64</td>
<td>
✅️ 完整支援 (Windows 7 ~ Windows 11)
<br>
✅️ 完整支援 (Windows Server 2008 R2 ~ 2025)
<br>
🕰️ <a href="https://github.com/HMCL-dev/HMCL/releases?q=3.6">HMCL 3.6</a> (Windows Vista)
<br>
🕰️ <a href="https://github.com/HMCL-dev/HMCL/releases?q=3.6">HMCL 3.6</a> (Windows Server 2003 ~ 2008)
</td>
<td>✅️ 完整支援</td>
<td>✅️ 完整支援</td>
<td>✅ 完整支援</td>
</tr>
<tr>
<td>x86</td>
<td>
🕰️ 有限支援 (Windows 7 ~ Windows 10)
<br>
🕰️ <a href="https://github.com/HMCL-dev/HMCL/releases?q=3.6">HMCL 3.6</a> (Windows XP/Vista)
</td>
<td>🕰️ 有限支援</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>ARM64</td>
<td>✅️ 完整支援</td>
<td>✅️ 完整支援</td>
<td>✅️ 完整支援</td>
<td>/</td>
</tr>
<tr>
<td>ARM32</td>
<td>/️</td>
<td>🕰️ 有限支援</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>MIPS64el</td>
<td>/</td>
<td>🕰️ 有限支援</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>RISC-V 64</td>
<td>/</td>
<td>✅️ 完整支援</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>LoongArch64</td>
<td>/</td>
<td>
✅️ 完整支援 (新世界)
<br>
🕰️ 有限支援 (舊世界)
</td>
<td>/</td>
<td>/</td>
</tr>
</tbody>
</table>
<!-- #END COPY -->
圖例:
* ✅️ 完整支援
受到完整支援的平臺。HMCL 會盡可能為此平臺提供支援。
* 🕰️ 有限支援
這些平臺通常是老舊的遺留平臺。
HMCL 可以在這些平臺上運作,但部分功能可能無法使用。
我們可能會為了降低維護成本而放棄為此平臺提供部分功能。
* 🕰️ HMCL 3.6(有限支援)
HMCL 主分支不再支援此平臺。
我們透過 HMCL 3.6 LTS 分支繼續為該平臺提供安全修補與錯誤修復,
但此平臺上將無法獲得功能更新。
* /(不支援)
HMCL 尚未支援此平臺。我們未來可能會支援此平臺。
## 遊戲相容性
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=GAME_COMPATIBILITY -->
<!-- #PROPERTY REPLACE="New World" "新世界" -->
<!-- #PROPERTY REPLACE="Old World" "舊世界" -->
<!-- #PROPERTY REPLACE="\\(use Rosetta 2\\)" "(使用 Rosetta 2)" -->
| | Windows | Linux | macOS | FreeBSD |
|----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:---------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.8) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.8) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.8) | / | / |
| LoongArch64 | / | 👌 (Minecraft 1.6~1.21.8) | / | / |
| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
| | Windows | Linux | macOS | FreeBSD |
|-----------------------------|:--------------------------------------------------|:---------------------------|:------------------------------------------------------------------------|:----------------------------|
| x86-64 | ✅️ | ✅️ | ✅️ | 👌 (Minecraft 1.13~1.21.10) |
| x86 | ✅️ (~1.20.4) | ✅️ (~1.20.4) | / | / |
| ARM64 | 👌 (Minecraft 1.8~1.18.2)<br/>✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.21.10) | 👌 (Minecraft 1.6~1.18.2)<br/>✅ (Minecraft 1.19+)<br/>✅ (使用 Rosetta 2) | ❔ |
| ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / |
| MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / |
| RISC-V 64 | / | 👌 (Minecraft 1.13~1.21.5) | / | / |
| LoongArch64 (新世界) | / | 👌 (Minecraft 1.6~1.21.10) | / | / |
| LoongArch64 (舊世界) | / | 👌 (Minecraft 1.6~1.20.1) | / | / |
| PowerPC-64 (Little-Endian) | / | ❔ | / | / |
| S390x | / | ❔ | / | / |
<!-- #END COPY -->
圖例:
@@ -43,3 +159,81 @@
我們目前還沒有打算支援這些平臺,主要是因為我們沒有測試這些平臺的裝置。
如果你能幫助我們進行測試,請透過提交 Issue 提出支援請求。
## 陶瓦聯機相容性
<!-- #BEGIN COPY -->
<!-- #PROPERTY NAME=TERRACOTTA_COMPATIBILITY -->
<!-- #PROPERTY REPLACE="New World" "新世界" -->
<!-- #PROPERTY REPLACE="Old World" "舊世界" -->
<table>
<thead>
<tr>
<th></th>
<th>Windows</th>
<th>Linux</th>
<th>macOS</th>
<th>FreeBSD</th>
</tr>
</thead>
<tbody>
<tr>
<td>x86-64</td>
<td>
✅️ (Windows 10 ~ Windows 11)
<br>
✅️ (Windows Server 2016 ~ 2025)
</td>
<td>✅️</td>
<td>✅️</td>
<td>✅️</td>
</tr>
<tr>
<td>x86</td>
<td>/</td>
<td>/</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>ARM64</td>
<td>✅️</td>
<td>✅️</td>
<td>✅️</td>
<td>/</td>
</tr>
<tr>
<td>ARM32</td>
<td>/️</td>
<td>/</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>MIPS64el</td>
<td>/</td>
<td>/</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>RISC-V 64</td>
<td>/</td>
<td>❔</td>
<td>/</td>
<td>/</td>
</tr>
<tr>
<td>LoongArch64</td>
<td>/</td>
<td>
✅️ (新世界)
<br>
❌ (舊世界)
</td>
<td>/</td>
<td>/</td>
</tr>
</tbody>
</table>
<!-- #END COPY -->