Compare commits

...
Author SHA1 Message Date
f921370c21 [release/3.10] 修复系统代理 JVM 参数格式 (#5591)
https://github.com/HMCL-dev/HMCL/pull/5265

Co-authored-by: Xirren <michaelqiancn@gmail.com>
Co-authored-by: xirren <xirren@outlook.com>
2026-02-20 23:13:37 +08:00
Glavo 61774c2efd [release/3.10] 修复部分请求头中 Accept-Language 字段包含 Java 版本的问题 (#5590)
https://github.com/HMCL-dev/HMCL/pull/5508
2026-02-20 23:13:19 +08:00
GlavoandCiiLu 4c209d1eb6 [release/3.10] fix: 安装整合包时任务弹窗可能空白 (#5589)
https://github.com/HMCL-dev/HMCL/pull/5550

Co-authored-by: CiiLu <109708109+CiiLu@users.noreply.github.com>
2026-02-20 23:12:42 +08:00
Glavo a1c9f1be7d [release/3.10] 修复 MultipleSourceVersionList 不会使用备用下载源的问题 (#5588)
https://github.com/HMCL-dev/HMCL/pull/5585
2026-02-20 23:11:24 +08:00
Glavo 3d1bd96d56 [release/3.10] 使用自定义 EasyTier 服务器节点 (#5511)
https://github.com/HMCL-dev/HMCL/pull/5504
2026-02-10 22:12:07 +08:00
Glavo d48a3c2109 [release/3.10] 修复 Unzipper 解压空 zip 文件时抛出异常的问题 (#5353)
https://github.com/HMCL-dev/HMCL/pull/5267
2026-01-28 21:47:26 +08:00
Glavoand辞庐 a0f9ba1976 [release/3.10] 点击实例列表条目的中央区域无法触发 Rippler 效果 (#5354)
https://github.com/HMCL-dev/HMCL/pull/5310

Co-authored-by: 辞庐 <109708109+CiiLu@users.noreply.github.com>
2026-01-28 21:46:56 +08:00
Glavoand辞庐 b8df46ef87 [release/3.10] 修复返回页面时可能卡进空白页面的问题 (#5355)
https://github.com/HMCL-dev/HMCL/pull/5305

Co-authored-by: 辞庐 <109708109+CiiLu@users.noreply.github.com>
2026-01-28 21:46:33 +08:00
Glavoand神奇的赵二 0c04a013c8 [release/3.10] 修复自动安装界面卡片上按钮无法点击的问题 (#5351)
https://github.com/HMCL-dev/HMCL/pull/5251

Co-authored-by: 神奇的赵二 (OrzMiku) <miku@ecy.pink>
2026-01-28 21:39:34 +08:00
Glavoand神奇的赵二 3cf0acc505 [release/3.10] 优化实例列表,限制宽度防止出现水平滚动条 (#5350)
https://github.com/HMCL-dev/HMCL/pull/5243

Co-authored-by: 神奇的赵二 (OrzMiku) <miku@ecy.pink>
2026-01-28 21:39:15 +08:00
Glavo 3be5ea409b [release/3.10] 修复使用 GIF 背景时调整背景不透明度导致启动器崩溃的问题 (#5349)
https://github.com/HMCL-dev/HMCL/pull/5262
2026-01-28 21:39:00 +08:00
Glavo 42f03411de [release/3.10] 修复 GameVersionNumber.getDefaultGameVersions() 包含未混淆版本的问题 (#5348)
https://github.com/HMCL-dev/HMCL/pull/5261
2026-01-28 21:38:35 +08:00
Glavoand辞庐 941e7cd858 [release/3.10] 修复 Cleanroom 本地安装完成后会删除安装器的问题 (#5347)
https://github.com/HMCL-dev/HMCL/pull/5257

Co-authored-by: 辞庐 <109708109+CiiLu@users.noreply.github.com>
2026-01-28 21:38:16 +08:00
Glavo 61c1b041cd [release/3.10] RemoteImageLoader 在图标尚未加载时应当将图像更新为占位符 (#5346)
https://github.com/HMCL-dev/HMCL/pull/5239
2026-01-28 21:37:27 +08:00
Glavo 7a48be5364 [release/3.10] 将世界管理界面“生成启动脚本”按钮折叠到二级菜单 (#5345)
https://github.com/HMCL-dev/HMCL/pull/5198
2026-01-28 21:37:06 +08:00
20 changed files with 271 additions and 74 deletions

No files matched your search

@@ -33,6 +33,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.util.FXThread;
import org.jackhuang.hmcl.util.InvocationDispatcher;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.HttpRequest;
@@ -44,13 +45,15 @@ import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -310,11 +313,18 @@ public final class TerracottaManager {
public static TerracottaState.HostScanning setScanning() {
TerracottaState state = STATE_V.get();
if (state instanceof TerracottaState.PortSpecific portSpecific) {
String uri = NetworkUtils.withQuery(String.format("http://127.0.0.1:%d/state/scanning", portSpecific.port), Map.of(
"player", getPlayerName()
));
Task.supplyAsync(Schedulers.io(), TerracottaNodeList::fetch)
.thenComposeAsync(nodes -> {
List<Pair<String, String>> query = new ArrayList<>(nodes.size() + 1);
query.add(pair("player", getPlayerName()));
for (URI node : nodes) {
query.add(pair("public_nodes", node.toString()));
}
return new GetTask(NetworkUtils.withQuery(
"http://127.0.0.1:%d/state/scanning".formatted(portSpecific.port), query
)).setSignificance(Task.TaskSignificance.MINOR);
}).start();
new GetTask(uri).setSignificance(Task.TaskSignificance.MINOR).start();
return new TerracottaState.HostScanning(-1, -1, null);
}
return null;
@@ -323,15 +333,19 @@ public final class TerracottaManager {
public static Task<TerracottaState.GuestConnecting> setGuesting(String room) {
TerracottaState state = STATE_V.get();
if (state instanceof TerracottaState.PortSpecific portSpecific) {
String uri = NetworkUtils.withQuery(String.format("http://127.0.0.1:%d/state/guesting", portSpecific.port), Map.of(
"room", room,
"player", getPlayerName()
));
return new GetTask(uri)
.setSignificance(Task.TaskSignificance.MINOR)
.thenSupplyAsync(() -> new TerracottaState.GuestConnecting(-1, -1, null))
.setSignificance(Task.TaskSignificance.MINOR);
return Task.supplyAsync(Schedulers.io(), TerracottaNodeList::fetch)
.thenComposeAsync(nodes -> {
ArrayList<Pair<String, String>> query = new ArrayList<>(nodes.size() + 2);
query.add(pair("room", room));
query.add(pair("player", getPlayerName()));
for (URI node : nodes) {
query.add(pair("public_nodes", node.toString()));
}
return new GetTask(NetworkUtils.withQuery("http://127.0.0.1:%d/state/guesting".formatted(portSpecific.port), query))
.setSignificance(Task.TaskSignificance.MINOR)
.thenSupplyAsync(() -> new TerracottaState.GuestConnecting(-1, -1, null))
.setSignificance(Task.TaskSignificance.MINOR);
});
} else {
return null;
}
@@ -0,0 +1,103 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2026 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.terracotta;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonSerializable;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
import org.jackhuang.hmcl.util.gson.Validation;
import org.jackhuang.hmcl.util.i18n.LocaleUtils;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jetbrains.annotations.Nullable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/// @author Glavo
public final class TerracottaNodeList {
private static final String NODE_LIST_URL = "https://terracotta.glavo.site/nodes";
@JsonSerializable
private record TerracottaNode(String url, @Nullable String region) implements Validation {
@Override
public void validate() throws JsonParseException, TolerableValidationException {
Validation.requireNonNull(url, "TerracottaNode.url cannot be null");
try {
new URI(url);
} catch (URISyntaxException e) {
throw new JsonParseException("Invalid URL: " + url, e);
}
}
}
private static volatile List<URI> list;
public static List<URI> fetch() {
List<URI> list = TerracottaNodeList.list;
if (list != null)
return list;
synchronized (TerracottaNodeList.class) {
list = TerracottaNodeList.list;
if (list != null)
return list;
try {
List<TerracottaNode> nodes = HttpRequest.GET(NODE_LIST_URL)
.getJson(JsonUtils.listTypeOf(TerracottaNode.class));
if (nodes == null) {
list = List.of();
LOG.info("No available Terracotta nodes found");
} else {
list = nodes.stream()
.filter(node -> {
if (node == null)
return false;
try {
node.validate();
} catch (Exception e) {
LOG.warning("Invalid terracotta node: " + node, e);
return false;
}
return StringUtils.isBlank(node.region) || LocaleUtils.IS_CHINA_MAINLAND == "CN".equalsIgnoreCase(node.region);
})
.map(it -> URI.create(it.url()))
.toList();
LOG.info("Terracotta node list: " + list);
}
} catch (Exception e) {
LOG.warning("Failed to fetch terracotta node list", e);
list = List.of();
}
TerracottaNodeList.list = list;
return list;
}
}
private TerracottaNodeList() {
}
}
@@ -300,11 +300,15 @@ public class InstallerItem extends Control {
}
pane.getStyleClass().add("installer-item");
RipplerContainer container = new RipplerContainer(pane);
container.setPosition(JFXRippler.RipplerPos.FRONT);
getChildren().setAll(container);
container.setPosition(JFXRippler.RipplerPos.BACK);
StackPane paneWrapper = new StackPane();
paneWrapper.getStyleClass().add("installer-item-wrapper");
paneWrapper.getChildren().setAll(container);
getChildren().setAll(paneWrapper);
pane.pseudoClassStateChanged(LIST_ITEM, control.style == Style.LIST_ITEM);
pane.pseudoClassStateChanged(CARD, control.style == Style.CARD);
paneWrapper.pseudoClassStateChanged(CARD, control.style == Style.CARD);
if (control.iconType != null) {
ImageView view = new ImageView(control.iconType.getIcon());
@@ -82,7 +82,9 @@ public class TransitionPane extends StackPane {
duration, interpolator);
newAnimation.setOnFinished(e -> {
setMouseTransparent(false);
getChildren().remove(previousNode);
if (previousNode != currentNode) {
getChildren().remove(previousNode);
}
if (cacheHint != null) {
newView.setCache(false);
@@ -264,7 +264,7 @@ public class DecoratorController {
private Background createBackgroundWithOpacity(Image image, int opacity) {
if (opacity <= 0) {
return new Background(new BackgroundFill(new Color(1, 1, 1, 0), CornerRadii.EMPTY, Insets.EMPTY));
} else if (opacity >= 100) {
} else if (opacity >= 100 || image.getPixelReader() == null) {
return new Background(new BackgroundImage(
image,
BackgroundRepeat.NO_REPEAT,
@@ -139,7 +139,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
} else {
reject.accept(e.getMessage());
}
}).executor(true), i18n("message.downloading"), TaskCancellationAction.NORMAL);
}), i18n("message.downloading"), TaskCancellationAction.NORMAL);
} else {
// otherwise we still consider the file as modpack zip file
// since casually the url may not ends with ".zip"
@@ -156,7 +156,7 @@ public final class ModpackSelectionPage extends VBox implements WizardPage {
} else {
reject.accept(e.getMessage());
}
}).executor(true),
}),
i18n("message.downloading"),
TaskCancellationAction.NORMAL
);
@@ -81,7 +81,9 @@ public final class GameListCell extends ListCell<GameListItem> {
{
HBox center = new HBox();
center.setMouseTransparent(true);
root.setCenter(center);
center.setPrefWidth(Region.USE_PREF_SIZE);
center.setSpacing(8);
center.setAlignment(Pos.CENTER_LEFT);
@@ -103,7 +103,7 @@ public final class Versions {
i18n("install.failed.downloading.detail", file.getFile().getUrl()) + "\n" + StringUtils.getStackTrace(e),
i18n("download.failed.no_code"), MessageDialogPane.MessageType.ERROR);
}
}).executor(true),
}),
i18n("message.downloading"),
TaskCancellationAction.NORMAL
);
@@ -112,14 +112,12 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) {
toolbar.addNavigationDrawerItem(i18n("version.launch"), SVG.ROCKET_LAUNCH, this::launch, advancedListItem -> advancedListItem.setDisable(isReadOnly()));
toolbar.addNavigationDrawerItem(i18n("version.launch_script"), SVG.SCRIPT, this::generateLaunchScript, null);
}
if (ChunkBaseApp.isSupported(world)) {
PopupMenu chunkBasePopupMenu = new PopupMenu();
JFXPopup chunkBasePopup = new JFXPopup(chunkBasePopupMenu);
chunkBasePopupMenu.getContent().addAll(
new IconedMenuItem(SVG.EXPLORE, i18n("world.chunkbase.seed_map"), () -> ChunkBaseApp.openSeedMap(world), chunkBasePopup),
new IconedMenuItem(SVG.VISIBILITY, i18n("world.chunkbase.stronghold"), () -> ChunkBaseApp.openStrongholdFinder(world), chunkBasePopup),
@@ -144,6 +142,14 @@ public final class WorldManagePage extends DecoratorAnimatedPage implements Deco
PopupMenu managePopupMenu = new PopupMenu();
JFXPopup managePopup = new JFXPopup(managePopupMenu);
if (world.getGameVersion() != null && world.getGameVersion().isAtLeast("1.20", "23w14a")) {
managePopupMenu.getContent().addAll(
new IconedMenuItem(SVG.ROCKET_LAUNCH, i18n("version.launch"), this::launch, managePopup),
new IconedMenuItem(SVG.SCRIPT, i18n("version.launch_script"), this::generateLaunchScript, managePopup),
new MenuSeparator()
);
}
managePopupMenu.getContent().addAll(
new IconedMenuItem(SVG.OUTPUT, i18n("world.export"), () -> WorldManageUIUtils.export(world, sessionLockChannel), managePopup),
new IconedMenuItem(SVG.DELETE, i18n("world.delete"), () -> WorldManageUIUtils.delete(world, () -> fireEvent(new PageCloseEvent()), sessionLockChannel), managePopup),
@@ -66,6 +66,8 @@ public abstract class RemoteImageLoader {
cache.remove(uri);
}
writableValue.setValue(getPlaceholder());
{
List<WeakReference<WritableValue<Image>>> list = pendingRequests.get(uri);
if (list != null) {
+13 -11
View File
@@ -388,31 +388,33 @@
-fx-cursor: hand;
}
.installer-item {
-fx-padding: 8px;
.installer-item-wrapper {
-fx-background-color: -monet-surface;
-fx-background-radius: 4;
-fx-pref-width: 180px;
}
.installer-item:list-item {
.installer-item-wrapper:card {
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
}
.installer-item-wrapper .installer-item:list-item {
-fx-padding: 8px;
-fx-border-color: -monet-outline-variant;
-fx-border-width: 0 0 1 0;
-fx-alignment: center-left;
}
.installer-item:list-item > .installer-item-name {
.installer-item-wrapper .installer-item:list-item > .installer-item-name {
-fx-pref-width: 80px;
}
.installer-item:list-item > .installer-item-status {
.installer-item-wrapper .installer-item:list-item > .installer-item-status {
-fx-max-width: infinity;
}
.installer-item:card {
-fx-background-color: -monet-surface;
-fx-background-radius: 4;
.installer-item-wrapper .installer-item:card {
-fx-alignment: center;
-fx-pref-width: 180px;
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
}
/*******************************************************************************
+21 -21
View File
@@ -1,63 +1,63 @@
{
"__comment__": "THIS FILE IS MACHINE GENERATED! DO NOT EDIT!",
"version_latest": "0.4.1",
"version_latest": "0.4.2",
"packages": {
"windows-x86_64": {
"hash": "4693fec29bb54a0bb1a1a8a263a935f1673b8f76e84d125974cf259f1912a195beab4dfd04c23cae592cf71b116e82ecd48828b1445ab75c980c8cd79c777d21",
"hash": "6a98f524d4f00373696517306af8aa50d01d55ce4eadb27e9e4bc2f882707a0b5f20d5d4c33371d1459dcf5bf144ffed9beb414202d9ccf32b11dbbfcf19d650",
"files": {
"VCRUNTIME140.DLL": "3d4b24061f72c0e957c7b04a0c4098c94c8f1afb4a7e159850b9939c7210d73398be6f27b5ab85073b4e8c999816e7804fef0f6115c39cd061f4aaeb4dcda8cf",
"terracotta-0.4.1-windows-x86_64.exe": "3d29f7deb61b8b87e14032baecad71042340f6259d7ff2822336129714470e1a951cd96ee5d6a8c9312e7cf1bb9bb9d1cbf757cfa44d2b2f214362c32ea03b5b"
"terracotta-0.4.2-windows-x86_64.exe": "6e98d1f2380ed22fb5a2dd4aafce6c773e9cf69100c8bb8e49e7d6983756bdb9a31f80e06bcfbe5a2742144fe806d3d687dec54d8f09d87c659341f99dd9fd80"
}
},
"windows-arm64": {
"hash": "6c68da53142cc92598a9d3b803f110c77927ee7b481e2f623dfab17cd97fee4a58378e796030529713d3e394355135cc01d5f5d86cef7dbd31bbf8e913993d4c",
"hash": "fc1077247014ac0c712469498bde2ef7f6d881d5fcb7bdd5e11ebe20218fed365be19afdb8d453a79d77b729f866058522b910741767f4df947faa891434b463",
"files": {
"VCRUNTIME140.DLL": "5cb5ce114614101d260f4754c09e8a0dd57e4da885ebb96b91e274326f3e1dd95ed0ade9f542f1922fad0ed025e88a1f368e791e1d01fae69718f0ec3c7b98c8",
"terracotta-0.4.1-windows-arm64.exe": "d2cf0f29aac752d322e594da6144bbe2874aabde52b5890a6f04a33b77a377464cbf3367e40de6b046c7226517f48e23fd40e611198fcaa1f30503c36d99b20c"
"terracotta-0.4.2-windows-arm64.exe": "30a15c5c53e5817c5a3634532172559327474741d3b2c7ef4e8a30acc6f59cdcf3570bf5f583e3cbe9e2abc8253e977c1abda1e9f36c88c4e99240da257347d0"
}
},
"macos-x86_64": {
"hash": "c247ab9023cf47231f3a056ddf70fe823e504c19ce241bf264c0a3cf2981c044b236dc239f430afb2541445d1b61d18898f8af5e1c063f31fb952bdfbea4aff5",
"hash": "a762e4b2d6f84e899292b9e3856d009411a516d3c47f54575f843ce082f63dff2baa68ba0faa844b8b64fb12e91017386f15f5e7f975f8ee605bf8d4217cb091",
"files": {
"terracotta-0.4.1-macos-x86_64": "dd2cde70a950849498425b8b00c17fb80edb8dd9bc0312da4885d97a28a4959d87782bd2202ef7f71bdbf2a92609089585c9e3faf24c10df6150e221e111a764",
"terracotta-0.4.1-macos-x86_64.pkg": "6057d5b4ea93da45796277a20ddaea7850e8c66326ded769f20fff74e132b453d6d615d937fc8f1d62b0b36b26961df5afb76535aecf85f25d2160889225ac6d"
"terracotta-0.4.2-macos-x86_64": "24efb85390eff88a538ed7e503fb1488e5e622730ca30c741a0e8b4c8f4e8d4868a2f9f38da8de540aeb535af2fd1e41c7081dae9c700e8a1a03b6c540218164",
"terracotta-0.4.2-macos-x86_64.pkg": "0f80437061231018ec0f860f875aba02cae9e0b36d21f2db8e99d25948806e1119f7844a4d4acca01d6124d7079718762cdebe3756b005a55632de7029fe0d24"
}
},
"macos-arm64": {
"hash": "0ddf48a44ea2563c37707caea8381ad3c69da73dd443d07dd98fe630f4e24ccda6f1fcdc9207a66bf61758b887a7b47186883ccd12bcb6b68387d8d756873f44",
"hash": "09e444fea2d9fd19f3e5cb62e29055228345be163924cbd408d947646fafed1012cf48508ee6a155ede3d571e2ffaa72d09ceeb1493c8a60feb05e0699f19ba3",
"files": {
"terracotta-0.4.1-macos-arm64": "ddc335ee082b4c0323314d482933810fc2d377cfc131a9aa978b54f245f1bed8973139edf7cf94f7ae6c04660dfe18d275b1a81638781e482b4ff585f351eed9",
"terracotta-0.4.1-macos-arm64.pkg": "5cbb41a450f080234b66fa264351bd594e3f6ef1599c96f277baa308e34dd26caefa3a34b3d65e972bc20868f2d4a661e8b3910d4b0311a374c6ac680bdccf8f"
"terracotta-0.4.2-macos-arm64": "8e59a9d78acd57702dc044d6f2799c6af586b075a262f7d4dbbf0876e1af8d8271e04783c24ff820b801e3b14cd0190ab8403d097f3f2d98b6d911f95ed1e972",
"terracotta-0.4.2-macos-arm64.pkg": "b0b72f8883767c359f0700e958bd859dd5932c4674fbdf2a32f7de189fb9f54822d4825a754dc856416c8760338eb8ffca80b40c0a57c608fbe6ae9928888993"
}
},
"linux-x86_64": {
"hash": "c00b0622203c1610a8e72bde5230fca6fe56cf1f45dc9bc7e857350f3a050d442c814084758090799b9a5b875730fa666539ee75bec706c05d9338ea208301eb",
"hash": "d326ad95815d04568d485b5038e40ffc47ca54292fa0925eee6f5cea014024f901d661708aac2a743037b990882ad82b4d0b7bb03dc3b2fe720dbf0f3efe1c98",
"files": {
"terracotta-0.4.1-linux-x86_64": "e53a9d8ec085ef7a7816b3da374e5c88fced2cf8319d447be3226422d01f9d7ee2019e403eafe21082135050652b2788b7d9520cc432c8d08931930b99595ed7"
"terracotta-0.4.2-linux-x86_64": "fac328ba8957a711b03557bb913940f22d61b76608cd203fdf51024b6f94b19f5bc91c9b8a9fa80baf6968e1e6873c1880fd4cf54a2f8e3c6cf1e6ac161f8d0c"
}
},
"linux-arm64": {
"hash": "4d661e1123ca050980960fe2d32b8d93a2c3ba807748569a45f16050fb1b52089bfc64c2dd64aba652dfed0e4ac3fba1ff9518cc3f95598c99fc71d93552b589",
"hash": "57c08f48d9535e93ad547d2dfc852d267992cc164a7208b42a2da0a6cbc2f21862f610e02a746b4b67150f4dec26b86a4f96eb9bd2f58d124d5b40ba50c6d55e",
"files": {
"terracotta-0.4.1-linux-arm64": "d4ccf6ff8c2fac060fecaa3c8368677f9342e283f2435493b3e259e922ee0bb0b08f15a292bf91898800a462336c64d0dee7b73f03c1752e5b0988a05adb3b52"
"terracotta-0.4.2-linux-arm64": "d807744c2041c98686e4b505324713badea7a0f31e8810be49ae053a63fb6dfc474ac58d678fb93eea0dd5cccff7372d9ec6135a1046f4b306cad35cd90ecacd"
}
},
"linux-loongarch64": {
"hash": "8297bb37617e9e7ce282fc553c5b14c84a900bcff4d025be31fd4a4da8b3943d040afc6143aa17de9a88e5fa29af7254d38db8ae6418ee539c2301632448da09",
"hash": "a19f4ad4fa1fd2f3c1c867f8743178ab0524d1634d63e122ffc080623e4f14207ecab58830fa0d888696ef28ce4cfa189e6ffbe92c6e1aa1836652617a96a801",
"files": {
"terracotta-0.4.1-linux-loongarch64": "ffdf7582d095307b91ddfc3e5d0daa78d434e4e34916d0cdf1520ae74b188fe5a48307047bf2da9a526eb725fe80cf230a93001bc8199d236b9cf28a1beaa6e9"
"terracotta-0.4.2-linux-loongarch64": "7d4896b275207def0861e544cf2afc9501c1946f1bc8bb2bad9ef4510dd915eabb63eeedff725e69565cdf5c27c146b52dd75d24115d5141909c0865f89cd7b9"
}
},
"linux-riscv64": {
"hash": "092f863885205107525e7ccb0e18977f6fd3018910ca5819772ec741dd8cffee52cc352a44b928bd2ba99ab881adaadff9e3bf4bf283f7384a35fea14becb0b4",
"hash": "ef3dfcf3c64dfcaf94adc518f0184d017a32a924f67ef33ffb8128f20669cf177cd345df3be8c00225a275bd44f70657b94fb43c60afeb21238000ac7a7f1721",
"files": {
"terracotta-0.4.1-linux-riscv64": "a6e5d70ddc433bf804764b69e8c204a6a428ece22b9d8ab713ed339fb81bfa1d29daeb6bdfd62c85ff193396315f96172f4a28925e5a4efc45f7d6fa868782a9"
"terracotta-0.4.2-linux-riscv64": "afddc55f16f5cf3103548d13f7a3238964c8899dba8eb0e1fdc9e354da65e91fa4486de4b8680a7a3d9326eacf18c1ddb6b5b1f9459908f35f77b36f769172ea"
}
},
"freebsd-x86_64": {
"hash": "288bd7a97b2e2c5fb3c7d8107ed1311bc881fb74cf92da40b6c84156239d0832d2306b74b1e93a683188a5db896a6506326c6a7a4ac0ab2e798fa4f1f00787f0",
"hash": "a4d02ebbb1419f4e9265f5b24060432799151a27e0d1871690d2f3a51f40bcfd6d92c176001a15a62caa9bf31ea1b67a7b2da10e3351bfcdd36bfac1377d08d9",
"files": {
"terracotta-0.4.1-freebsd-x86_64": "c90e7db3c5e5cc8d53d8d10a8bf88899e2c418758c14c3d14bc24efc32a711f76882bb5b5a49db5b0256ead4f22b666139bd2a212bf09036d7e7e64ddec4ec3c"
"terracotta-0.4.2-freebsd-x86_64": "a50da02752cef73d21804b26eb02261568ed3053c8fef1074014d50226843810f71e6eda39a53af3bad8e173cec40826631a4c8877af460d14a307f1b0a62519"
}
}
},
@@ -20,6 +20,8 @@ package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.task.Task;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
@@ -47,25 +49,58 @@ public class MultipleSourceVersionList extends VersionList<RemoteVersion> {
private Task<?> refreshAsync(String gameVersion, int sourceIndex) {
VersionList<?> versionList = backends[sourceIndex];
return versionList.refreshAsync(gameVersion)
.thenComposeAsync(() -> {
Task<?> refreshTask = versionList.refreshAsync(gameVersion);
return new Task<Object>() {
private Task<?> nextTask = null;
{
setSignificance(TaskSignificance.MODERATE);
setName("MultipleSourceVersionList.refreshAsync(" + sourceIndex + ")");
}
@Override
public Collection<Task<?>> getDependents() {
return List.of(refreshTask);
}
@Override
public Collection<? extends Task<?>> getDependencies() {
return nextTask != null ? List.of(nextTask) : List.of();
}
@Override
public boolean isRelyingOnDependents() {
return false;
}
@Override
public void execute() throws Exception {
if (isDependentsSucceeded()) {
lock.writeLock().lock();
try {
versions.putAll(gameVersion, versionList.getVersions(gameVersion));
} catch (Exception e) {
if (sourceIndex == backends.length - 1) {
LOG.warning("Failed to fetch versions list from all sources", e);
throw e;
} else {
LOG.warning("Failed to fetch versions list and try to fetch from other source", e);
return refreshAsync(gameVersion, sourceIndex + 1);
}
} finally {
lock.writeLock().unlock();
}
return null;
});
setResult(refreshTask.getResult());
} else {
Exception exception = refreshTask.getException();
assert exception != null;
if (sourceIndex == backends.length - 1) {
LOG.warning("Failed to fetch versions list from all sources", exception);
setSignificance(TaskSignificance.MINOR);
throw exception;
} else {
LOG.warning("Failed to fetch versions list and try to fetch from other source", exception);
nextTask = refreshAsync(gameVersion, sourceIndex + 1);
nextTask.storeTo(this::setResult);
}
}
}
};
}
@Override
@@ -92,7 +92,10 @@ public final class CleanroomInstallTask extends Task<Version> {
@Override
public void postExecute() throws Exception {
Files.deleteIfExists(installer);
if (remote != null) {
Files.deleteIfExists(installer);
}
setResult(task.getResult());
}
@@ -172,7 +172,7 @@ public class DefaultLauncher extends Launcher {
Proxy.Type proxyType = options.getProxyType();
if (proxyType == null) {
res.addDefault("-Djava.net.useSystemProxies", "true");
res.addDefault("-Djava.net.useSystemProxies=", "true");
} else {
String proxyHost = options.getProxyHost();
int proxyPort = options.getProxyPort();
@@ -212,7 +212,7 @@ public abstract class FetchTask<T> extends Task<T> {
do {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(currentURI)
.timeout(Duration.ofMillis(NetworkUtils.TIME_OUT))
.header("User-Agent", Holder.USER_AGENT);
.header("User-Agent", NetworkUtils.USER_AGENT);
headers.forEach(requestBuilder::header);
response = Holder.HTTP_CLIENT.send(requestBuilder.build(), BODY_HANDLER);
@@ -518,7 +518,6 @@ public abstract class FetchTask<T> extends Task<T> {
/// Ensure that [#HTTP_CLIENT] is initialized after ProxyManager has been initialized.
private static final class Holder {
private static final HttpClient HTTP_CLIENT;
private static final String USER_AGENT = System.getProperty("http.agent", "HMCL");
static {
if (!initialized) {
@@ -40,6 +40,8 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
* @author huangyuhui
*/
public final class NetworkUtils {
public static final String USER_AGENT = System.getProperty("http.agent", "HMCL");
public static final String PARAMETER_SEPARATOR = "&";
public static final String NAME_VALUE_SEPARATOR = "=";
public static final int TIME_OUT = 8000;
@@ -95,6 +97,27 @@ public final class NetworkUtils {
return sb.toString();
}
public static String withQuery(String baseUrl, List<Pair<String, String>> params) {
StringBuilder sb = new StringBuilder(baseUrl);
boolean first = true;
for (Pair<String, String> param : params) {
if (param.getValue() == null)
continue;
if (first) {
if (!baseUrl.isEmpty()) {
sb.append('?');
}
first = false;
} else {
sb.append(PARAMETER_SEPARATOR);
}
sb.append(encodeURL(param.getKey()));
sb.append(NAME_VALUE_SEPARATOR);
sb.append(encodeURL(param.getValue()));
}
return sb.toString();
}
public static List<URI> withQuery(List<URI> list, Map<String, String> params) {
return list.stream().map(uri -> URI.create(withQuery(uri.toString(), params))).collect(Collectors.toList());
}
@@ -147,6 +170,7 @@ public final class NetworkUtils {
connection.setReadTimeout(TIME_OUT);
if (connection instanceof HttpURLConnection httpConnection) {
httpConnection.setRequestProperty("Accept-Language", Locale.getDefault().toLanguageTag());
httpConnection.setRequestProperty("User-Agent", USER_AGENT);
httpConnection.setInstanceFollowRedirects(false);
}
return connection;
@@ -152,7 +152,7 @@ public final class Unzipper {
}
}
if (entryCount == 0 && !terminateIfSubDirectoryNotExists) {
if (entryCount == 0 && !"/".equals(subDirectory) && !terminateIfSubDirectoryNotExists) {
throw new NoSuchFileException("Subdirectory " + subDirectory + " does not exist in the zip file.");
}
}
@@ -795,7 +795,8 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
} else if (version instanceof Release release) {
currentRelease = release;
if (currentRelease.eaType == Release.ReleaseType.GA) {
if (currentRelease.eaType == Release.ReleaseType.GA
&& currentRelease.additional == Release.Additional.NONE) {
defaultGameVersions.addFirst(currentRelease.value);
}
} else if (version instanceof Special special) {
+1 -1
View File
@@ -18,7 +18,7 @@ pci-ids = "0.4.0"
java-info = "1.0"
authlib-injector = "1.2.7"
monet-fx = "0.4.0"
terracotta = "0.4.1"
terracotta = "0.4.2"
# testing
junit = "6.0.1"